此文已由作者严跃杰授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
滚动歌词比较常见的一种歌词显示方式,今天我们来讨论如何通过原生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);
网易云免费体验馆,0成本体验20+款云产品!
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 Memcached Hash算法
【推荐】 反欺诈(Fraud Detection)中所用到的机器学习模型
【推荐】 3分钟掌握一个有数小技能:回头客分析