纯属无聊写的,可能有很多问题,欢迎批评指教。

效果图:图一是预设的一些弹幕,图二是自己发射的弹幕,效果是一样的。demo地址

 

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

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

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

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

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

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

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

其实我写的挺简单的,又说了好多废话,代码里我都加了注释。这我是第一次写JS全部加了分号哦!表扬下自己。

完整代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>原生JS实现弹幕效果</title>
<style>
#wrapper {
height: 400px;
width: 700px;
position: relative;
overflow: hidden;
background: url(http://www.drama-asia.se/wp-content/uploads/2016/06/14375197_1349947520504_800x600.jpg); color: #ffffff82;
font-size: 14px;
text-shadow: 1px 1px #000;
}
.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>发&nbsp;&nbsp;送</button>
</div>
<script>
/**
* 设置 弹幕DOM池 每一个通道最多六条弹幕
**/ const MAX_DM_COUNT = 6;
const CHANNEL_COUNT = 10; let domPool = [];
let danmuPool = [
'前方大量弹幕来袭,请做好准备!', '2333333', '2333333', '2333333', '2333333', '2333333',
'浔阳江头夜送客, 枫叶荻花秋瑟瑟', '2333333', '2333333', '2333333', '2333333', '2333333', '2333333',
'主人下马客在船, 举酒欲饮无管弦。', '醉不成欢惨将别, 别时茫茫江浸月', '忽闻水上琵琶声, 主人忘归客不发。',
'寻声暗问弹者谁? 琵琶声停欲语迟。', '移船相近邀相见, 添酒回灯重开宴。', '千呼万唤始出来, 犹抱琵琶半遮面。',
'转轴拨弦三两声, 未成曲调先有情。', '弦弦掩抑声声思, 似诉平生不得志。', '低眉信手续续弹, 说尽心中无限事。',
'轻拢慢捻抹复挑, 初为霓裳后六幺。', '大弦嘈嘈如急雨, 小弦切切如私语。', '嘈嘈切切错杂弹, 大珠小珠落玉盘。',
'间关莺语花底滑, 幽咽泉流冰下难。', '冰泉冷涩弦凝绝, 凝绝不通声暂歇。', '别有幽愁暗恨生, 此时无声胜有声。',
'银瓶乍破水浆迸, 铁骑突出刀枪鸣。', '曲终收拨当心画, 四弦一声如裂帛。', '东船西舫悄无言, 唯见江心秋月白。',
'沉吟放拨插弦中, 整顿衣裳起敛容。', '自言本是京城女, 家在虾蟆陵下住。', '十三学得琵琶成, 名属教坊第一部。',
'曲罢曾教善才服, 妆成每被秋娘妒。', '五陵年少争缠头, 一曲红绡不知数。', '钿头银篦击节碎, 血色罗裙翻酒污。',
'今年欢笑复明年, 秋月春风等闲度。', '弟走从军阿姨死, 暮去朝来颜色故。', '门前冷落鞍马稀, 老大嫁作商人妇。',
'商人重利轻别离, 前月浮梁买茶去。', '去来江口守空船, 绕船月明江水寒。', '夜深忽梦少年事, 梦啼妆泪红阑干。',
'我闻琵琶已叹息, 又闻此语重唧唧。', '同是天涯沦落人, 相逢何必曾相识!', '我从去年辞帝京, 谪居卧病浔阳城。',
'浔阳地僻无音乐, 终岁不闻丝竹声。', '住近湓江地低湿, 黄芦苦竹绕宅生。', '其间旦暮闻何物? 杜鹃啼血猿哀鸣。',
'春江花朝秋月夜, 往往取酒还独倾。', '岂无山歌与村笛? 呕哑嘲哳难为听。', '今夜闻君琵琶语, 如听仙乐耳暂明。',
'莫辞更坐弹一曲, 为君翻作《琵琶行》。', '感我此言良久立, 却坐促弦弦转急。', '凄凄不似向前声, 满座重闻皆掩泣。',
'座中泣下谁最多? 江州司马青衫湿。'
];
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;
// 弹幕全部显示之后 才能开始下一条弹幕
// 大概 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>

