弹幕

首先是弹幕的位置,是要从最右滑到最左,为了防止随机高度弹幕会覆盖的问题,设置了通道。

每一个通道是从左到右的一条,高度固定,这样不同通道的弹幕不会相互覆盖。

弹幕滑动就是简单设置CSS属性  transition 实现。开始使用 left 改变弹幕的位置,后来改为 transform ,性能确实提高很多。

设置10条弹幕通道,每个通道有一个DOM池,每一次发射弹幕就从DOM池中拿出一个DOM从右滑到左边直到消失,然后再放回DOM池,当DOM池为空时就不能再通过该通道发射弹幕了,通过这种方式来限制最大同屏弹幕数。

因为通过 transition 设置了弹幕滑动的时间,而这个时间固定的,距离弹幕最左露头到最右消失,也就是“屏幕宽度+弹幕长度”,所以: 弹幕越长,速度越快 。这样的话,后面特别长的弹幕就有可能超过前面比较短的弹幕,本来根据弹幕长度设置了滑动时间,但是跑去看了下B站弹幕也有这个属性,所以就又改回去了>_<

最后设置一个弹幕池,设置一个定时器不停的去弹幕池拿弹幕,当DOM空闲且有未发射弹幕时就发射弹幕。

点击发送按钮就是把弹幕放到弹幕池就好了。

效果图:

   

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>原生JS实现弹幕效果</title>
<style>
#wrapper {
height: 400px;
width: 700px;
position: relative;
overflow: hidden;
background: url(./game_manwall_213255_m.jpg);    //自己换个背景图
color: #ffffff82;
font-size: 14px;
text-shadow: 1px 1px #000;
margin: 0 auto;
}
.right {
position: absolute;
visibility: hidden;
white-space: nowrap;
/*left: 700px;*/
transform: translateX(700px);
}
.left {
position: absolute;
white-space: nowrap;
user-select: none;
transition: transform 7s linear; /* 时间相同 越长的弹幕滑动距离越长 所以越快~ */
}
input {
position: absolute;
bottom: 10px;
left: 150px;
width: 300px;
height: 26px;
}
button {
position: absolute;
bottom: 8px;
left: 476px;
width: 100px;
height: 38px;
border-radius: 10px;
font-size: 16px;
}
</style>
</head>
<body>
<div id="wrapper">
<input type="text">
<button id="bth">发&nbsp;&nbsp;送</button>
</div>
<script>
/**
* 设置 弹幕DOM池 每一个通道最多六条弹幕
**/
const MAX_DM_COUNT = 6;
const CHANNEL_COUNT = 10;
let domPool = [];
let danmuPool = [
'2333333', '233333366666666666666', '2333333', '2333333', '2333333',
'2333333', '2333333', '233333366666', '2333333', '2333333', '2333333',
'66666666','888888888','ahahahaha','2333333', '233333366666666666666', '2333333', '2333333', '2333333',
'2333333', '2333333', '233333366666', '2333333', '2333333', '2333333',
'66666666','888888888','ahahahaha','2333333', '233333366666666666666', '2333333', '2333333', '2333333',
'2333333', '2333333', '233333366666', '2333333', '2333333', '2333333',
'66666666','888888888','ahahahaha'
 
];
let hasPosition = [];
/**
* 做一下初始化工作
*/
function init() {
let wrapper = document.getElementById('wrapper')
// 先new一些span 重复利用这些DOM
for (let j = 0; j < CHANNEL_COUNT; j++) {
let doms = [];
for (let i = 0; i < MAX_DM_COUNT; i++) {
// 要全部放进wrapper
let dom = document.createElement('span');
wrapper.appendChild(dom);
// 初始化dom的位置 通过设置className
dom.className = 'right';
// DOM的通道是固定的 所以设置好top就不需要再改变了
dom.style.top = j * 20 + 'px';
// 放入改通道的DOM池
doms.push(dom);
// 每次到transition结束的时候 就是弹幕划出屏幕了 将DOM位置重置 再放回DOM池
dom.addEventListener('transitionend', () => {
dom.className = 'right';
// dom.style.transition = null;
// dom.style.left = null;
dom.style.transform = null;
domPool[j].push(dom);
});
}
domPool.push(doms);
}
// hasPosition 标记每个通道目前是否有位置
for (let i = 0; i < CHANNEL_COUNT; i++) {
hasPosition[i] = true;
}
}
/**
* 获取一个可以发射弹幕的通道 没有则返回-1
*/
function getChannel() {
for (let i = 0; i < CHANNEL_COUNT; i++) {
if (hasPosition[i] && domPool[i].length) return i;
}
return -1;
}
/**
* 根据DOM和弹幕信息 发射弹幕
*/
function shootDanmu(dom, text, channel) {
console.log('biu~ [' + text + ']');
dom.innerText = text;
// 如果为每个弹幕设置 transition 可以保证每个弹幕的速度相同 这里没有保证速度相同
// dom.style.transition = `transform ${7 + dom.clientWidth / 100}s linear`;
// dom.style.left = '-' + dom.clientWidth + 'px';
// 设置弹幕的位置信息 性能优化 left -> transform
dom.style.transform = `translateX(${-dom.clientWidth}px)`;
dom.className = 'left';
 
hasPosition[channel] = false;
var bth=document.getElementById("bth");
bth.onclick=function(){
dom.innerHTML=text.value;
};
// 弹幕全部显示之后 才能开始下一条弹幕
// 大概 dom.clientWidth * 10 的时间 该条弹幕就从右边全部划出到可见区域 再加1秒保证弹幕之间距离
setTimeout(() => {
hasPosition[channel] = true;
}, dom.clientWidth * 10 + 1000);
}
window.onload = function() {
init();
// 为input和button添加事件监听
let btn = document.getElementsByTagName('button')[0];
let input = document.getElementsByTagName('input')[0];
btn.addEventListener('click', () => {
input.value = input.value.trim();
if (input.value) danmuPool.push(input.value);
})
input.addEventListener('keyup', (e) => {
if (e.key === 'Enter' && (input.value = input.value.trim())) {
danmuPool.push(input.value);
}
})
// 每隔1ms从弹幕池里获取弹幕(如果有的话)并发射
setInterval(() => {
let channel;
if (danmuPool.length && (channel = getChannel()) != -1) {
let dom = domPool[channel].shift();
let danmu = danmuPool.shift();
shootDanmu(dom, danmu, channel);
}
}, 1);
}
 
