想要实现比较理想的弹幕滚动效果还是有一定难度的。这里以vue为基础,讲一种实现弹幕滚动的方法,不考虑重叠,弹幕过长或其他问题。
一个弹幕即为一个组件,所以要实现设计此子组件,即弹幕组件,这里属性要包含该弹幕的位置(随机、定端、底端),大小,颜色,内容等等,由父组件循环渲染并赋值。
生成一个弹幕并不难,难就难在如何让其移动,第一个想到的点就是设其position为relative或是absolute,通过修改left或right使其移动。那么选择relative还是absolute呢?他们的效果是否一样呢?
1.弹幕的position为relative还是absolute
这里我推荐使用absolute,而且效果是不一样的,如果使用relative,将会导致新div(弹幕)每次生成时的位置会受前面的div或组件影响到,这时修改位置就会不准确,即使使其float,也会受前面弹幕堆叠的影响而不能准确使其生成在固定的某一位置。
如果使用absolute,就可以保证每次生成的横向位置是固定的,不会收到前面的div/组件的影响,进而准确的调整其left/right(但是重叠无法避免)。
接下来是第二个问题:
2.弹幕的left/right通过js修改与通过css的transition过渡比较
要实现弹幕匀速平稳的移动,首先当然可以使用js来操作,通过设置定时器,不停地一点一点地修改弹幕地位置来实现肉眼上弹幕地匀速滚动,但我想说这样太麻烦了。
你需要考虑不同div大小导致地弹幕移动速度地变化,这时你需要通过计算来获取每次弹幕移动地增量(即速度),你还要调试定时器地时间,你还要遍历每一条弹幕一次进行修改,十分繁琐。
但是,如果你使用transition地过度来实现,就十分地简单:
transition-property: left;
transition-duration: 6s;
transition-timing-function: linear;
这里对随机滚动地弹幕做过度处理,注意这里特指随机,因为固定定端和固定底端是瞬间出现而且没有变化,不应该有次样式,所以因该通过class绑定来解决。
当你使用了transition之后你只用关心几个数值即可:
- 弹幕的初始位置(left & top)
- 弹幕的结束位置(left & top)
- 弹幕的过度属性 transition-property(一般以x轴做匀速运动,就只有left,当然b站有斜的弹幕)
- 弹幕的运动时间 transition-duration (当使用transition时就不考虑速度了,只用考虑运动时间即可)
- 弹幕运动的方式 transition-timing-function(一般为linear,即线性)
这里最强大的样式即为transition-duration,它使你不用再去关注弹幕具体的移动速度,在不同宽度的div下重新计算弹幕的速度,以免弹幕消失过快或者过早。而transition-duration会智能的调节移动的速度,保证这个弹幕一定会在此div移动固定的时间。当div长时,移动加快,当短时,移动变慢,但时间是一致的,而且你也不用再去写新的js方法,十分的方便。
这里,我将我写的弹幕的核心移动算法列出来:
created() {
// console.log(this.position)
var _self = this;
var danWidth;
var danHeight;
//弹幕生成速度需时间,以免ref获取为undefined
window.setTimeout(function() {
danWidth = _self.$refs.dan.offsetWidth //弹幕的宽
danHeight = _self.$refs.dan.offsetHeight //弹幕的高
if (_self.position == 0) { //0指弹幕位置随机
_self.top = Math.ceil(Math.random() * (_self.top - 60 - danHeight)); //纵向随机高度
_self.left = - danWidth //目标移动位置
} else if (_self.position == 1) { //1为固定顶部居中
_self.top = 5
_self.left = (_self.width-danWidth)/2 //居中
} else { //2为固定底部居中
_self.top = _self.top - 60 - danHeight
_self.left = (_self.width-danWidth)/2 //居中
}
}, 100);
//6秒移动,7秒后删除
window.setTimeout(function() {
_self.$emit('deletedan', _self.id) //调用父级方法去除该弹幕
}, 7000);
}
其中_self.top即为弹幕上偏移量,_self.left为左偏移量,_self.width为父div(弹幕框)的宽,_self.height为父div的高。
首先要熟悉style绑定,这里首先把弹幕的left调整为父div(弹幕框)的宽度,正好右侧溢出。此时延迟了100ms后将其移动到left=负的弹幕的宽度,正好到左侧溢出,这里之所以做延迟处理首先是保证先后顺序要正确,其次弹幕(该组件)生成需要时间,要获取弹幕宽高不能立马通过ref属性获取,否则为undefined。
通过position(0,1,2)进行相应移动处理后,再调用延迟进行删除处理,这个就比较简单了,直接在父级写一个方法,将弹幕id传过去,删除即可:
deleteDanmu(ids) {
this.danmus = this.danmus.filter(danmu => danmu.id != ids)
},
而且并不会影响其他弹幕,但需要注意的是该删除时间延迟最小为6100.(本例)
最后需要注意的是要给该组件的根div加上
white-space:nowrap;
这个可以避免弹幕内容(数字、英文、中文等)在溢出时被折叠,而导致出现的异常问题。
以上说的即是使用transition实现弹幕滚动效果时的核心问题,当这些实现后,剩下弹幕的美化就都是比较一般的问题了,当然本文并没有指出弹幕重叠的解决方法。
网友评论