最后加一个 transform 和 left 的性能图对比:

transform

left

原生JS实现弹幕效果的更多相关文章

  1. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

  2. 原生JS实现分页效果2.0(新增了上一页和下一页,添加当前元素样式)

    虽然写的很烂,但至少全部都是自己写的,因为这个没有固定的顺序,所以就没有封装,如果你技术好的话,可以你写的分享给我,谢谢. <!DOCTYPE html><html lang=&qu ...

  3. 原生JS实现分页效果1.0

    不太完整,写的太急,等等加上完整注释,写起来还是有些难度的,写的有点水,后面再改进改进. <!DOCTYPE html><html lang="en">&l ...

  4. js实现弹幕效果

    <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...

  5. 原生JS实现"旋转木马"效果的图片轮播插件

    一.写在最前面 最近都忙一些杂七杂八的事情,复习软考.研读经典...好像都好久没写过博客了... 我自己写过三个图片轮播,一个是简单的原生JS实现的,没有什么动画效果的,一个是结合JQuery实现的, ...

  6. 原生js实现的效果

    原生js实现tooltip提示框的效果   在js的世界里面,每一个小的特效都那么微不足道,却又那么的令人向往与好奇.前端工程师的任务特别高大上,因为他们的一个小小的设计就会激发别人的求知欲.比如说我 ...

  7. 原生js仿jquery--animate效果

    效果 代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  8. 原生js简易日历效果实现

    这里我们将用原生js实现简易的日历,原理和之前的原生js选项卡差不多,不过也有些区别: 首先html代码: <div class="container"> <di ...

  9. 再谈React.js实现原生js拖拽效果

    前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...

随机推荐

  1. 微信支付自带的简易log

    using System; using System.Collections.Generic; using System.Web; using System.IO; namespace WxPayAP ...

  2. ABP 设置默认为中文

    把资源文件 的zh-cn去掉就可以,改成默认文件

  3. 【Mybatis】MyBatis之表的关联查询(五)

    本章介绍Mybatis之表的关联查询 一对一关联 查询员工信息以及员工的部门信息 1.准备表employee员工表,department部门表 CREATE TABLE `employee` ( `i ...

  4. Web性能和负载测试工具补充

    压力测试文档:https://yq.aliyun.com/articles/377543https://www.cnblogs.com/ahjxxy/archive/2012/09/17/268899 ...

  5. python学习基础总结

    看了一篇python基础的博客   感觉写的很好,总结的很到位,原地址为   http://blog.csdn.net/iloveyin/article/details/38754231 ****** ...

  6. 解决Chrome 70及以上版本的证书问题:Failed to load resource: net::ERR_CERT_SYMANTEC_LEGACY

    1.桌面必须要有Chrome 快捷方式 2.进入快捷方式属性 3.修改目标为:"C:\Program Files (x86)\Google\Chrome\Application\chrome ...

  7. shell脚本学习-分支结构

    跟着RUNOOB网站的教程学习的笔记 if语法格式 if condition then command1 command2 ... commandN fi 写成一行(使用于终端命令提示符): ]; t ...

  8. NIOS II 之串口学习

    UART中有6个寄存器分别为control, status, rxdata, txdata, divisor,endofpacket. 的寄存器是16位位宽的. UART会产生一个高电平的中断,当接收 ...

  9. idea 新建项目上传至git(coding)

    一.新建项目 1.改为git版本 2.出现如下框 选择Git 3.新建一个.gitignore file (Git) 4.勾掉一些不需要的 5.出现如下框 5.1.如果不知道.gitignore fi ...

  10. My year of 2017

    有一个姓罗的胖子,他说他有一个要坚持20年计划,第一年我真的不觉得什么,好比每天晚上都要刷牙每天早上都要吃早饭一样简单.实际几年走下来之后,发现能坚持下来真不是一件容易的事情,生活中总会有各种各样的事 ...