1:DOM实现
实现思路:
1,在一定的可视区划分出几条水平轨道,然后在轨道中添加弹幕,这样基本可以防止弹幕的重叠
2,建立所需要的dom节点并添加到dom池里面,dom节点总数=轨道数X每条轨道可放置的最大弹幕数,并把建好的dom节点加入到可视区容器里面通过添加class使其定位到可视区外部,并绑定相应的事件进行逻辑处理(注意作用域问题)
3,建立轨道是否可放置弹幕判断函数,此位置为true则表示此条轨道可进行弹幕的展示,否则不行,当弹幕运动一定的时间后可以把此条轨道的状态置为true
4,建立轨道选择函数,通过依次遍历轨道状态来选择一条轨道进行弹幕的展示,若不存在这样的轨道则返回-1
5,建立弹幕发送函数,从弹幕池依次中取出弹幕,从dom池中依次取出dom,组装成dom节点,然后给此dom增删相应的css属性实现dom的运动(此实现使用的是transtion+transform来实现dom运动,可大大提高性能)
6,添加一个定时器,定时触发弹幕发送函数
7,关闭弹幕,停止定时器,同时把所有dom节点移到不可见区域,添加弹幕,把弹幕信息push到弹幕池
核心代码:
html:
关闭弹幕
发送
js(使用了jq):
src="https://res.variflight.com/carnoc/zt/index_zt/fast/jquery/jquery-1.8.3.min.js">
var danmoChi = __data__;
var domChi = [];
//判断该通道是否可以放置弹幕
var hasPos = [];
var MAX_Channel = 8;
var MAX_Number = 6;
timer = null;
//最大通道数和每条通道的弹幕数,可视区最大需要的dom数量为8x4=32
function init() {
for (let i = 0; i < MAX_Channel; i++) {
var doms = [];
for (let j = 0; j < MAX_Number; j++) {
let dom = document.createElement('p');
$('.displayArea')[0].appendChild(dom);
dom.className = 'right';
dom.style.top = i * 60 + 'px';
doms.push(dom);
//该条弹幕已经移出了可视区
dom.addEventListener('transitionend', () => {
dom.className = 'right';
dom.style.transform = null;
//新发的弹幕直接加入到dom池最前的位置
domChi[i].push(dom);
})
}
domChi.push(doms);
//初始时每个通道都可放置弹幕
hasPos[i] = true
}
}
//选择一个可以发送弹幕的通道
function chioceChnnel() {
for (let i = 0; i < MAX_Channel; i++) {
if (hasPos[i] && domChi[i].length > 0) {
return i
}
}
return -1
}
//发送弹幕
function shotDanmo(dom, text, channel) {
dom.innerText = text;
dom.className = 'left';
hasPos[channel] = false;
var cw = dom.clientWidth;
dom.style.transform = `translateX(${-dom.clientWidth}px)`;
//等待这条弹幕移动一段距离后释放改通道
setTimeout(function () {
hasPos[channel] = true
}, cw * 10 + 1000);
}
function open() {
timer = setInterval(function () {
let chnnel = chioceChnnel();
if (danmoChi.length > 0 && chnnel != -1) {
let dom = domChi[chnnel].shift();
let danmo = danmoChi.shift();
shotDanmo(dom, danmo.content, chnnel)
}
}, 10)
}
function close() {
clearInterval(timer);
$('.displayArea p').addClass('right')
$('.displayArea p').css({'transform': null})
}
window.onload = function () {
init();
open();
};
function controlDanmo(e) {
let event = e || window.event;
if (event.target.checked) {
close()
}
else {
open()
}
}
function tianjiaDanmo() {
var val = $('.sendDanmo input').val();
if (val.trim()) {
var obj = {};
obj.img = "";
obj.content = val;
danmoChi.push(obj);
$('.sendDanmo input').val("");
console.log('发送弹幕成功!')
}
}
function sendDanmo(e) {
let event = e || window.event;
if (event.code == 'Enter') {
tianjiaDanmo();
}
}
注:弹幕.js里面是用户发送的弹幕,这里我随便编的几个弹幕来进行模拟的结构是
var _data_=[
{
img:'',//发送弹幕用户的头像
content:""//弹幕内容
}
................
]
效果:
总结:
设计优点;利用css3动画通过修改class自动实现了运动,实现起来较为简单且兼容性较强,性能也还不错,但是在dom绑定transitionend事件时要注意变量作用域,这里我使用let不会有什么问题,使用var时要注意闭包问题、
缺点:不断通过定时器来进行弹幕剩余检测,会消耗部分性能
2:canvas实现
实现思路基本和上面一致,
1,创建canvas,fillText构造函数用来创建弹幕对象
2,轨道划分判断原则,当此条轨道发出弹幕的瞬间状态置为“忙碌”,当运动一段时间后置空闲,再在剩余空闲的轨道中随机选择一条这样就基本避免了弹幕重叠的问题。
3,弹幕运动,通过定时器修改弹幕的坐标,并且重绘所有弹幕
4,发送弹幕,通过定时器定时检测弹幕池如果有弹幕则发射弹幕
核心代码:
html:
发送
JS:
script src="弹幕数据.js" charset="UTF-8">
var cav = document.getElementById('cav');
var context = cav.getContext('2d');
var W = document.getElementById('cav').clientWidth;
var H = document.getElementById('cav').clientHeight;
var timer = null;
var tms = null;
//发射弹幕速度
const popSpeed=100;
//弹幕移动速度
const moveSpeed=10;
const MAX_Channel = 9;
//单条弹幕的高度
const tpH=60;
context.font = "30px Arial";
context.fillStyle = "#00ff00";
var danmoChi = __data__;
//当前画布里面存在的弹幕对象
var viewDanmo = [];
//通道占用状态数组
var hasPos = [];
//用来进行随机选择通道的数组
var possibleArr=[];
for (var i = 1; i < MAX_Channel; i++) {
hasPos[i] = true;
}
//用来生成弹幕对象
function createDanmo(text, channel) {
this.x = 800;
//绘制弹幕
this.draw = function () {
context.beginPath();
context.fillText(text, this.x, tpH * channel);
context.closePath()
};
this.move = function () {
this.x -= 2;
this.draw();
}
}
//检测轨道情况,并在空闲轨道中随机选取一条,没有则返回-1
function getChannel() {
possibleArr=[];
for (var i = 0; i < MAX_Channel; i++) {
if (hasPos[i]) {
possibleArr.push(i)
}
}
if(possibleArr.length>0){
var len=possibleArr.length;
if(len==1){
return possibleArr[len-1]
}
else {
var t=Math.floor(Math.random()*len);
return possibleArr[t]
}
}
else {
return -1
}
}
//取出弹幕并发送
function start() {
timer = setInterval(function () {
if (danmoChi.length > 0 && getChannel() != -1) {
var dammo = danmoChi.shift();
var channel = getChannel();
hasPos[channel] = false;
var obj = new createDanmo(dammo.content, channel);
obj.draw();
obj.channel = channel;
viewDanmo.push(obj);
setTimeout(function () {
hasPos[channel] = true;
}, dammo.content.length * 60 + 1000);
dammoMove(obj, channel);
}
}, popSpeed);
}
start();
//使用定时器实现弹幕每次的运动重绘
function dammoMove() {
if (tms) {
clearInterval(tms);
}
tms = setInterval(function () {
if(viewDanmo.length<1){
clearInterval(tms);
tms=null;
return
}
context.clearRect(0, 0, W, H);
for (var i = 0; i < viewDanmo.length; i++) {
viewDanmo[i].move();
//弹幕以及完全移出可视区,删除此弹幕对象
if(viewDanmo[i].x<-700){
viewDanmo.splice(i,1)
}
}
}, moveSpeed)
}
//添加弹幕,将信息存入弹幕池
function tianjiaDanmo() {
var val = document.getElementById('ctx').value;
if (val.trim()) {
var obj = {};
obj.img = "";
obj.content = val;
danmoChi.push(obj);
document.getElementById('ctx').value="";
console.log('发送弹幕成功!');
// start();
}
}
function sendDanmo(e) {
let event = e || window.event;
if (event.code == 'Enter') {
tianjiaDanmo();
}
}
效果:
分析:当弹幕数量较多时,有一定的优势
缺点,原生的定时器实现运动可能会很僵硬,可以借助js动画库