干货转自网易实践者社区,感谢作者【严跃杰】
【歌词滚动效果】
滚动歌词比较常见的一种歌词显示方式,今天我们来讨论如何通过原生js来完成一个简单的滚动歌词实现。
一般来说,滚动歌词有如下几项需求:
1. 歌词在一个矩形区域内显示
2. 当前歌词行高亮
3. 在矩形显示区域中部固定位置显示当前歌词行
4. 当前歌词行切换到下一行时,下一行歌词高亮,并逐步移动到当前歌词行位置
5. 初始显示时歌词第一行顶住显示区域,当前歌词行未到达显示矩形区域的中部固定高亮位置时,不需要移动
6. 歌词尾行到达显示区域后,不需要移动
7. 用户可通过鼠标或者滚动条滚动歌词
8. 用户滚动歌词后,若当前歌词行被移动到矩形显示区域外,下一行歌词需定位到当前歌词行位置
其中需求5, 6通过图详述表示如下(黄色表示歌词容器,绿色表示歌词容器显示区域)
明确需求后,我们可以着手分析和实现了。如下图所示(我们假设高亮歌词固定位置在图中所示位置),我们通过设置歌词容器的scrollTop属性来上下移动歌词在显示区域的位置。
通常情况下,我们的目标srollTop要求满足:
scrollTop = offsetTop(lineno) – 2/5*clientHeight
但是根据需求5、6,我们需要满足
0 <= scrollTop(lineno) <= scrollHeight – clientHeight
替换进去,得倒如下限制条件:
2/5*clientHeight <= scrollTop(lineno) <= scrollHeight – 3/5*clientHeight
用代码实现获取目标scrollTop:
var _scrollTop;
if (_ep.offsetTop < __eul.clientHeight*2/5){
_scrollTop = 0;
} else if (_ep.offsetTop > (__eul.scrollHeight - __eul.clientHeight*(3/5))){
_scrollTop = __eul.scrollHeight - __eul.clientHeight;
} else {
_scrollTop = _ep.offsetTop - __eul.clientHeight*2/5;
}
通过延时递归来实现当前歌词行滚动目标位置的动画效果
__scroll = function(_crt, _dst, _step){
if(Math.abs(_crt - _dst) < _step){
return;
}
if(_crt < _dst){
__eul.scrollTop += _step;
_crt += _step;
} else {
__eul.scrollTop -= _step;
_crt -= _step;
}
setTimeout(__scroll.bind(this, _crt, _dst, _step), __freq);
};
完整实现如下:
var __freq = 30; // 滚动频率(ms)
var __fraction = 2/5; // 高亮显示行在歌词显示区域中的固定位置百分比
/**
* 当前歌词行(lineno)滚动
*/
var __go = function(_lineno){
var _time;
if (_lineno === 0) {
_time = __lis[_lineno].offset;
} else {
_time = __lis[_lineno].offset - __lis[_lineno-1].offset;
}
var _timer = setTimeout(__go.bind(this, _lineno+1), _time);
// 高亮下一行歌词
if (_lineno > 0) {
__eul.children[_lineno-1].setAttribute("class", "");
}
var _ep = __eul.children[_lineno];
_ep.setAttribute("class", "z-crt");
// 满足需求5,6
var _scrollTop;
if (_ep.offsetTop < __eul.clientHeight*__fraction){
_scrollTop = 0;
} else if (_ep.offsetTop > (__eul.scrollHeight - __eul.clientHeight*(1-__fraction))){
_scrollTop = __eul.scrollHeight - __eul.clientHeight;
} else {
_scrollTop = _ep.offsetTop - __eul.clientHeight*__fraction;
}
// 如用户拖动滚动条导致当前显示行超出显示区域范围,下一行直接定位到当前显示行
if (__eul.scrollTop > (_scrollTop + __eul.clientHeight*__fraction)
|| (__eul.scrollTop + __eul.clientHeight*__fraction) < _scrollTop){
__eul.scrollTop = _scrollTop;
} else { // 单行滚动
// 获取滚动步长
var _step = Math.ceil(Math.abs(__eul.scrollTop - _scrollTop)/(_time/__freq));
__scroll(__eul.scrollTop, _scrollTop, _step);
}
};
/**
* 歌词单行滚动实现
*/
__scroll = function(_crt, _dst, _step){
if(Math.abs(_crt - _dst) < _step){
return;
}
if(_crt < _dst){
__eul.scrollTop += _step;
_crt += _step;
} else {
__eul.scrollTop -= _step;
_crt -= _step;
}
setTimeout(__scroll.bind(this, _crt, _dst, _step), __freq);
};
__go(0);
附件demo可在chrome中运行
滚动歌词Demo.rar
【卡拉OK效果】
采用上述方法,我们有两种实现方案可以选择:
1.一行歌词设置一个背景,歌词推进时将红色背景从左逐步推进。如下图
这是一种比较直观和好理解方式,但是在实现时遇到如下两个问题:
1)性能损失:js每次修改背景页面需要重新渲染整行歌词,性能有所损失,特别是在pc端混合应用中采用离屏渲染模式用该方案实现桌面歌词的时候,
性能损失较为明显。
2)实现复杂度:在固定刷新频率下,推进量和时间计算较为复杂。