使用canvas實(shí)現(xiàn)一個(gè)vue彈幕組件功能
看B站時(shí),對(duì)彈幕的實(shí)現(xiàn)產(chǎn)生了興趣,一開(kāi)始想到用css3動(dòng)畫(huà)去實(shí)現(xiàn),后來(lái)感覺(jué)這樣性能不是很好,查了下資料,發(fā)現(xiàn)可以用canvas實(shí)現(xiàn),于是就摸索著寫(xiě)了一個(gè)簡(jiǎn)單的彈幕。
彈幕功能
- 支持動(dòng)態(tài)添加彈幕
- 彈幕不重疊
- 自定義彈幕顏色
效果圖

前端框架選了比較熟悉的vuejs
彈幕滾動(dòng)的基本思路就是通過(guò)定時(shí)器不斷地改變彈幕的位置,時(shí)時(shí)重繪畫(huà)布。
實(shí)現(xiàn)步驟
先加入一個(gè)canvas標(biāo)簽,這里有個(gè)注意點(diǎn),關(guān)于設(shè)備像素比對(duì)canvas的影響,會(huì)出現(xiàn)繪圖模糊。
<canvas width="600" height="600"></canvas> // 如果單純這樣寫(xiě),canvas會(huì)出現(xiàn)模糊 <canvas width="600" height="600" style="width: 300px;height: 300px"> </canvas> //為了不出現(xiàn)模糊,需要設(shè)置canvas的css寬高為上下文寬高的1/devicePixelRatio, 本文是對(duì)于devicePixelRatio:2的設(shè)備設(shè)置的,該值可從window.devicePixelRatio取得。 <canvas ref="hiddenCanvas" width="0" height="0" style="display: none"> </canvas> // 后面會(huì)用到
我們先定義一個(gè)數(shù)組來(lái)存放彈幕數(shù)據(jù),一條彈幕信息,包括文本內(nèi)容,x,y坐標(biāo)位置,顏色,速度(可以是隨機(jī)或者固定,為了計(jì)算簡(jiǎn)單,我們這里采用了固定的速度)
var dmArr = [];
var gap = 80; // 彈幕的上下間距
var hiddenCanvas = this.$refs.hiddenCanvas;
// 增加彈幕的方法
function pushDm(text, color) {
let y = getY(); // 先確定跑道
let x = 600; // 初始x坐標(biāo)為canvas的右邊界
let delayWidth = 0; // 同跑道
for (let i = 0, len = dmArr.length; i < len; i++) {
let dm = dmArr[i];
if (y === dm.y) { // 如果是同跑道,則往后排,設(shè)置一定的間隔,保證彈幕不會(huì)重疊;
delayWidth += Math.floor(hiddenCanvas.getContext('2d').measureText(dm.text).width * 4 + 50);
} }
dmArr.push({
text: text,
x: x + delayWidth,
y: y,
speed: 8,
color: color || getColor()
});
}
// 隨機(jī)獲得y坐標(biāo)
function getY() {
let range = Math.floor(600 / gap); // 跑道數(shù)量
return Math.floor(Math.random() * range + 1) * gap;
}
// 隨機(jī)獲得顏色
function getColor() {
return `${Math.floor(Math.random() * 16777215).toString(16)}`;
}
// 寫(xiě)一個(gè)for循環(huán),初始化30條彈幕
for (let i = 0; i < 30; i++) {
pushDm(`It's barrage ${i}`);
}
接下來(lái)設(shè)置一個(gè)20ms的定時(shí)器,實(shí)現(xiàn)彈幕滾動(dòng)效果
var timer = null;
var ctx = this.$refs.canvas.getContext('2d');
function start(){
timer = setInterval(() => {
ctx.clearRect(0, 0, 600, 600); // 每次需要清空畫(huà)布
ctx.save();
ctx.font = '30px Microsoft YaHei'; // 這里需要把字體大小設(shè)為需要顯示的css大小的2倍(devicePixelRatio為2時(shí))
if (!dmArr.length) stop(); // 如果沒(méi)有新彈幕了,就停止計(jì)時(shí)器
for (let i = 0, len = this.dmArr.length; i < len; i++) {
let dm = dmArr[i];
let overRange = -ctx.measureText(dm.text).width * 2;
dm.x -= dm.speed;
if (dm.x < overRange) {
dmArr.splice(i, 1); // 彈幕在畫(huà)布中不可見(jiàn)時(shí),從數(shù)組中移除該項(xiàng)
continue;
}
ctx.fillStyle = `#${dm.color}`;
ctx.fillText(dm.text, dm.x, dm.y);
}
ctx.restore();
}, 20);
}
function stop() {
clearInterval(timer);
ctx.clearRect(0, 0, 600, 600);
}
我們還需要一個(gè)輸入框,來(lái)實(shí)現(xiàn)手動(dòng)添加彈幕功能
<input type="text" @keyup.enter="sent" v-model="dmInput" maxlength="20">
<button type="button" @click="sent">發(fā)表</button>
var dmInput = '';
var color = ''; // 可自定義彈幕的顏色
function sent() {
if (!dmInput) return;
stop();
pushDm(dmInput, color);
start();
dmInput = '';
}
有待改進(jìn)的地方和疑問(wèn)?速度不恒定時(shí),怎么保持彈幕不重疊視頻彈幕是根據(jù)彈幕發(fā)送時(shí)間點(diǎn)來(lái)定位到視頻的每一幀?如何實(shí)現(xiàn)?
總結(jié)
以上所述是小編給大家介紹的使用canvas實(shí)現(xiàn)一個(gè)vue彈幕組件功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Vue中textarea自適應(yīng)高度方案的實(shí)現(xiàn)
本文主要介紹了Vue中textarea自適應(yīng)高度方案的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
vue3中的watch和watchEffect實(shí)例詳解
watch和watchEffect都是監(jiān)聽(tīng)器,但在寫(xiě)法和使用上有所區(qū)別,下面這篇文章主要給大家介紹了關(guān)于vue3中watch和watchEffect的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
在項(xiàng)目中封裝axios的實(shí)戰(zhàn)過(guò)程
這篇文章主要給大家介紹了關(guān)于如何在項(xiàng)目中封裝axios的相關(guān)資料,axios 請(qǐng)求的封裝,無(wú)非是為了方便代碼管理,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
iview+vue實(shí)現(xiàn)導(dǎo)入EXCEL預(yù)覽功能
這篇文章主要為大家詳細(xì)介紹了iview+vue實(shí)現(xiàn)導(dǎo)入EXCEL預(yù)覽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
vue3移動(dòng)端嵌入pdf的多種方法小結(jié)
這篇文章主要介紹了vue3移動(dòng)端嵌入pdf的多種方法小結(jié),使用embed嵌入有好處也有缺點(diǎn),本文給大家講解的非常詳細(xì),需要的朋友可以參考下2023-10-10

