那些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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM