更新:支持头像和背景色设置css
更新:支持实时弹幕插入,支持弹幕的流式布局html
最近因为项目须要定制化一个弹幕功能,因此尝试使用canvas来开发组件。通过测试在一些低端机的效果也没有明显的卡顿,和你们交流一下vue
npm i vue-barrage
git
name | type | default | desc |
---|---|---|---|
barrageList | Array | [] | 弹幕数据 |
speed | Number | 4 | 弹幕滚动速度 |
loop | Boolean | true | 是否循环滚动 |
channels | Number | 2 | 弹幕轨道数 |
borderColor | String | '#000' | 弹幕边框 |
background | String | '#FFF' | 弹幕背景色 |
<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>
复制代码
watch: {
barrageList (val) {
if (val.length !== 0) {
this.barrageQueue = val // 将数据保存在更新队列
this.initData() // 数据初始化
window.requestAnimationFrame(this.render) // 开始渲染
}
}
}
复制代码
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
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
/**
* 重置数据
*/
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)
},
复制代码
新增弹幕时将在未出现的弹幕数组首部添加一条数据数组
<style lang="scss" scoped>
.barrage-container { // 点击事件穿透
pointer-events: none;
}
.container {
width: 100%;
overflow: hidden;
}
</style>
复制代码
/**
* 获取随机颜色
*/
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
若是这篇文章对你有帮助,不妨点个赞吧!