基于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

若是这篇文章对你有帮助,不妨点个赞吧!

源码地址

相关文章
相关标签/搜索