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

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

 

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

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

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

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

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

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

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

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

完整代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>原生JS实现弹幕效果</title>
  6. <style>
  7. #wrapper {
  8. height: 400px;
  9. width: 700px;
  10. position: relative;
  11. overflow: hidden;
  12. background: url(http://www.drama-asia.se/wp-content/uploads/2016/06/14375197_1349947520504_800x600.jpg);
  13.  
  14. color: #ffffff82;
  15. font-size: 14px;
  16. text-shadow: 1px 1px #000;
  17. }
  18. .right {
  19. position: absolute;
  20. visibility: hidden;
  21. white-space: nowrap;
  22. /*left: 700px;*/
  23. transform: translateX(700px);
  24. }
  25. .left {
  26. position: absolute;
  27. white-space: nowrap;
  28. user-select: none;
  29. transition: transform 7s linear; /* 时间相同 越长的弹幕滑动距离越长 所以越快~ */
  30. }
  31. input {
  32. position: absolute;
  33. bottom: 10px;
  34. left: 150px;
  35. width: 300px;
  36. height: 26px;
  37. }
  38.  
  39. button {
  40. position: absolute;
  41. bottom: 8px;
  42. left: 476px;
  43. width: 100px;
  44. height: 38px;
  45. border-radius: 10px;
  46. font-size: 16px;
  47. }
  48. </style>
  49. </head>
  50. <body>
  51. <div id="wrapper">
  52. <input type="text">
  53. <button>发&nbsp;&nbsp;送</button>
  54. </div>
  55. <script>
  56. /**
  57. * 设置 弹幕DOM池 每一个通道最多六条弹幕
  58. **/
  59.  
  60. const MAX_DM_COUNT = 6;
  61. const CHANNEL_COUNT = 10;
  62.  
  63. let domPool = [];
  64. let danmuPool = [
  65. '前方大量弹幕来袭,请做好准备!', '2333333', '2333333', '2333333', '2333333', '2333333',
  66. '浔阳江头夜送客, 枫叶荻花秋瑟瑟', '2333333', '2333333', '2333333', '2333333', '2333333', '2333333',
  67. '主人下马客在船, 举酒欲饮无管弦。', '醉不成欢惨将别, 别时茫茫江浸月', '忽闻水上琵琶声, 主人忘归客不发。',
  68. '寻声暗问弹者谁? 琵琶声停欲语迟。', '移船相近邀相见, 添酒回灯重开宴。', '千呼万唤始出来, 犹抱琵琶半遮面。',
  69. '转轴拨弦三两声, 未成曲调先有情。', '弦弦掩抑声声思, 似诉平生不得志。', '低眉信手续续弹, 说尽心中无限事。',
  70. '轻拢慢捻抹复挑, 初为霓裳后六幺。', '大弦嘈嘈如急雨, 小弦切切如私语。', '嘈嘈切切错杂弹, 大珠小珠落玉盘。',
  71. '间关莺语花底滑, 幽咽泉流冰下难。', '冰泉冷涩弦凝绝, 凝绝不通声暂歇。', '别有幽愁暗恨生, 此时无声胜有声。',
  72. '银瓶乍破水浆迸, 铁骑突出刀枪鸣。', '曲终收拨当心画, 四弦一声如裂帛。', '东船西舫悄无言, 唯见江心秋月白。',
  73. '沉吟放拨插弦中, 整顿衣裳起敛容。', '自言本是京城女, 家在虾蟆陵下住。', '十三学得琵琶成, 名属教坊第一部。',
  74. '曲罢曾教善才服, 妆成每被秋娘妒。', '五陵年少争缠头, 一曲红绡不知数。', '钿头银篦击节碎, 血色罗裙翻酒污。',
  75. '今年欢笑复明年, 秋月春风等闲度。', '弟走从军阿姨死, 暮去朝来颜色故。', '门前冷落鞍马稀, 老大嫁作商人妇。',
  76. '商人重利轻别离, 前月浮梁买茶去。', '去来江口守空船, 绕船月明江水寒。', '夜深忽梦少年事, 梦啼妆泪红阑干。',
  77. '我闻琵琶已叹息, 又闻此语重唧唧。', '同是天涯沦落人, 相逢何必曾相识!', '我从去年辞帝京, 谪居卧病浔阳城。',
  78. '浔阳地僻无音乐, 终岁不闻丝竹声。', '住近湓江地低湿, 黄芦苦竹绕宅生。', '其间旦暮闻何物? 杜鹃啼血猿哀鸣。',
  79. '春江花朝秋月夜, 往往取酒还独倾。', '岂无山歌与村笛? 呕哑嘲哳难为听。', '今夜闻君琵琶语, 如听仙乐耳暂明。',
  80. '莫辞更坐弹一曲, 为君翻作《琵琶行》。', '感我此言良久立, 却坐促弦弦转急。', '凄凄不似向前声, 满座重闻皆掩泣。',
  81. '座中泣下谁最多? 江州司马青衫湿。'
  82. ];
  83. let hasPosition = [];
  84.  
  85. /**
  86. * 做一下初始化工作
  87. */
  88. function init() {
  89. let wrapper = document.getElementById('wrapper')
  90. // 先new一些span 重复利用这些DOM
  91. for (let j = 0; j < CHANNEL_COUNT; j++) {
  92. let doms = [];
  93. for (let i = 0; i < MAX_DM_COUNT; i++) {
  94. // 要全部放进wrapper
  95. let dom = document.createElement('span');
  96. wrapper.appendChild(dom);
  97. // 初始化dom的位置 通过设置className
  98. dom.className = 'right';
  99. // DOM的通道是固定的 所以设置好top就不需要再改变了
  100. dom.style.top = j * 20 + 'px';
  101. // 放入改通道的DOM池
  102. doms.push(dom);
  103. // 每次到transition结束的时候 就是弹幕划出屏幕了 将DOM位置重置 再放回DOM池
  104. dom.addEventListener('transitionend', () => {
  105. dom.className = 'right';
  106. // dom.style.transition = null;
  107. // dom.style.left = null;
  108. dom.style.transform = null;
  109.  
  110. domPool[j].push(dom);
  111. });
  112. }
  113. domPool.push(doms);
  114. }
  115. // hasPosition 标记每个通道目前是否有位置
  116. for (let i = 0; i < CHANNEL_COUNT; i++) {
  117. hasPosition[i] = true;
  118. }
  119. }
  120.  
  121. /**
  122. * 获取一个可以发射弹幕的通道 没有则返回-1
  123. */
  124. function getChannel() {
  125. for (let i = 0; i < CHANNEL_COUNT; i++) {
  126. if (hasPosition[i] && domPool[i].length) return i;
  127. }
  128. return -1;
  129. }
  130.  
  131. /**
  132. * 根据DOM和弹幕信息 发射弹幕
  133. */
  134. function shootDanmu(dom, text, channel) {
  135. console.log('biu~ [' + text + ']');
  136. dom.innerText = text;
  137. // 如果为每个弹幕设置 transition 可以保证每个弹幕的速度相同 这里没有保证速度相同
  138. // dom.style.transition = `transform ${7 + dom.clientWidth / 100}s linear`;
  139.  
  140. // dom.style.left = '-' + dom.clientWidth + 'px';
  141. // 设置弹幕的位置信息 性能优化 left -> transform
  142. dom.style.transform = `translateX(${-dom.clientWidth}px)`;
  143. dom.className = 'left';
  144.  
  145. hasPosition[channel] = false;
  146. // 弹幕全部显示之后 才能开始下一条弹幕
  147. // 大概 dom.clientWidth * 10 的时间 该条弹幕就从右边全部划出到可见区域 再加1秒保证弹幕之间距离
  148. setTimeout(() => {
  149. hasPosition[channel] = true;
  150. }, dom.clientWidth * 10 + 1000);
  151. }
  152.  
  153. window.onload = function() {
  154.  
  155. init();
  156.  
  157. // 为input和button添加事件监听
  158. let btn = document.getElementsByTagName('button')[0];
  159. let input = document.getElementsByTagName('input')[0];
  160. btn.addEventListener('click', () => {
  161. input.value = input.value.trim();
  162. if (input.value) danmuPool.push(input.value);
  163. })
  164. input.addEventListener('keyup', (e) => {
  165. if (e.key === 'Enter' && (input.value = input.value.trim())) {
  166. danmuPool.push(input.value);
  167. }
  168. })
  169. // 每隔1ms从弹幕池里获取弹幕(如果有的话)并发射
  170. setInterval(() => {
  171. let channel;
  172. if (danmuPool.length && (channel = getChannel()) != -1) {
  173. let dom = domPool[channel].shift();
  174. let danmu = danmuPool.shift();
  175. shootDanmu(dom, danmu, channel);
  176. }
  177. }, 1);
  178.  
  179. }
  180.  
  181. </script>
  182. </body>
  183. </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. 第一章 odoo的配置(centos7 版)

    一: 简述 (1) odoo是python开发的一款erp软件,目前的最新版本为odoo 11, 支持Python2和Python3, 但odoo 11目测是一个过渡版本,为了稳定,我们还是上odoo ...

  2. HttpServletRequest字符集问题

    post中文处理 1post在spring里的设置web.xml文件 <!-- 字符处理 UTF8 --> <filter> <filter-name>encodi ...

  3. Mac 安装配置Jenkins+github完成项目构建

    Jenkins Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建.测试和部署软件.Jenkins 支持各种运行方式,可通过系统包, Docker 或者通过一个独立的 J ...

  4. clouderamanager安装

    下载地址 http://archive.cloudera.com/cm5/cm/5/ 安装 先安装manager,再安装cdh 待续

  5. orm单表查询和模糊查询

    一.单表查询 1. 返回queryset对象的查询 all() 以列表形式返回全部queryset对象 filter(**kwargs) 筛选 exclude(**kwargs) 排除 reverse ...

  6. python中3个连续的单引号是什么意思?''' ... ''' 这样的引号是什么意思?

  7. Codeforces 884 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 传送门 A题 传送门 题意简述: 一个人要完成一件事总共需要ttt秒,现在有nnn天,每天有aia_iai​不能做事,问他可以在第几天做完. 思路:按照题 ...

  8. Python_day7

    sys模块 import sys def _add(a, b): return a + b def _sub(a, b): return a - b def _mul(a, b): return a ...

  9. shell遍历文件夹

    遍历目录下的所有文件 假如有一个文件夹路径为dir,遍历文件 for file in /path/dir/* do if test -f $file then echo $file arrary=($ ...

  10. 摘录<奇特的一生>1~4——[苏]格拉宁

    一 只有在不实事求是的时候,事实才会叫人感兴趣. 虚构的人物任人摆布,并且纤毫毕露--他的一切想法意图,他的过去和未来,作者都一清二楚. 我还有一个任务:向读者灌输一些有用的知识,介绍些材料. 是一个 ...