原生js实现歌词滚动以及卡拉OK效果

干货转自网易实践者社区,感谢作者【严跃杰】

【歌词滚动效果】

滚动歌词比较常见的一种歌词显示方式,今天我们来讨论如何通过原生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效果】

卡拉OK桌面歌词自从诞生以来一直广受音乐爱好者的欢迎,几乎成为众多PC端音乐软件标准配置。
桌面歌词的一般表现效果如下(由于ks只能上传10FPS GIF图,所以下图动画效果略有卡顿),同时用户可以根据自己喜好设置显示方式(单行,多行)、字体、颜色、透明度等,此外还提供了歌词窗口锁定等功能。
桌面歌词的诞生要归功于windows2000开始ms开放的layered window解决方案,它为应用开发者提供了开发异形窗口功能强大的API。音乐软件开发者用这些API开发出了桌面歌词,此后长久以来桌面歌词的实现原理都不尽相同,主要有如下两个步骤:
1. GDI/GDI+绘制
2. layered window输出
那么有没有其他实现桌面歌词的方法呢? layered window作为异形窗口唯一的解决方案我们无法绕过去,但是显然直接操作GDI/GDI+不是绘制歌词的唯一方法,今天我们就来看通过浏览器离屏渲染绘制卡拉OK歌词的实现方法。
要浏览器实现绘制,自然就要用到html/css/js。虽然css3.0提供了一系列动画功能,但是我们很快发现只靠css无法实现卡拉OK歌词效果的动画。那么如何实现呢,我们最终找到一种方法:镂空字体+背景色推进方式,如下:

采用上述方法,我们有两种实现方案可以选择:

1.一行歌词设置一个背景,歌词推进时将红色背景从左逐步推进。如下图

这是一种比较直观和好理解方式,但是在实现时遇到如下两个问题:

1)性能损失:js每次修改背景页面需要重新渲染整行歌词,性能有所损失,特别是在pc端混合应用中采用离屏渲染模式用该方案实现桌面歌词的时候,

性能损失较为明显。

2)实现复杂度:在固定刷新频率下,推进量和时间计算较为复杂。

2.每个字单独设置背景,在这个字所在的时间段内,只完成该字背景的推进,如下图。
该方案下,js每次修改背景实现推进时,页面只需要渲染单个字所在的区域,降低了性能损失。在实现复杂度上,由于处理的对象粒度变小了,相当于简化了处理对象的行为模式,因此实现难度也有所降低(虽然还是有点复杂),具体的实现这里不做介绍,感兴趣的可以查阅附件代码。代码可以直接通过chrome打开运行。
krc demo.rar
至于如何将上述html+css+js实现的卡拉OK歌词效果应用到桌面歌词中,实现原理可以参阅文章 一种结合CEF离屏渲染技术实现Windows桌面异形窗口应用的技术方案 。前端组目前正在实现PC混合应用容器框架,届时可以直接引入,并通过js实现窗口开关、移动、锁定等等功能。
性能问题
layered window方案中存在一个对应用性能影响较大的一个限制因素:每次需要更新窗口时,都要更新整个窗口,而不能更新局部。如果我们更新频率过高,会导致性能出现明显下降。正因为如此,我们没有采用按像素推进的方式,而采用了定时刷新模式。这样一来我们可以通过调节刷新频率满足动画平滑度(用户体验)和应用性能之间的平衡关系,经过测试我们发现30ms左右比较合适(这个值可以开放给用户根据自身需求进行配置)。
以上介绍的通过浏览器离屏渲染实现桌面歌词的方案适合于应用了混合框架的音乐软件,因为需要内置支持离屏渲染浏览器引擎,所以单独用于实现桌面歌词是不合适的。不过鉴于目前云终端应用开发模式,市场上大部分pc音乐软件都内置了浏览器引擎。应用这种方式,将极大的提升开发维护效率并降低相应的成本。

你可能感兴趣的:(JS,js,歌词,滚动,卡拉OK)