以前玩卷轴射击游戏的时候,大量的 BOSS 子弹让我们无路可逃的时候,让我见识到了真正弹幕的威力,可自从 A 站 B 站火了之后,大量评论留言参与到了视频的播放中,也让我见识到了 "弹幕" 的威力,压根视频就没法看了…… 全看评论去了,就是那么好玩。
现在没有弹幕功能都不好意思说是做视频 or 直播网站的。而我们也不能落后呐,产品提需求了,活动 H5 里面弄个弹幕留言,看起来就高大上有木有啊,以前的静态留言形势都太古板啦,弹幕才能用户 high 起来啊!好吧,本来说这玩意已经比较成熟了,找轮子,结果发现貌似没有 html&js 版本的,索性就自己写一个吧,暂时满足简单的需求就行。
如下图所示
主要有 3 个步骤
1、生成弹幕
根据相应的参数,设置弹幕的字体大小,字体颜色,甚至是弹幕背景(VIP 功能,类似 QQ 聊天气泡),总之可以根据需求设计各种不同的弹幕样式。
2、展示弹幕
生成好弹幕我们就要对其进行展示了,目前主流的展示方式有 2 种,一种是从屏幕的右往左漂移展示,一种是在屏幕的上、中、下进行展示,这里需要考虑到尽量避免弹幕重叠,有效率用屏幕空间,当实在是没有空间了,才考虑重叠。
3、清除弹幕
在设定时间内显示完弹幕,为了节约资源,所以要对其进行移除清理,或者对其进行复用。
话不多说,上代码看注释
- /** * by Leestar54 * 简易弹幕插件 V1.0 */
- ; (function($) {
- $.fn.danmu = function(options) { //默认参数 var defaults = { fontSize: 16, color: 'black', showTime: 10000 } //使用jQuery.extend 覆盖插件默认参数 var options = $.extend(defaults, options); var hideTime = defaults.showTime + 500; //弹幕的行数 var dm_lines = Math.floor($(this).height() / 16); //当前弹幕行是否有空间进行显示 var dm_line_empty = Array(dm_lines); for (var i = 0; i < dm_line_empty.length; i++) { dm_line_empty[i] = true; } //中文和英文的宽度要分别算 function getDivWidth(txt, fontSize) { var len = 0; var charRatio = fontSize * 5 / 8; for (var i = 0; i < txt.length; i++) { if (txt[i].match(/[^\x00-\xff]/ig) != null) //全角中文 len += fontSize; else len += charRatio; //非中文经过测试得出比例 } return len; } //公开函数 this.add = function(txt, id, line, color, fontSize) { if (txt === undefined || txt === '') { //抛出异常 throw 'txt should not be null'; } if (line === undefined || line === '') { for (var i = 0; i < dm_line_empty.length; i++) { if (dm_line_empty[i]) { dm_line_empty[i] = false; line = i; break; } } //如果都满了,就随机覆盖了 if (line === undefined || line === '') { line = Math.floor(Math.random() * dm_lines); } } if (color === undefined || color === '') { color = defaults.color; } if (fontSize === undefined || fontSize === '') { fontSize = defaults.fontSize; } if (id === undefined || id === '') { id = Math.floor(Math.random() * 100000000); } //使用dom构造单个弹幕div var div = document.createElement("div"); div.id = 'txt_dm' + id; div.innerHTML = txt; div.style.webkitAnimation = 'run 1s 8s linear forwards'; div.style.position = 'absolute'; div.style.top = line * fontSize + 'px'; div.style.left = $(this).width() + 'px'; //设置宽度,否则字体会自适应换行,影响显示 div.style.width = getDivWidth(txt, fontSize) + 'px'; div.style.fontSize = fontSize + 'px'; div.style.color = color; $(this).append(div); $(div).velocity({ translateX: '-' + ($(this).width() + $(div).width()) + 'px' }, defaults.showTime, 'linear'); //屏幕完全显示完弹幕,就可以添加下一条了, +1为了用一定缓冲距离 var fullShowTime = Math.floor($(div).width() / $(this).width() * defaults.showTime); setTimeout(function() { dm_line_empty[line] = true; }, fullShowTime); //删除显示完的弹幕 setTimeout(function() { $('#txt_dm' + id).remove(); }, hideTime); } return this; }})(jQuery);
需要注意的是
来对每行的弹幕能否显示做判断,添加弹幕的时候,哪行空闲就忘哪行添加数据,提高屏幕的利用率。
- var dm_line_empty = Array(dm_lines);
使用起来非常的简单,样式自行设置即可
- var dm = $('.container').danmu();
- dm.add('123');
- dm.add('双击66666666666666666');
- dm.add('老铁没毛病!')
插件是有了,但是应用到业务上还需要我们自己做空置,一般来说有 2 个模式,供不同场景使用
这里设计弹幕的基础数据库格式为,至于扩展和业务字段自行添加。
id:自增
txt:用户输入的弹幕
time:用户提交时的总秒数,进入页面就开始从 0 每秒自增,提交时就是当时的总数,如果是视频,就是当时的播放时间。
type:弹幕模式,可以包括从左往右,从右往左,屏幕上方,中间,下方等等。由于只实现了一种,这个字段我们这里去掉。
可以考虑 time,type 作为复合索引。
这应该是大多情况使用的模式,因为 H5 活动不像看视频,不可能按照发布时间来展示弹幕,因为也许用户发布一条评论的时间有 20 秒,那么岂不是前 20 秒整个弹幕都空空的?所以我们首要目的就是呈现那种热闹劲,一次性的读取全部弹幕,如果数量过多,可以做一些取舍,循环播放,每隔一定时间,重复显示弹幕。
- //--------------------------------重复展示弹幕---------------------------------------- //重复展示时间间隔,如果弹幕少,就设置小一些,这样重复的多。 var recycle_time = 10; var load_recycle_interval; var recycle_load_timeout; //重复展示弹幕 function dmRecycleStart(data) { for (var i = 0; i < data.length; i++) { var id = data[i]['id']; var txt = data[i]['txt']; //闭包&立即执行传递参数,返回新的方法,否则add方法会被立即执行 recycle_load_timeout[i] = setTimeout(function(txt, id) { return function() { dm.add(txt, id); }; }(txt, id), 1000 * Math.floor(Math.random() * recycle_time) + 1); //随机时间点显示文字 } } recycle_load_timeout = Array(data.length); //先显示一次 dmRecycleStart(data); //然后每隔一段时间都展示一次 load_recycle_interval = setInterval(function() { dmRecycleStart(data); }, recycle_time * 1000);
这种模式就是和视频弹幕一样的模式了,按照用户发送弹幕的时间进行播放, 我们需要设置个定时器,每一秒显示一次该时间点的弹幕,每隔一段时间读取时间段内的所有弹幕。
我这里设计时,为了方便直接调用,服务器做了些格式转化,预先把消息按照时间点进行分组了,php 参考代码如下:
- public
- function getdm() {
- $start_time = I('get.start_time');
- $end_time = I('get.end_time');
- $mode = I('get.type');
- $db_result = M('dm') - >query("select id,txt,ptime from dm where and ptime between %d and %d"and type = %d, $start_time, $end_time, $type);
- $ret = array(); //根据时间进行分组 foreach ($db_result as $key => $value) { $ret[$value['ptime']][] = array($value['id'], $value['txt']); } $this->ajaxReturn($ret); }
JS 代码参考如下:
- //---------------------------------时间线弹幕----------------------------------------- var dm_get_count = 0; //用户提交弹幕的时候,时间参数可以以此为基准。 var dm_time = 0; var load_interval; var load_timeout; //加载弹幕 function dmLoadTimeLine() { //每秒钟执行一次,更新时间,这里设置为每30秒为间隔显示一次弹幕(16秒开始预加载),根据时间点投放 load_interval = setInterval(function() { if (dm_time % 16 == 0) { var start_time = dm_get_count * 30; var end_time = (dm_get_count + 1) * 30; //中间预加载 // $.get('./getdm', { // start_time: start_time, // end_time: end_time, // type: typeid, // }, function(data) { dm_pre = line_data; console.log(start_time + '-' + end_time + '预加载完成'); //第一次加载完成直接显示 if (dm_get_count == 0) { dmTimeLineStart(); } dm_get_count++; // }); } else if (dm_time % 30 == 0) { dmTimeLineStart(); } dm_time++; }, 1000); //每秒1个弹幕,30秒则有30个 load_timeout = Array(30); } //时间线弹幕 function dmTimeLineStart() { var start_time = dm_get_count * 30; var end_time = (dm_get_count + 1) * 30; console.log(start_time + '-' + end_time + '显示弹幕'); //深度拷贝数组,如果是从接口获取数据,最好拷贝一次。 var dm_current = $.extend(true, {}, dm_pre); //时间点内显示弹幕 for (var i = start_time; i < end_time; i++) { if (dm_current[i] != null) { for (var j = 0; j < dm_current[i].length; j++) { var id = dm_current[i][j]['id']; var txt = dm_current[i][j]['txt']; //内部函数立即执行,闭包传递参数, load_timeout[i] = setTimeout(function(txt, id) { return function() { dm.add(txt, id) }; }(txt, id), 1000 * i + 1); //指定时间点显示文字 } } } }
当用户关闭弹幕时,可以用下列代码,也说明了上面代码中一些参数的用处
- function dmStop() {
- clearInterval(load_interval);
- clearInterval(load_recycle_interval);
- dm_time = 0;
- dm_get_count = 0;
- dm_pre = Array();
- if (load_timeout != undefined) {
- for (var i = 0; i < load_timeout.length; i++) {
- clearTimeout(load_timeout[i]);
- }
- }
- if (recycle_load_timeout != undefined) {
- for (var i = 0; i < recycle_load_timeout.length; i++) {
- clearTimeout(recycle_load_timeout[i]);
- }
- }
- }
最终 demo 效果如下:
demo 地址:
{aa10aa}
来源: http://www.bubuko.com/infodetail-1974691.html