Svelte中如何获取动态加载HTML元素的宽高?
问题原因
你遇到的核心问题是:onMount钩子执行时机早于动态数据的渲染完成时间。静态内容在组件挂载时就已存在,所以onMount能拿到容器的真实宽高;但从数据库异步加载的page.data.personalMessage还未返回时,容器是空的,此时获取的尺寸自然是空白状态的错误值。
解决方案
方案1:父组件等待数据加载完成后再渲染打字机组件
在父组件中通过{#await}或{#if}确保数据存在后,再渲染TypewriterEffect,保证组件挂载时容器内已有完整内容。
修改+page.svelte:
<script> import { page } from '$app/state'; </script> <div class="catch-phrase"> {#await page.data} <!-- 可选:添加加载占位符 --> <div>加载中...</div> {:then data} <TypewriterEffect> <div>{data.personalMessage}</div> </TypewriterEffect> {/await} </div> <style> .catch-phrase { display: flex; align-items: center; margin: 0 10vw; max-width: 1200px; height: 90dvh; } </style>
此时Typewriter.svelte的onMount就能正常获取到完整内容渲染后的宽高,原代码的onMount逻辑可以保留(因为组件挂载时内容已存在)。
方案2:子组件监听内容变化,延迟获取尺寸
如果不想修改父组件,可以在Typewriter.svelte中使用Svelte的$effect监听容器内容变化,等内容实际渲染后再执行尺寸获取逻辑。
修改Typewriter.svelte:
<script> import { $effect } from 'svelte'; let container; let fullNode; let isReady = false; let chunkSize = 1; // 根据你的需求调整分片大小 let timeout; let callback = () => {}; // 自定义完成回调 // 监听容器内容变化,确保内容存在后再处理 $effect(() => { if (!container || !container.children.length) return; // 克隆完整内容节点 fullNode = container.cloneNode(true); // 设置容器固定宽高 container.style.height = `${container.offsetHeight}px`; container.style.width = `${container.offsetWidth}px`; // 清空内容准备打字机动画 container.innerHTML = ''; isReady = true; // 初始化交叉观察者 const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting && isReady) { console.log('launch !!'); typewriter(); observer.disconnect(); } }); }, { root: null, threshold: 1 } ); observer.observe(container); // 清理函数 return () => { observer.disconnect(); clearTimeout(timeout); }; }); // 文本分片函数(补全你原代码中缺失的逻辑) const splitHtmlText = (text, size) => { return text.split('').reduce((acc, char, idx) => { if (idx % size === 0) acc.push([]); acc[acc.length - 1].push(char); return acc; }, []).map(chunk => chunk.join('')); }; const typewriter = async () => { if (!fullNode) return; for (let i = 0; i < fullNode.childNodes.length; i++) { if (fullNode.childNodes[i].nodeType === 1) { const fullHtmlText = fullNode.childNodes[i].textContent; const splittedText = splitHtmlText(fullHtmlText, chunkSize); container.appendChild(fullNode.childNodes[i]); container.childNodes[i].textContent = ''; await type(container.childNodes[i], splittedText, 0); } } }; function type(node, splittedText, currentIndex) { return new Promise(resolve => { if (currentIndex >= splittedText.length) { resolve(); clearTimeout(timeout); callback(); return; } node.innerHTML = splittedText.slice(0, currentIndex + 1).join(''); timeout = setTimeout(() => type(node, splittedText, currentIndex + 1), 10); }); } </script> <div bind:this={container}>{@render children?.()}</div>
内容的提问来源于stack exchange,提问作者16ar
相关产品推荐
相关产品推荐

