JavaScript可点击SVG滚动字幕动画:未完全移出屏幕问题
更新于2023年4月11日
我尝试使用JavaScript和SVG文本元素制作可点击的滚动字幕动画,此前的动画方向及位置问题已解决,但目前动画在未完全移出屏幕时就停止了,这让我十分困扰!
对于建议我使用JavaScript以外框架或库的朋友,我拒绝采纳!因为我正尝试为网站切换回「无框架」方案——无论选择何种框架或库,这些让网站外观精美的工具都可能遭黑客攻击,导致网站崩溃后需重建。因此这次我遵循KISS原则(保持简洁)——以下是我目前的代码,功能已接近完成,只是动画未完全移出屏幕就停止了。
更新后的代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>SVG Ani w/Clickable</title> <style> body, html { margin: 0; padding: 0; height: 100%; width: 100%; } #svg-container { position: relative; overflow: hidden; height: 100%; width: 100%; white-space: nowrap; } .svg-container { position: absolute; width: auto; height: 30px; } .svg { display: inline-block; cursor: pointer; } .svg text { fill: #333; font-size: 14px; font-family: Arial, Helvetica, sans-serif; } </style> </head> <body> <div id="svg-container"></div> <script> const links = [ { text: 'Welcome to a soon fully revamped site - using AI to construct it - click to learn more.', href: './aiwork.html' }, ]; const duration = 9000; const spacing = 20; function createSVGObject(link) { const div = document.createElement('div'); div.classList.add('svg-container'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); const a = document.createElement('a'); text.setAttribute('y', '20'); text.setAttribute('font-family', 'Arial, Helvetica, sans-serif'); text.textContent = link.text; svg.appendChild(text); a.appendChild(svg); div.appendChild(a); a.setAttribute('href', link.href); a.setAttribute('class', 'svg'); const computedWidth = text.getComputedTextLength(); div.style.width = (computedWidth + 10) + 'px'; svg.setAttribute('width', (computedWidth + 10) + 'px'); svg.setAttribute('height', '30'); svg.setAttribute('viewBox', `0 0 ${computedWidth + 10} 30`); svg.style.overflow = 'visible'; return div; } function animateSVGObject(object) { object.style.top = '0'; object.style.left = window.innerWidth + 'px'; const startTime = performance.now(); requestAnimationFrame(function animate(time) { const progress = (time - startTime) / duration; const distance = (window.innerWidth + object.getBoundingClientRect().width + spacing) * progress; const currentPosition = window.innerWidth - distance; object.style.left = currentPosition + 'px'; if (currentPosition + object.getBoundingClientRect().width > -object.getBoundingClientRect().width) { requestAnimationFrame(animate); } else { setTimeout(() => { object.style.left = window.innerWidth + 'px'; animateSVGObject(object); }, duration * links.length); } }); } links.forEach((link, index) => { const svgObject = createSVGObject(link); document.getElementById('svg-container').appendChild(svgObject); setTimeout(() => animateSVGObject(svgObject), duration * index); }); </script> <p>SVG</p> </body> </html>
请问当前代码中是什么原因导致SVG滚动字幕动画未完全移出屏幕就停止?
原因分析
动画提前停止的核心问题在于停止条件的逻辑错误:
在animateSVGObject函数的循环判断中,你使用了:
if (currentPosition + object.getBoundingClientRect().width > -object.getBoundingClientRect().width) { requestAnimationFrame(animate); }
这个条件的含义是:只要元素的右边缘(currentPosition + 元素宽度)大于 -元素宽度,就继续动画。但这个判定标准和"完全移出屏幕"的预期不符——屏幕左侧的边界是0,只要元素右边缘大于0,就说明仍有部分在屏幕可见范围内。当前的条件允许元素继续移动到屏幕左侧外一个元素宽度的位置才停止,但实际运行中,可能因为getBoundingClientRect()的实时计算偏差、窗口大小变化,或者总移动距离的计算冗余,导致动画在元素还未完全移出屏幕时就提前触发停止逻辑。
另外,你在计算总移动距离时加入了spacing参数,但停止条件并未匹配这个参数,进一步导致动画的终止位置和预期脱节。
修复方案
修改停止条件为判断元素右边缘是否大于0,确保元素完全移出屏幕左侧后才停止动画:
if (currentPosition + object.getBoundingClientRect().width > 0) { requestAnimationFrame(animate); }
同时,调整总移动距离的计算,移除冗余的spacing(如果需要间隔,应该在重新启动动画时添加延迟,而非加入移动距离),保证动画在duration时间内刚好完成从右侧进入到完全移出的过程:
const distance = (window.innerWidth + object.getBoundingClientRect().width) * progress;
这样修改后,动画会在元素完全离开屏幕可见区域时停止,随后进入下一轮循环。
内容的提问来源于stack exchange,提问作者Einar Petersen

