原生js实现歌词滚动

勿忘初心2018-10-26 10:54

此文已由作者严跃杰授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


滚动歌词比较常见的一种歌词显示方式,今天我们来讨论如何通过原生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分钟掌握一个有数小技能:回头客分析