</script>
</body>
</html>

最新文章

  1. NOIP2016纪录[那些我所追求的]
  2. doT.js
  3. jQuery动画的实现
  4. 1.Java基础之System对象
  5. H5学习系列之Communication API
  6. xml序列化及反序列化.net对象
  7. Manacher算法 - 求最长回文串的利器
  8. 论文笔记之:Semi-Supervised Learning with Generative Adversarial Networks
  9. ThinkPHP中的动态缓存(S方法)和快速缓存(F方法)(转)
  10. 在oj平台上练习的一些总结【转】
  11. 谈谈Linux内存释放
  12. 如何将excel导入到数据库中并在gridview中显示
  13. angular directive知识
  14. UOJ#152. 【UR #10】汉诺塔
  15. android studio 在使用图片的时候会检测图片的合法性
  16. I/O多路复用之select、poll、epoll
  17. Kubernetes Ingress 学习
  18. Linux平台网络配置-----C语言
  19. springcloud报错Cannot execute request on any known server
  20. 在启动vsftpd,有时会报错

热门文章

  1. eclipse的下载安装配置
  2. ORACLE 的CONNECT BY、START WITH,CONNECT_BY_ROOT、CONNECT_BY_ISLEAF、SYS_CONNECT_BY_PATH,LEVEL的使用(Hierarchical query-层次查询)
  3. Keil5调试过程中遇到的一些警告和错误
  4. 关于AJAX的跨域问题
  5. 总结Idea环境,吐血踩过的坑
  6. Java学习|多线程学习笔记
  7. 1.Apache的配置
  8. (17)ASP.NET Core EF基于数据模型创建数据库
  9. 快速了解Python并发编程的工程实现(上)
  10. flask入门 七行代码讲解