這一節簡單的模仿一下人氣眼中的無重疊彈幕效果,也不賣關子了,下一節模仿頭部的標籤切換效果
一、簡介
一談到彈幕相信大家多不陌生,平時看直播,那彈幕可是看的很歡啊。
人氣眼中的彈幕可能數量比較少的原因,是一種不重疊的彈幕。先看一下實現的效果:
當你決定看下文之前,你需要注意幾點:
- 最好這時你已經有了一定的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核心技術》
原始碼