基於Canvas+Vue的彈幕組件

更新:支持頭像和背景色設置css

更新:支持實時彈幕插入,支持彈幕的流式佈局html

最近因爲項目須要定製化一個彈幕功能,因此嘗試使用canvas來開發組件。通過測試在一些低端機的效果也沒有明顯的卡頓,和你們交流一下vue

彈幕效果

功能介紹

  • 支持循環彈幕
  • 彈幕不重疊
  • 支持選擇軌道數
  • 支持彈幕發送

使用

npm i vue-barragegit

參數配置

name type default desc
barrageList Array [] 彈幕數據
speed Number 4 彈幕滾動速度
loop Boolean true 是否循環滾動
channels Number 2 彈幕軌道數
borderColor String '#000' 彈幕邊框
background String '#FFF' 彈幕背景色

功能實現

html樣式

<template>
    <div class="barrage-container">
        <canvas
            ref="canvasContainer"
            :width="barrageWidth"
            :height="barrageHeight"
            style="display: none;"></canvas>
        <div
            class="container"
            :style="{height: barrageHeight/2+'px'}"
        >
            <canvas
                id="canvas"
                ref="canvas"
                :width="barrageWidth"
                :height="barrageHeight"
                :style="{'width': barrageWidth/2 + 'px',
                 'height': barrageHeight/2 + 'px'}"
            />
        </div>
    </div>
</template>
複製代碼

js實現

  1. 監聽數據源
watch: {
    barrageList (val) {
        if (val.length !== 0) {
            this.barrageQueue = val // 將數據保存在更新隊列
            this.initData() // 數據初始化
            window.requestAnimationFrame(this.render) // 開始渲染
        }
    }
}
複製代碼
  1. 數據初始化

barrageQueue是須要初始化的數組(包括原始數據或者原始數據加新增數據)
waitArray是原始數據加增長的彈幕數據
barrageArray存儲未出現的彈幕數據,改數組數據不斷減小github

/**
 * 數據初始化
 */
initData () {
    for (let i = 0; i < this.barrageQueue.length; i++) { // 此到處理只顯示55個字符
        let content = this.barrageQueue[i].content.length > 55 ? `${this.barrageQueue[i].content.substring(0, 55)}...` : this.barrageQueue[i].content
        this.barrageArray.push({
            content: content, // 初始化內容
            x: this.barrageWidth, // 初始彈幕出現位置
            width: this.ctx1.measureText(content).width * 3, // 文字寬度
            color: this.barrageQueue[i].color || this.getColor()
        })
    }
    this.initChannel()
},
/**
 * 初始化軌道數據
 * 爲每一條軌道初始化一條數據
 */
initChannel () { 
    for (let i = 0; i < this.channels; i++) {
        let item = this.barrageArray.shift()
        this.waitArray.push(item)
        if (item) {
            this.channelsArray[i] = [item]
        } else {
            this.channelsArray[i] = []
        }
    }
}
複製代碼

初始化數據須要處理的就是計算當前彈幕的軌道、位置、寬度,以便在canvas繪製的時候使用npm

  1. 繪製canvas
/**
 * 渲染
 */
render () {
    this.ctx.clearRect(0, 0, this.barrageWidth, this.barrageHeight)
    this.ctx.font = '30px Microsoft YaHei'
    this.draw()
    window.requestAnimationFrame(this.render) // 每隔16.6毫秒渲染一次,若是使用setInterval的話在低端機型會有點卡頓
}
/**
 * 開始繪製 文字和背景
 */
draw () {
    for (let i = 0; i < this.channelsArray.length; i++) {
        for (let j = 0; j < this.channelsArray[i].length; j++) {
            try {
                let barrage = this.channelsArray[i][j]
                barrage.x -= this.speed
                if (barrage.x <= this.barrageWidth) {
                    this.drawRoundRect(this.ctx, barrage.x - 15, i * 46 + 8, barrage.width + 30, 40, 20, `rgba(0,0,0,0.75)`)
                    this.ctx.fillStyle = `${barrage.color}`
                    this.ctx.fillText(barrage.content, barrage.x, i * 46 + 39)
                }
                if (barrage.x < -(barrage.width + this.barrageWidth)) { // 彈幕超過必定距離就刪除
                    let item = this.channelsArray[i].shift()
                    item.x = this.barrageWidth
                    if (this.loop) { // 彈幕循環處理
                        let arr = this.channelsArray.reduce((a, b) => a.concat(b))
                        if (arr.length === 0) {
                            this.barrageQueue = []
                            this.barrageQueue = this.waitArray
                            this.waitArray = []
                            this.initData()
                        }
                    }
                }
                // 插入彈幕的時機
                if (barrage.x <= (this.barrageWidth - barrage.width - 50) && barrage.x >= (this.barrageWidth - barrage.width - 50 - this.speed) && (j === this.channelsArray[i].length - 1) && this.barrageArray.length !== 0) {
                    let item = this.barrageArray.shift()
                    this.channelsArray[i].push(item)
                    this.waitArray.push(item)
                }
            } catch (e) {
                console.log(e)
            }
        }
    }
}
複製代碼

此處判斷繪製邏輯,包括何時取消,彈幕開始繪製判斷,彈幕消失判斷canvas

  1. 新增彈幕
/**
 * 重置數據
 */
add (obj) {
    let item = {
        content: obj.content,
        x: this.barrageWidth,
        width: this.ctx1.measureText(obj.content).width * 3,
        color: obj.color || this.getColor()
    }
    this.barrageArray.unshift(item)
},
複製代碼

新增彈幕時將在未出現的彈幕數組首部添加一條數據數組

  1. CSS
<style lang="scss" scoped>
    .barrage-container { // 點擊事件穿透
        pointer-events: none;
    }
    .container {
        width: 100%;
        overflow: hidden;
    }
</style>
複製代碼
  1. 其餘函數
/**
 * 獲取隨機顏色
 */
getColor () {
    return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6);
},
/**
 * 繪畫圓角矩形
 * @param context
 * @param x
 * @param y
 * @param width
 * @param height
 * @param radius
 * @param color
 */
drawRoundRect (context, x, y, width, height, radius, color) {
    context.beginPath()
    context.fillStyle = color
    context.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2)
    context.lineTo(width - radius + x, y)
    context.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2)
    context.lineTo(width + x, height + y - radius)
    context.arc(width - radius + x, height - radius + y, radius, 0, Math.PI / 2)
    context.lineTo(radius + x, height + y)
    context.arc(radius + x, height - radius + y, radius, Math.PI / 2, Math.PI)
    context.fill()
    context.closePath()
}
複製代碼

此處爲彈幕服務函數bash

使用

<barrage
    ref="barrage"
    class="barrage"
    :barrage-list="barrageList"
    :speed="speed"
    :loop="loop"
    :channels="channels"/>
    
import Barrage from 'vue-barrage'

// 彈幕數據格式
this.barrageList = [{
    content: '試數據測試數測試數據數測試數據',
    color: 'white'
}]

// 新增彈幕方式調用
this.$refs.barrage.add({
    content: '增長一條新的彈幕增長一條新的彈幕', color: 'white'
})
複製代碼

結語

這一次改版作了插入彈幕當即顯示,原來的彈幕插入只會出如今列表的最後。彈幕的插入軌道也是上一條彈幕出現必定距離後自動插入到後面,符合流式佈局的格式。dom

若是這篇文章對你有幫助,不妨點個贊吧!

源碼地址

相關文章
相關標籤/搜索