geekwen.com

用requestAnimationFrame实现文字滚动效果

上一周用requestAnimationFrame做了一个文字循环滚动的效果。用这个API的好处是:

  1. 它会通知浏览器,将会有动画执行,进而浏览器可以确定重绘的最佳方式
  2. 会针对动画进行代码优化
  3. 运行间隔精准(The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation. – MDN

恰好解决了以往用定时器setTimeout或者setInterval做动画时的一些问题。

此API兼容性如下:

具体需求

  • 从上往下滚动
  • 从容器外滚动到内部
  • 循环滚动

需求分析

从上往下滚动

说到滚动,肯定是要循环去改变某个值。可以是边距margin,也可以是top(用相对/绝对定位)。但是重复改变这两个值,会触发reflow,使浏览器重新渲染整个页面,性能低下,所以不推荐使用。另外还有一种方式:scrollTop。改变某个元素的scrollTop值,不会触发整个页面的reflow(根据chrome浏览器的paint_flashing工具观察得知,但没找到具体的文档说明,如有误欢迎指出)。所以我们需要给包裹容器设置固定高度,并且overflow:hidden,再通过scrollTop来滚动内容。

从容器外滚动到内部

这个简单,给内部的list加一个padding-bottom: 容器高度。初始化的时候,让包裹容器滚动到底部就行了。

循环滚动

这里是关键。怎么循环滚动?可以在内容滚动出容器的时候处理(超出容器的元素立马挪到list顶部);也可以在最后,容器滚动到顶部的时候处理(将list最后一个挪到list顶部)。这里我选择的后者。当容器scrollTop <= 1,即将滚动到顶部时,将list最后一个元素挪到list第一个,并且将scrollTop设为list元素的高度(这样挪过来的元素才会从容器外面滚动进来)。然后接着滚动。

代码

html
1
2
3
4
5
6
7
8
9
10
<div id="scroll-wrapper">
<ul class="list">
...
<li>这是标题5</li>
<li>这是标题4</li>
<li>这是标题3</li>
<li>这是标题2</li>
<li>这是标题1</li>
</ul>
</div>
scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$wrapper-height: 300px;
#scroll-wrapper {
width: 60%;
height: $wrapper-height;
margin: 20px auto;
border: 1px solid #ccc;
overflow: hidden;
}
.list {
margin: 0;
padding: 0;
padding-bottom: $wrapper-height; //为初始化预留空白位置
list-style: none;
line-height: 40px; // 设置好元素的高度
li {
padding-left: 20px;
padding-right: 20px;
}
}
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* @param selector {String} 元素选择器
**/
(function scrollAPI(selector) {
// 处理浏览器的兼容性
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame,
cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame,
$con = $(selector),
$list = $con.children('ul'),
ITEMHEIGHT = 40, // 元素高度
CONHEIGHT = $con.innerHeight(),
LISTHEIGHT = $list.outerHeight(),
SPEED = 0.4, // 滚动速度; 越大越快 不能小于零!!
sTop = LISTHEIGHT - CONHEIGHT, // 初始值应等于列表的实际高度
animation;
// 初始化样式,让容器默认滚动到底部
$con.scrollTop(sTop);
// 设置动画
animation = requestAnimationFrame(action);
// 循环的主体
function action() {
// 如果找不到元素, 则清除动画
if ($con.length == 0) {
cancelAnimationFrame(animation);
return false;
}
// 如果没有到达顶端,则减小sTop的值,向下滚动;
// 否则重置sTop的值为元素高度,并将list最后一个放到list第一个
if (sTop > 1) {
sTop -= SPEED;
} else {
sTop = ITEMHEIGHT;
$list.children('li:last-child').insertBefore($list.children('li:first-child'));
}
$con.scrollTop(sTop);
animation = requestAnimationFrame(action);
}
$con.off();
$con.on('mouseenter', function() {
// 鼠标进入, 则停止滚动;并且添加鼠标移出事件(移出后开启滚动)
cancelAnimationFrame(animation);
$con.off('mouseleave');
$con.on('mouseleave', function() {
animation = requestAnimationFrame(action);
});
});
}('#scroll-wrapper'));

讲解完毕。如果有需要可以改成向上滚动;也可以改成一个通用型的,通过传递参数控制滚动方向。

最终效果

See the Pen wen zi gun dong xiao guo by geekwen (@geekwen) on CodePen.

参考资料

Window.requestAnimationFrame()
Window.cancelAnimationFrame()