之前在公司有一个项目,需要使用uni-app实现一个弹幕的效果,展示已付款用户。
当时项目赶时间,使用了一个插件,后来琢磨了一下实现思路:
一个弹幕功能,首先需要拿到弹幕内容,这个不用多说。
除此以外,还要有画布的总宽度,让数据一开始处于画布区域之外。
然后让数据以不同的速度,向canvas区域内容滑动,知道完全穿过
这个canvas区域。
当然数据不会只有一条,所以还要设置y轴的间距,如图所示:
接口中返回的数据,一般只包含弹幕内容,所以速度和x轴,y轴这些属性,只能靠前端自己对数据做进一步处理。
例如接口提供的信息如下:
data(){
return {
bullet:[
{bullet:"弹幕内容1"},
{bullet:"弹幕内容2"},
{bullet:"弹幕内容3"},
...
]
}
}
而我们需要的数据格式却应该是
data(){
return {
bullet:[
{bullet:"弹幕内容1",speed:Math.random()*1+1,x:画布宽度,y:随机高度},
{bullet:"弹幕内容2",speed:Math.random()*1+1,x:画布宽度,y:随机高度},
{bullet:"弹幕内容3",speed:Math.random()*1+1,x:画布宽度,y:随机高度},
...
]
}
}
这里我使用了Object.assign去合并数据
this.bullet = this.bullet.map(item=>{
return Object.assign(item,{
speed:Math.random()*1+1,
x: 750,
y: Math.random()*100+20
}
)
})
创建canvas绘制箱
const ctx = uni.createCanvasContext("bulletCanvas")
绘制前清空整个画布
ctx.clearRect(0,0,750,300)
因为有多个bullet内容
,所以首先考虑的是循环绘制,将每个循环到的内容配置进去。
考虑到移动端性能参差不齐,所以配合requestAnimationFrame
来实现动画。
this.bullet.forEach(item=>{
// 绘制文字内容及位置
ctx.fillText(item.bullet,item.x,item.y)
// 每次达到渲染频率,就让x轴向左移动一段随机的宽度,即速度
item.x -= item.speed
// 调用measureText()来获取TextMertics对象
let textMertics = ctx.measureText(item.bullet)
// 根据TextMertics对象获取文字宽度
let textWidth = textMertics.width
// 当弹幕内容滚动到画布之外的时候,让其再次回到右边
if(item.x<-textWidth){
item.x = 750
}
})
<template>
<view class="cart">
<view class="bullet">
<canvas style="width:750rpx;height:300rpx" canvas-id="bulletCanvas"></canvas>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bullet:[
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"},
{bullet:"内容1"}
]
}
},
onLoad() {
},
onReady() {
this.order()
.then(()=>{
this.draw()
})
},
methods: {
// 整理数据
order(){
return new Promise((resolve,reject)=>{
resolve(
this.bullet = this.bullet.map(item=>{
return Object.assign(item,{
speed:Math.random()*1+1,
x: 750,
y: Math.random()*100+20
})
})
)
})
},
draw(){
const ctx = uni.createCanvasContext("bulletCanvas")
function anim(){
ctx.setFontSize(20)
// 擦除整个画布
ctx.clearRect(0,0,750,300)
// 循环绘制
this.bullet.forEach(item=>{
ctx.fillText(item.bullet,item.x,item.y)
item.x -= item.speed
// 调用measureText()来获取TextMertics对象
let textMertics = ctx.measureText(item.bullet)
// 根据TextMertics对象获取文字宽度
let textWidth = textMertics.width
// 当弹幕滚动到画布之外的时候,再次回到右边
if(item.x<-textWidth){
item.x = 750
}
})
ctx.draw()
requestAnimationFrame(anim.bind(this))
}
anim.call(this)
}
}
}
</script>
<style>
</style>