这一节简单的模仿一下人气眼中的无重叠弹幕效果,也不卖关子了,下一节模仿头部的标签切换效果
一、简介
一谈到弹幕相信大家多不陌生,平时看直播,那弹幕可是看的很欢啊。
人气眼中的弹幕可能数量比较少的原因,是一种不重叠的弹幕。先看一下实现的效果:
当你决定看下文之前,你需要注意几点:
- 最好这时你已经有了一定的canvas基础;
- 如果你压根听过canvas,那真是太好了,哇!你应该要入坑了。
二、绘制圆角矩形
在canvas的API中并没有圆角矩形的绘制方法,所以我们得自己写一个绘制圆角矩形的方法,一说到圆角矩形,对于我们前端可以说是小菜一碟了,border-radius早就用烂了:
由上面这幅图,想必绘制圆角矩形的思路已经有了。这里我们可以通过canvas中的弧线和直线组合成圆角矩形(这里我就不过多的设置各个角的半径了):
// =====================
// canvas 圆角矩形的绘制
// =====================
// @param { Number } x 左上角的x坐标
// @param { Number } y 左上角的y坐标
// @param { Number } w 宽度
// @param { Number } h 高度
// @param { Number } r 圆角半径
// @param { String } bg 背景颜色
Barrage.prototype.drawRoundRect = function(options) {
let {x,y,w,h,r,bg = "rgb(246,185,206)"} = options;
const context = this.context;
context.beginPath();
context.fillStyle = bg;
//左上角
context.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
context.lineTo(x + w - r, y);
//右上角
context.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0);
context.lineTo(x + w, y + h - r);
//右下角
context.arc(x + w - r, y + h - r, r, 0, Math.PI / 2);
context.lineTo(x + r, y + h);
//左下角
context.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI);
context.lineTo(x, y + r);
context.fill();
}复制代码
这里需要注意的是beginPath方法,这通常是新手的一个坑:
- 在Canvas中只有一条路径存在,称之为当前路径;
- 在当前路径中我们可以添加很多子路径;
- 而beginPath方法就是清除当前路径中的所有子路径;
- 如果不适当使用beginPath方法,则会导致你会重复绘制很多路径。
这里你可以实验一下下面的例子:
context.strokeStyle = "#fff";
context.rect(10,10,100,100);
context.stroke();
context.beginPath(); //去掉这段代码 会导致重复绘制上面生成的四个子路径
context.strokeStyle = "#000";
context.rect(200, 200, 100, 100);
context.stroke();复制代码
三、获取文字宽度
很巧的是,在Canvas中提供计算文字宽度的方法,需要注意的是:
- 计算文字的宽度需要考虑到文字样式的影响。
// ===================
// 计算文字宽度
// ===================
Barrage.prototype.getTextWidth = function(font, text) {
const context = this.context;
context.font = font + "px Arial";
const textWidth = context.measureText(text).width;
return textWidth;
}复制代码
很遗憾的是,这里并没有获取高度的属性。。。。
四、绘制每一条标签
上面我们已经知道了绘制圆角矩形和测量文字的宽度了,这里我们只需要绘制文字即可,而你需要注意的是合理的利用textAlign和textBaseline将文字调整到合适的位置,这里我将文字居中于圆角矩形内部:
context.textBaseline = "middle";
context.textAlign = "center";
context.fillText(text, x + width / 2, y + h / 2);复制代码
五、无重叠弹幕
对于这一条,我们一步步来:
1、初始化数据的基本信息
初始化每个数据的字体、位置、速度等基本信息,接下来绘制多需要这些参数,这些数据存放在data数组中。
2、划分车道
无重叠弹幕和道路上行驶的车辆类似,我们需要划分车道,让它们有序的运作在各自的车道中:
const pathHeight = 10 + vp * 2 + size,
pathNumber = Math.floor(h / pathHeight);复制代码
3、弹幕的状态区分
弹幕我们可以区分为待显示的弹幕、即将显示的弹幕、未完全显示的弹幕、完全显示的弹幕和显示完成的弹幕。这里我们还需要声明activeArray保存未完全显示的弹幕和完全实现的弹幕,nextBarrage保存即将显示的弹幕。
4、待显示弹幕
我们的弹幕是从左边发射的,所以当一个弹幕完全显示出来,这个“车道”应该就要添加新的弹幕了:
// 部分代码
if (item.x + item.w < width && item.status === 0) {
next.push(item.y);
item.status = 1;
break;
}复制代码
5、添加待显示弹幕
动画执行的每一帧我们需要将待显示弹幕加入到activeArray数组当中:
if (next.length > 0 && data.length > 0) {
const temp = data.shift();
temp.y = next.shift();
temp.x = width + 200;
activeArray.push(temp);
}复制代码
这里的200并不是一个随意的数,因为我们在设置速度时,是设置1~1.25的随机数,所以你建立一个方程也就解出两个弹幕之间的最小安全距离了。
6、动画的暂停和恢复
暂停和恢复的根本就是要保存暂停的绘制状态,这里可以通过Canvas的getImageData和putImageData实现:
// ==============
// 动画暂停与恢复
// ==============
Barrage.prototype.pause = function () {
const context = this.context;
if (!this.animationLock) {
this.drawSurface = context.getImageData(0,0,this.width, this.height);
this.animationLock = true;
} else {
context.putImageData(this.drawSurface, 0, 0);
this.animationLock = false;
this.animate();
}
}复制代码
到这里,简单的弹幕功能就完成了。
参考书籍: 《HTML5 Canvas核心技术》
源代码