• 那些H5用到的技术(4)——弹幕


    前言

    以前玩卷轴射击游戏的时候,大量的BOSS子弹让我们无路可逃的时候,让我见识到了真正弹幕的威力,可自从A站B站火了之后,大量评论留言参与到了视频的播放中,也让我见识到了“弹幕”的威力,压根视频就没法看了……全看评论去了,就是那么好玩。

    现在没有弹幕功能都不好意思说是做视频or直播网站的。而我们也不能落后呐,产品提需求了,活动H5里面弄个弹幕留言,看起来就高大上有木有啊,以前的静态留言形势都太古板啦,弹幕才能用户high起来啊!好吧,本来说这玩意已经比较成熟了,找轮子,结果发现貌似没有html&js版本的,索性就自己写一个吧,暂时满足简单的需求就行。

    思路

    如下图所示

    主要有3个步骤
    1、生成弹幕
    根据相应的参数,设置弹幕的字体大小,字体颜色,甚至是弹幕背景(VIP功能,类似QQ聊天气泡),总之可以根据需求设计各种不同的弹幕样式。

    2、展示弹幕
    生成好弹幕我们就要对其进行展示了,目前主流的展示方式有2种,一种是从屏幕的右往左漂移展示,一种是在屏幕的上、中、下进行展示,这里需要考虑到尽量避免弹幕重叠,有效率用屏幕空间,当实在是没有空间了,才考虑重叠。

    3、清除弹幕
    在设定时间内显示完弹幕,为了节约资源,所以要对其进行移除清理,或者对其进行复用。

    实现

    话不多说,上代码看注释

    1. /**
    2. * by Leestar54
    3. * 简易弹幕插件 V1.0
    4. */
    5. ;
    6. (function($) {
    7. $.fn.danmu = function(options) {
    8. //默认参数
    9. var defaults = {
    10. fontSize: 16,
    11. color: 'black',
    12. showTime: 10000
    13. }
    14. //使用jQuery.extend 覆盖插件默认参数
    15. var options = $.extend(defaults, options);
    16. var hideTime = defaults.showTime + 500;
    17. //弹幕的行数
    18. var dm_lines = Math.floor($(this).height() / 16);
    19. //当前弹幕行是否有空间进行显示
    20. var dm_line_empty = Array(dm_lines);
    21. for (var i = 0; i < dm_line_empty.length; i++) {
    22. dm_line_empty[i] = true;
    23. }
    24. //中文和英文的宽度要分别算
    25. function getDivWidth(txt, fontSize) {
    26. var len = 0;
    27. var charRatio = fontSize * 5 / 8;
    28. for (var i = 0; i < txt.length; i++) {
    29. if (txt[i].match(/[^x00-xff]/ig) != null) //全角中文
    30. len += fontSize;
    31. else
    32. len += charRatio; //非中文经过测试得出比例
    33. }
    34. return len;
    35. }
    36. //公开函数
    37. this.add = function(txt, id, line, color, fontSize) {
    38. if (txt === undefined || txt === '') {
    39. //抛出异常
    40. throw 'txt should not be null';
    41. }
    42. if (line === undefined || line === '') {
    43. for (var i = 0; i < dm_line_empty.length; i++) {
    44. if (dm_line_empty[i]) {
    45. dm_line_empty[i] = false;
    46. line = i;
    47. break;
    48. }
    49. }
    50. //如果都满了,就随机覆盖了
    51. if (line === undefined || line === '') {
    52. line = Math.floor(Math.random() * dm_lines);
    53. }
    54. }
    55. if (color === undefined || color === '') {
    56. color = defaults.color;
    57. }
    58. if (fontSize === undefined || fontSize === '') {
    59. fontSize = defaults.fontSize;
    60. }
    61. if (id === undefined || id === '') {
    62. id = Math.floor(Math.random() * 100000000);
    63. }
    64. //使用dom构造单个弹幕div
    65. var div = document.createElement("div");
    66. div.id = 'txt_dm' + id;
    67. div.innerHTML = txt;
    68. div.style.webkitAnimation = 'run 1s 8s linear forwards';
    69. div.style.position = 'absolute';
    70. div.style.top = line * fontSize + 'px';
    71. div.style.left = $(this).width() + 'px';
    72. //设置宽度,否则字体会自适应换行,影响显示
    73. div.style.width = getDivWidth(txt, fontSize) + 'px';
    74. div.style.fontSize = fontSize + 'px';
    75. div.style.color = color;
    76. $(this).append(div);
    77. $(div).velocity({
    78. translateX: '-' + ($(this).width() + $(div).width()) + 'px'
    79. },
    80. defaults.showTime,
    81. 'linear');
    82. //屏幕完全显示完弹幕,就可以添加下一条了, +1为了用一定缓冲距离
    83. var fullShowTime = Math.floor($(div).width() / $(this).width() * defaults.showTime);
    84. setTimeout(function() {
    85. dm_line_empty[line] = true;
    86. }, fullShowTime);
    87. //删除显示完的弹幕
    88. setTimeout(function() {
    89. $('#txt_dm' + id).remove();
    90. }, hideTime);
    91. }
    92. return this;
    93. }
    94. })(jQuery);

    需要注意的是

    1. 中文与英文混合的情况下,宽度计算需要注意,经过测试,得出5/8的比例刚刚好,否则不是太长,就是不够导致换行。
    2. 使用var dm_line_empty = Array(dm_lines);来对每行的弹幕能否显示做判断,添加弹幕的时候,哪行空闲就忘哪行添加数据,提高屏幕的利用率。
    3. 弹幕移动使用的是velocity.js,否则效率太低,在移动设备上卡的飞起。
    4. 并没有实现屏幕上中下的弹幕模式……

    使用起来非常的简单,样式自行设置即可

    1. var dm = $('.container').danmu();
    2. dm.add('123');
    3. dm.add('双击66666666666666666');
    4. dm.add('老铁没毛病!')

    模式

    插件是有了,但是应用到业务上还需要我们自己做空置,一般来说有2个模式,供不同场景使用
    这里设计弹幕的基础数据库格式为,至于扩展和业务字段自行添加。
    id:自增
    txt:用户输入的弹幕
    time:用户提交时的总秒数,进入页面就开始从0每秒自增,提交时就是当时的总数,如果是视频,就是当时的播放时间。
    type:弹幕模式,可以包括从左往右,从右往左,屏幕上方,中间,下方等等。由于只实现了一种,这个字段我们这里去掉。
    可以考虑time,type作为复合索引。

    无限循环模式

    这应该是大多情况使用的模式,因为H5活动不像看视频,不可能按照发布时间来展示弹幕,因为也许用户发布一条评论的时间有20秒,那么岂不是前20秒整个弹幕都空空的?所以我们首要目的就是呈现那种热闹劲,一次性的读取全部弹幕,如果数量过多,可以做一些取舍,循环播放,每隔一定时间,重复显示弹幕。

    1. //--------------------------------重复展示弹幕----------------------------------------
    2. //重复展示时间间隔,如果弹幕少,就设置小一些,这样重复的多。
    3. var recycle_time = 10;
    4. var load_recycle_interval;
    5. var recycle_load_timeout;
    6. //重复展示弹幕
    7. function dmRecycleStart(data) {
    8. for (var i = 0; i < data.length; i++) {
    9. var id = data[i]['id'];
    10. var txt = data[i]['txt'];
    11. //闭包&立即执行传递参数,返回新的方法,否则add方法会被立即执行
    12. recycle_load_timeout[i] = setTimeout(function(txt, id) {
    13. return function() {
    14. dm.add(txt, id);
    15. };
    16. }(txt, id), 1000 * Math.floor(Math.random() * recycle_time) + 1); //随机时间点显示文字
    17. }
    18. }
    19. recycle_load_timeout = Array(data.length);
    20. //先显示一次
    21. dmRecycleStart(data);
    22. //然后每隔一段时间都展示一次
    23. load_recycle_interval = setInterval(function() {
    24. dmRecycleStart(data);
    25. }, recycle_time * 1000);

    时间线模式

    这种模式就是和视频弹幕一样的模式了,按照用户发送弹幕的时间进行播放, 我们需要设置个定时器,每一秒显示一次该时间点的弹幕,每隔一段时间读取时间段内的所有弹幕。
    我这里设计时,为了方便直接调用,服务器做了些格式转化,预先把消息按照时间点进行分组了,php参考代码如下:

    1. public function getdm()
    2. {
    3. $start_time = I('get.start_time');
    4. $end_time = I('get.end_time');
    5. $mode = I('get.type');
    6. $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);
    7. $ret = array();
    8. //根据时间进行分组
    9. foreach ($db_result as $key => $value) {
    10. $ret[$value['ptime']][] = array($value['id'], $value['txt']);
    11. }
    12. $this->ajaxReturn($ret);
    13. }

    JS代码参考如下:

    1. //---------------------------------时间线弹幕-----------------------------------------
    2. var dm_get_count = 0;
    3. //用户提交弹幕的时候,时间参数可以以此为基准。
    4. var dm_time = 0;
    5. var load_interval;
    6. var load_timeout;
    7. //加载弹幕
    8. function dmLoadTimeLine() {
    9. //每秒钟执行一次,更新时间,这里设置为每30秒为间隔显示一次弹幕(16秒开始预加载),根据时间点投放
    10. load_interval = setInterval(function() {
    11. if (dm_time % 16 == 0) {
    12. var start_time = dm_get_count * 30;
    13. var end_time = (dm_get_count + 1) * 30;
    14. //中间预加载
    15. // $.get('./getdm', {
    16. // start_time: start_time,
    17. // end_time: end_time,
    18. // type: typeid,
    19. // }, function(data) {
    20. dm_pre = line_data;
    21. console.log(start_time + '-' + end_time + '预加载完成');
    22. //第一次加载完成直接显示
    23. if (dm_get_count == 0) {
    24. dmTimeLineStart();
    25. }
    26. dm_get_count++;
    27. // });
    28. } else if (dm_time % 30 == 0) {
    29. dmTimeLineStart();
    30. }
    31. dm_time++;
    32. }, 1000);
    33. //每秒1个弹幕,30秒则有30个
    34. load_timeout = Array(30);
    35. }
    36. //时间线弹幕
    37. function dmTimeLineStart() {
    38. var start_time = dm_get_count * 30;
    39. var end_time = (dm_get_count + 1) * 30;
    40. console.log(start_time + '-' + end_time + '显示弹幕');
    41. //深度拷贝数组,如果是从接口获取数据,最好拷贝一次。
    42. var dm_current = $.extend(true, {}, dm_pre);
    43. //时间点内显示弹幕
    44. for (var i = start_time; i < end_time; i++) {
    45. if (dm_current[i] != null) {
    46. for (var j = 0; j < dm_current[i].length; j++) {
    47. var id = dm_current[i][j]['id'];
    48. var txt = dm_current[i][j]['txt'];
    49. //内部函数立即执行,闭包传递参数,
    50. load_timeout[i] = setTimeout(function(txt, id) {
    51. return function() {
    52. dm.add(txt, id)
    53. };
    54. }(txt, id), 1000 * i + 1); //指定时间点显示文字
    55. }
    56. }
    57. }
    58. }

    停止显示弹幕

    当用户关闭弹幕时,可以用下列代码,也说明了上面代码中一些参数的用处

    1. function dmStop() {
    2. clearInterval(load_interval);
    3. clearInterval(load_recycle_interval);
    4. dm_time = 0;
    5. dm_get_count = 0;
    6. dm_pre = Array();
    7. if (load_timeout != undefined) {
    8. for (var i = 0; i < load_timeout.length; i++) {
    9. clearTimeout(load_timeout[i]);
    10. }
    11. }
    12. if (recycle_load_timeout != undefined) {
    13. for (var i = 0; i < recycle_load_timeout.length; i++) {
    14. clearTimeout(recycle_load_timeout[i]);
    15. }
    16. }
    17. }

    最终demo效果如下:

    demo地址:
    https://github.com/leestar54/h5-demo/blob/master/danmu.html

  • 相关阅读:
    MVC3分页传2参
    C# 二进制存储图片到mssql(一)
    著名黑客组织[转]
    浅看C# md5加密
    google搜索技巧
    字符串编码转换 GBK utf8
    objectivec 中随机数的用法 (3种:arc4random() 、random()、CCRANDOM_0_1() )
    NSPredicate的用法
    Java关键字final、static使用总结()
    CGAffineTransform相关函数
  • 原文地址:https://www.cnblogs.com/leestar54/p/6523620.html
Copyright © 2020-2023  润新知