最近的一个页面需要一个文字水平滚动的效果,效果如图所示:
本来想在网上直接找代码一把梭,但发现现有代码都是用的 jQuery + animation,不符合项目的技术栈,就想干脆自己实现一个。最终用了18行代码实现了上面的效果。
原理
具体原理跟轮播图类似,把包裹文字的元素复制多一个出来,当第一个元素滚动到看不见的时候,把第一个元素重新插到尾部,因为两个元素是紧挨着的,第一个元素滚动到看不见的一瞬间,第二个元素也已经到了开始的位置,所以肉眼看不出来元素插入的过程。最终实现了滚动效果。
实现
HTML
<div class="scrollX">
<p class="scrollTxt">窗前明月光,疑是地上霜,举头望明月,低头思故乡</p>
</div>
第一步,拿到<p>
标签并复制,插入到父容器中:
const domScroll = document.querySelector('.scrollTxt');
const cloneNode = document.querySelector('.scrollTxt').cloneNode(true);
domScroll.parentNode.appendChild(cloneNode);
此时的HTML标签就变成了:
<div class="scrollX">
<p class="scrollTxt">窗前明月光,疑是地上霜,举头望明月,低头思故乡</p>
<p class="scrollTxt">窗前明月光,疑是地上霜,举头望明月,低头思故乡</p>
</div>
这样有两个问题:
①父元素随着子元素的增大而增大,需要给父元素一个固定的宽度,且隐藏溢出的部分;
②两个<p>标签不在同一行,需要让两个元素在同一行才有水平滚动的效果。
针对第一个问题,给 scrollX一个固定宽度加 overflow: hidden
就可以解决。
第二个问题,我是用了 flex布局,在父元素定义 display: flex
就让两个子元素在了同一个水平线上。最终的CSS:
.scrollX {
display: flex;
height: 100%;
width: 500px;
overflow: hidden;
p {
height: 100%;
lex-shrink: 0;
padding-right: 20px;
white-space: nowrap;
}
}
让元素动起来
让元素滚动有两种方法
①将元素设置为 position: absolute
并控制元素的left
属性,让元素往左移动。
②使用transform
并控制translateX()
让元素往左移动。
没吃过猪肉也见过猪跑,没写过动画的人,也知道第二种方法的性能会比第一种要好。为什么好呢,我们放在最后面总结,先把动画实现起来~
我们要让元素往左滚,那么要滚多远呢?就是元素本身的宽度,当元素滚动的距离超过自身宽度时,进行元素插入尾部的操作:
const domScroll2 = document.querySelectorAll('.scrollTxt')[1];
let width = domScroll2.getAttribute('width');
const animation = () => {
if (width > -100) {
domScroll.style = `transform: translateX(${width}%)`;
domScroll2.style = `transform: translateX(${width}%)`;
width -= 1;
} else {
domScroll.parentNode.appendChild(domScroll);
width = 0;
}
setTimeout(animation, 100); // 使用setTimeout 嵌套代替 setInterval
};
animation();
代码写到这里,动画就可以动起来了。
这里有一个实现的细节是,用 setTimeout
的嵌套代替setInterval
,原因是 setInterval
的运行机制并不是真实的“每隔XX秒后调用指定的函数”,因为setInterval
并不会将指定函数需要执行的时间考虑在内。比如我们用setInterval
每隔100ms调用一次函数A,如果A本身的执行需要95ms,那么A执行完,经过至少5ms就会马上被调用,所以为了保证每次执行都有一定的间隔,要使用 setTimeout
代替setInterval
。
在查资料的过程中,想起来还有requestAnimationFrame
这个API,这个API相当于一个时间被固定为 浏览器绘制频率(一般为16ms一次) 的setTimeout
,但它是setTimeout
的改进版,浏览器会为requestAnimationFrame
做优化,以每一次浏览器的重绘为间隔执行动画。而且,当浏览器不在当前 tab 的时候,requestAnimationFrame
调用的动画会停止。会根据浏览器绘制频率调整刷新时机。
GPU渲染导致的动画中字体模糊的问题
一开始实现的时候考虑过用 transition
来实现运动效果,发现运动过程中字体会变得模糊,而且不仅是动画中的字体会模糊,连页面上的其他字体也变得模糊起来。在实现了动画后考虑过使用will-change
优化动画,发现也存在同样的问题(区别是will-change
只会导致运动的字体变模糊)。
一番查找发现文字模糊是因为浏览器启用了GPU渲染,而GPU渲染不具备CPU渲染的次像素抗锯齿,导致了文字会出现模糊。
参考文章:
【翻译】:CSS 的 will-change 属性详解 - 前端 - 掘金
CSS3动画那么强,requestAnimationFrame还有毛线用? « 张鑫旭-鑫空间-鑫生活
你所不知道的setInterval | 晚晴幽草轩
深入理解requestAnimationFrame - 最骚的就是你 - 博客园