彈幕的實現
一、前言
今天瀏覽某網站看到一個活動頁有內嵌的彈幕模塊(圖一),但是看到移動的彈幕重疊很多,不忍直視啊。突然想起很久之前自己寫寫過類似的彈幕,就翻出來看了一下,呵,也是不忍直視的,最後再附上當年的效果以及代碼;
二、大話幾點
1、彈幕應用場景,視頻中,直播中,微信墻等;
2、彈幕增加了大家的互動性,不再是單純的傻白甜的看著視頻,還可以吐吐槽,增加趣味性,但對於密集恐懼癥的我,每當一大波彈幕來襲,我習慣性的cut off;
3、現在視頻類的彈幕頁面一般內嵌到視頻中,微信墻等互動彈幕頁面一般用h5實現,呈現的方式都差不多,有的彈幕可以每條進行操作,有的只能看。有的彈幕每條彈幕還要根據用戶的相關等級,然後展示不同的效果,滿足不同玩家的訴求,各種玩;
4、好的彈幕應該至少滿足:彈幕分布密度,出現時機,速度,顏色,字體大小等都要做的相得益彰,至少能吸引大家進行互動。這就要考慮要一定的算法了,後面說。比如b站的彈幕就做的很好;
5、彈幕的後臺實現可以通過websocket實現,當然也可以借助node實現。當用戶輸入彈幕,彈幕需經過特殊處理,比如經過第三方(數美等)過濾敏感關鍵字等,最後再把內容下發,觜最後展示;
三、思考:怎麽做到彈幕均勻排布,不會重疊呢?還有速度控制問題。
1、可以想象把彈幕面板分成幾個管道,每條彈幕隨機分配(當然不是簡單的隨機,應該加上點概率論的知識)到每條管道中,當前方有彈幕時候,該彈幕的位置就要與前方彈幕保持一定計算的距離;
2、每條彈幕的速速要結合當前彈幕的長度和移動的位置去動態的添加初始速度,當然運行速度可以可以有linear ease-in ease-out ...等把控;
四、最後
很久前寫的彈幕,沒有對彈幕分布和速度進行把控,當然也沒有後臺,純粹前端展示了,我就不折騰了。
-------------------------------------------------
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>barrage</title> <style> .box{ width: 800px; height: 500px; margin:0 auto; } .barrage-container-wrap{ width: 100%; height: 500px; position: relative; overflow: hidden; background: url(‘./img/bg.jpg‘) no-repeat; background-size: 100% 100%; } .barrage-container{ position: absolute; top: 0; left: 0; right: 0; bottom: 30px; cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .barrage-item{ position:absolute; top:0; left: 100%; white-space: nowrap; cursor: pointer; color:#fff; } .barrage-item .barrage-tip{ display: none; position: absolute; top:-26px; padding: 7px 15px; line-height: 12px; font-size: 12px; color: #f20606; background-color: #fff; white-space: nowrap; border: 1px solid #ddd; border-radius: 8px; -webkit-box-shadow: 0 0 10px 1px rgba(0,0,0,.1); box-shadow: 0 0 10px 1px rgba(0,0,0,.1); -webkit-transform-origin: 15px 100%; -ms-transform-origin: 15px 100%; transform-origin: 15px 100%; webkit-animation: tipScale cubic-bezier(.22,.58,.12,.98) .4s; animation: tipScale cubic-bezier(.22,.58,.12,.98) .4s; } .send-wrap{ margin-top: 20px; } .input{ width: 300px; height: 30px; line-height: 30px; outline: none; -webkit-appearance: none; border-radius: 5px; padding:0; padding-left: 10px; } .send-btn{ height: 38px; line-height: 38px; text-align: center; font-weight: bold; color: #fff; background: #93d0ea; text-shadow:1px 1px 1px #333; border-radius: 5px; margin:0 20px 20px 0; position: relative; overflow: hidden; cursor: pointer; padding:5px 15px; } @-webkit-keyframes tipScale{ 0{ -webkit-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.1); transform: scale(1.1); } 100% { -webkit-transform: scale(1); transform: scale(1); } } </style> </head> <body> <div class="box"> <div class="barrage-container-wrap clearfix"> <div class="barrage-container"> </div> </div> <div class="send-wrap"> <input type="text" class="input" placeholder="彈幕發送"> <span class="send-btn">發送</span> </div> </div> </body> <script> ;(function(){ var barrageArray = [ { url: ‘用戶頭像‘, text: ‘秋天愛美麗‘, level: 10 }, { url: ‘用戶頭像‘, text: ‘今天很開心啊‘, level: 10 }, { url: ‘用戶頭像‘, text: ‘winter has come‘, level: 10 }, { url: ‘‘, text: ‘土耳其現在形勢‘, level: 10 }, { url: ‘‘, text: ‘沒事早點回家吃飯啊‘, level: 10 }, { url: ‘‘, text: ‘這主角真實醉了,不會回啊‘, level: 10 }, { url: ‘‘, text: ‘背景音樂真好聽啊‘, level: 10 }, { url: ‘‘, text: ‘背景音樂是***‘, level: 10 }, { url: ‘‘, text: ‘經費在燃燒啊‘, level: 10 }, { url: ‘‘, text: ‘國產良心劇‘, level: 10 }, ]; var barrageColorArray = [ ‘#0099CC‘,‘#333333‘, ‘#009966‘,‘#FFFF66‘,‘#9933FF‘,‘#FFFF99‘,‘#CCCCFF‘,‘#CC9933‘,‘#FFFF66‘ ]; var barrageTipWidth = 50; //提示語的長度 var barrageBoxWrap = document.querySelector(‘.barrage-container-wrap‘);; var barrageBox = document.querySelector(‘.barrage-container‘); var inputBox = document.querySelector(‘.input‘); var sendBtn = document.querySelector(‘.send-btn‘); //容器的寬高度 var barrageWidth = ~~window.getComputedStyle(barrageBoxWrap).width.replace(‘px‘,‘‘); var barrageHeight = ~~window.getComputedStyle(barrageBoxWrap).height.replace(‘px‘,‘‘); //發送 function sendMsg(){ var inputValue = inputBox.value; inputValue .replace(/\ +/g, ""); if (inputValue.length <= 0) { alert(‘請輸入‘); return false; } //生成彈幕 createBarrage(inputValue,true); inputBox.value = ‘‘; } //創建彈幕 function createBarrage(msg, isSendMsg){ var divNode = document.createElement(‘div‘); var spanNode = document.createElement(‘span‘); divNode.innerHTML = msg; divNode.classList.add(‘barrage-item‘); barrageBox.appendChild(divNode); spanNode.innerHTML = ‘舉報‘; spanNode.classList.add(‘barrage-tip‘); divNode.appendChild(spanNode); barrageOffsetLeft = getRandom(barrageWidth, barrageWidth*2); barrageOffsetLeft = isSendMsg ? barrageWidth : barrageOffsetLeft barrageOffsetTop = getRandom(10, barrageHeight-10); barrageColor = barrageColorArray[Math.floor(Math.random()*(barrageColorArray.length))]; //執行初始化滾動 initBarrage.call(divNode,{ left : barrageOffsetLeft, top : barrageOffsetTop, color : barrageColor }); } //初始化彈幕移動(速度,延遲) function initBarrage(obj) { //初始化 obj.top = obj.top || 0; obj.class = obj.color || ‘#fff‘; this.style.left = obj.left + ‘px‘; this.style.top = obj.top + ‘px‘; this.style.color = obj.color; //添加屬性 this.distance = 0; this.width = ~~window.getComputedStyle(this).width.replace(‘px‘,‘‘); this.offsetLeft = obj.left; this.timer = null; //彈幕子節點 var barrageChileNode = this.children[0]; barrageChileNode.style.left = (this.width-barrageTipWidth)/2 + ‘px‘; //運動 barrageAnimate(this); //停止 this.onmouseenter = function(){ barrageChileNode.style.display= ‘block‘; cancelAnimationFrame(this.timer); }; this.onmouseleave = function(){ barrageChileNode.style.display = ‘none‘; barrageAnimate(this); }; //舉報 barrageChileNode.onclick = function(){ alert(‘舉報成功‘); } } //彈幕動畫 function barrageAnimate(obj){ move(obj); if(Math.abs(obj.distance) < obj.width+obj.offsetLeft){ obj.timer = requestAnimationFrame(function(){ barrageAnimate(obj); }); }else{ cancelAnimationFrame(obj.timer); //刪除節點 obj.parentNode.removeChild(obj); } } //移動 function move(obj){ obj.distance--; obj.style.transform = ‘translateX(‘+obj.distance+‘px)‘; obj.style.webkitTransform = ‘translateX(‘+obj.distance+‘px)‘; } //隨機獲取高度 function getRandom(start, end){ return start +(Math.random() * (end - start)); } /*******初始化事件**********/ //系統數據 barrageArray.forEach(function(item,index){ createBarrage(item.text, false); }); //點擊發送 sendBtn.onclick = sendMsg; //點擊發送 //回車 inputBox.onkeydown = function(e){ e = e|| window.event; if(e.keyCode == 13){ send(); } } })() //兼容寫法 (function() { var lastTime = 0; var vendors = [‘webkit‘, ‘moz‘]; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + ‘RequestAnimationFrame‘]; window.cancelAnimationFrame = window[vendors[x] + ‘CancelAnimationFrame‘] || // Webkit中此取消方法的名字變了 window[vendors[x] + ‘CancelRequestAnimationFrame‘]; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16.7 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function(id) { clearTimeout(id); }; } }()); </script> </html>
彈幕的實現