實現(xiàn)vue的彈幕功能

1.代碼如下

<template>

??<div?class="home">

????<h2?style="?text-align:?center;">Vue-barrage?基于vue的彈幕組件</h2>

????<div

??????style="height:400px;width:800px;?position:?relative;margin:0px?auto;background:#000;"

????>

??????<!--?<video?controls

?????????????src="@/assets/1.mp4"

?????????????autoplay

??????style="width:100%;height:100%;z-index:2;"?/>-->

??????<!--?確保父元素是相對定位,彈幕容器是絕對定位?-->

??????<v-barrage?:arr="arr"?:isPause="isPause"?:percent="100"></v-barrage>

????</div>

????<div?class="barrage-control">

??????<input

????????type="text"

????????v-model="sendContent"

????????placeholder="回車發(fā)送"

????????id="sendContent"

????????@keyup.enter="sendBarrage"

??????/>

??????方向:

??????<select?style="margin:0px?12px;"?v-model="direction">

????????<option?value="default">默認</option>

????????<option?value="top">頂部</option>

??????</select>

??????<input?type="checkbox"?v-model="isJs"?/>?js彈幕(直接寫代碼)

??????<button

????????id="sendBarrageBtn"

????????style="margin-left:25px;"

????????@click="sendBarrage"

??????>

????????發(fā)送

??????</button>

??????<button?id="pauseBtn"?@click="isPause?=?true">暫停</button>

??????<button?id="startBtn"?@click="isPause?=?false">開始</button>

????</div>

??</div>

</template>

<script>

//?@?is?an?alias?to?/src

import?VBarrage?from?"../components/index";

export?default?{

??name:?"home",

??components:?{

????VBarrage,

??},

??data()?{

????return?{

??????arr:?[],

??????isPause:?false,

??????sendContent:?null,

??????isJs:?false,

??????direction:?"default",

????};

??},

??mounted()?{

????this.initTestData();

??},

??methods:?{

????//?初始化模擬彈幕數(shù)據

????initTestData()?{

??????let?arr?=?["這是一條有彈幕",?"今天去打LOL",?"可以嗎辆琅?",?"一起嗨8囱Nò馈疤苹!"];

??????for?(let?i?=?0;?i?<?6;?i++)?{

????????for?(let?index?=?0;?index?<?1000;?index++)?{

??????????if?(index?%?2?==?0)?{

????????????this.arr.push({

??????????????direction:?"top",

??????????????content:?arr[parseInt(Math.random()?*?arr.length)],

????????????});

??????????}?else?{

????????????this.arr.push({

??????????????direction:?"default",

??????????????content:?arr[parseInt(Math.random()?*?arr.length)],

????????????});

??????????}

????????}

??????}

????},

????//?發(fā)送彈幕

????sendBarrage()?{

??????if?(

????????this.arr.length?>?1?&&

????????this.sendContent?!=?""?&&

????????this.sendContent?!=?null

??????)?{

????????this.arr.unshift({

??????????content:?this.sendContent,

??????????direction:?this.direction,

??????????isSelf:?true,

??????????style:?{

????????????color:?"red",

????????????fontSize:?"25px",

??????????},

??????????isJs:?this.isJs,

????????});

??????}?else?{

????????this.arr.push({

??????????content:?this.sendContent,

??????????direction:?this.direction,

??????????isSelf:?true,

??????????style:?{

????????????color:?"red",

??????????},

??????????isJs:?this.isJs,

????????});

??????}

??????this.sendContent?=?null;

????},

??},

};

</script>

<style?lang="scss"?scoped>

.barrage-control?{

??text-align:?center;

??margin:?10px?0px;

}

</style>

2.在component 里面創(chuàng)建 index.vue 代碼如下

<template>

??<section?class="barrage-wrapper"

???????????ref="barrageWrapper"

???????????:style="{height:percent+'%'}">

????<div?class="barrage-main">

??????<div?class="barrage-main-dm"

???????????:class="{'ani-pause':isPause,'ani-running':!isPause}"

???????????ref="barrageMainDm"></div>

????</div>

??</section>

</template>

<script>

export?default?{

??props:?{

????//?彈幕源數(shù)組

????arr:?{

??????type:?Array,

??????default:?function?()?{

????????return?[]

??????}

????},

????//?彈幕是否暫停狀態(tài)

????isPause:?{

??????type:?Boolean

????},

????//?彈幕占比

????percent:?{

??????type:?Number,

??????default:?80

????}

??},

??data?()?{

????return?{

??????//?每行彈幕數(shù)最大值

??????MAX_DM_COUNT:?5,

??????//?行數(shù)

??????CHANNEL_COUNT:?8,

??????//?彈幕數(shù)組

??????barrages:?[],

??????//?dom池

??????domPool:?[],

??????//?intervalDM

??????intervalDM:?null,

??????//?取彈幕時間間隔

??????interValTime:?500,

??????//?滾動彈幕的通道

??????hasPosition:?[],

??????//?頂部彈幕的通道

??????hasTopPosition:?[],

??????//?彈幕容器

??????barrageMainDm:?null,

??????//?彈幕容器寬度

??????barMainWidth:?500,

??????//?自定義彈幕樣式屬性列表

??????dmStyles:?[

????????'color',

????????'fontSize'

??????]

????};

??},

??computed:?{

??},

??created?()?{

??},

??mounted?()?{

????this.barrageMainDm?=?this.$refs.barrageMainDm;

????//?緩存容器寬度

????this.barMainWidth?=?this.barrageMainDm.clientWidth;

????//?初始化彈幕dom組

????this.init();

????//?開始播放彈幕

????this.playDm();

????//?注冊頁面監(jiān)聽器

????document.addEventListener("visibilitychange",?this.visibilitychangeFn);

??},

??watch:?{

????arr?(list)?{

??????this.barrages?=?list;

????},

????isPause?(val)?{

??????if?(val)?{

????????this.pauseDm();

??????}?else?{

????????this.playDm();

??????}

????},

????percent?(val)?{

??????this.CHANNEL_COUNT?=?val?/?10;

????}

??},

??methods:?{

????visibilitychangeFn?()?{

??????if?(!document.hidden)?{

????????//處于當前頁面

????????this.playDm();

????????console.log('進入頁面');

??????}?else?{

????????console.log('離開頁面');

????????clearInterval(this.intervalDM);

????????this.intervalDM?=?null;

??????}

????},

????init?()?{

??????let?wrapper?=?this.$refs.barrageMainDm;

??????//?先new一些div?重復利用這些DOM

??????for?(let?j?=?0;?j?<?this.CHANNEL_COUNT;?j++)?{

????????let?doms?=?[];

????????for?(let?i?=?0;?i?<?this.MAX_DM_COUNT;?i++)?{

??????????//?初始化dom

??????????let?dom?=?document.createElement('div');

??????????//?初始化dom的位置?通過設置className

??????????dom.className?=?'barrage-item';

??????????dom.style.transform?=?`translate3d(${this.barMainWidth}px,0,0)`

??????????//?DOM的通道是固定的?所以設置好top就不需要再改變了

??????????dom.style.top?=?j?*?(this.barrageMainDm.clientHeight?/?this.CHANNEL_COUNT)?+?'px';

??????????console.log(dom.style.top)

??????????//?每次到animationend結束的時候?就是彈幕劃出屏幕了?將DOM位置重置?再放回DOM池

??????????dom.addEventListener('animationend',?(e)?=>?{

????????????//?初始化dom樣式

????????????dom.className?=?'barrage-item';

????????????dom.style.transform?=?`translate3d(${this.barMainWidth}px,0,0)`

????????????dom.style.animation?=?null;

????????????//?清空自定義樣式

????????????this.dmStyles.forEach(key?=>?{

??????????????dom.style[key]?=?null;

????????????})

????????????//?this.domPool[j].push({?el:?dom?});

????????????//?動畫結束后設置當前dom為空閑狀態(tài)

????????????//?console.log(i,?j)

????????????if?(this.domPool[i][j])?{

??????????????this.domPool[i][j].isFree?=?false;

????????????}

????????????//?console.log(dom)

??????????});

??????????//?放入該通道的DOM池

??????????doms.push({

????????????row:?j,

????????????col:?i,

????????????el:?dom,

????????????isUse:?false,

????????????isFree:?false

??????????});

????????}

????????this.domPool.push(doms);

??????}

??????//?hasPosition?標記每個通道目前是否有位置

??????for?(let?i?=?0;?i?<?this.CHANNEL_COUNT;?i++)?{

????????this.hasPosition[i]?=?true;

????????this.hasTopPosition[i]?=?true;

??????}

????},

????/**

?????*?獲取一個可以發(fā)射彈幕的通道?沒有則返回-1

?????*/

????getChannel?()?{

??????for?(let?i?=?0;?i?<?this.CHANNEL_COUNT;?i++)?{

????????if?(this.hasPosition[i]?&&?this.domPool[i].length)?return?i;

??????}

??????return?-1;

????},

????/**

????*?獲取一個可以發(fā)射頂部彈幕的通道?沒有則返回-1

????*/

????getTopChannel?()?{

??????for?(let?i?=?0;?i?<?this.CHANNEL_COUNT;?i++)?{

????????if?(this.hasTopPosition[i])?return?i;

??????}

??????return?-1;

????},

????/**

????*?根據DOM和彈幕信息?發(fā)射彈幕

????*/

????shootDanmu?(domItem,?dmItem,?channel)?{

??????//?設置當前通道為false

??????this.hasPosition[channel]?=?false;

??????//獲取dom

??????let?dom?=?domItem.el;

??????//?是否已經使用的dom

??????if?(!domItem.isUse?&&?this.barrageMainDm)?{

????????domItem.isUse?=?true;

????????this.barrageMainDm.appendChild(domItem.el);

??????}

??????//?判斷是否有自定義樣式

??????if?(dmItem.style)?{

????????let?keys?=?Object.keys(dmItem.style);

????????let?self?=?this;

????????keys.forEach(key?=>?{

??????????//?檢查樣式屬性是否合法

??????????let?isStyle?=?self.dmStyles.includes(key)

??????????if?(isStyle)?{

????????????dom.style[key]?=?dmItem.style[key];

??????????}

????????});

??????}

??????domItem.isFree?=?true;

??????//?console.log('biu~?['?+?dmItem.content?+?']');

??????//?dom.innerText?=?dmItem.content;

??????//?判斷是否是js彈幕

??????if?(dmItem.isJs)?{

????????dom.innerHTML?=?dmItem.content;

??????}?else?{

????????dom.innerText?=?dmItem.content;

??????}

??????//?設置彈幕的位置信息?性能優(yōu)化?left?->?transform

??????//?dom.style.transform?=?`translateX(${-dom.clientWidth}px)`;

??????//?dom.style.transform?=?`translate3d(${-dom.clientWidth}px,0,0)`;?//?css硬件加速

??????dom.className?=?dmItem.isSelf???'barrage-item?self-dm'?:?'barrage-item';

??????dom.style.animation?=?'barrage-run?5s?linear';

??????//?彈幕全部顯示之后?才能開始下一條彈幕

??????//?大概?dom.clientWidth?*?10?的時間?該條彈幕就從右邊全部劃出到可見區(qū)域?再加1秒保證彈幕之間距離

??????setTimeout(()?=>?{

????????this.hasPosition[channel]?=?true;

??????},?dom.clientWidth?*?10?+?1000);

????},

????shootTopDanmu?(dmItem,?channel)?{

??????//?設置當前通道為false

??????this.hasTopPosition[channel]?=?false;

??????//獲取dom

??????let?dom?=?document.createElement('div');

??????dom.className?=?dmItem.isSelf???'barrage-item?top-item?self-dm'?:?'barrage-item?top-item';

??????//?判斷是否有自定義樣式

??????if?(dmItem.style)?{

????????let?keys?=?Object.keys(dmItem.style);

????????let?self?=?this;

????????keys.forEach(key?=>?{

??????????//?檢查樣式屬性是否合法

??????????let?isStyle?=?self.dmStyles.includes(key)

??????????if?(isStyle)?{

????????????dom.style[key]?=?dmItem.style[key];

??????????}

????????});

??????}

??????//?判斷是否是js彈幕

??????if?(dmItem.isJs)?{

????????dom.innerHTML?=?dmItem.content;

??????}?else?{

????????dom.innerText?=?dmItem.content;

??????}

??????dom.addEventListener('animationend',?()?=>?{

????????this.barrageMainDm.removeChild(dom)

????????this.hasTopPosition[channel]?=?true;

??????})

??????this.barrageMainDm.appendChild(dom);?//?一定要在獲取寬度和執(zhí)行動畫之前渲染dom

??????dom.style.transform?=?`translate3d(${this.barMainWidth?/?2?-?(dom.clientWidth?/?2)}px,${channel?*?dom.clientHeight}px,0)`

??????dom.style.animation?=?'barrage-fade?3s';

????},

????//?獲取空閑通道中空閑的dom

????getFreeChannelDom?(channel)?{

??????let?item;

??????item?=?this.domPool[channel].find(it?=>?!it.isFree)

??????return?item

????},

????//?暫停彈幕

????pauseDm?()?{

??????if?(this.intervalDM)?{

????????clearInterval(this.intervalDM)

????????this.intervalDM?=?null;

??????}

????},

????//?播放彈幕

????playDm?()?{

??????//?每隔1ms從彈幕池里獲取彈幕(如果有的話)并發(fā)射

??????let?self?=?this;?//?這里取一個self?this?為了方便調試的時候看到this具體內容

??????self.intervalDM?=?setInterval(()?=>?{

????????//?let?channel;

????????//?if?(self.barrages.length?&&?(channel?=?self.getChannel())?!=?-1)?{

????????//???let?domItem?=?self.getFreeChannelDom(channel);

????????//???//?let?domItem?=?self.domPool[channel].shift();

????????//???if?(domItem)?{

????????//?????let?danmu?=?self.barrages.shift();

????????//?????self.shootDanmu(domItem,?danmu,?channel);

????????//???}

????????//?}

????????//?更新邏輯

????????if?(self.barrages.length?>?0)?{

??????????let?channel;

??????????let?topChannel;

??????????let?barrage?=?self.barrages.shift();

??????????if?(barrage.direction?==?'top'?&&?(topChannel?=?self.getTopChannel())?!=?-1)?{

????????????//?top....

????????????self.shootTopDanmu(barrage,?topChannel);

??????????}

??????????if?(barrage.direction?==?'default'?&&?(channel?=?self.getChannel())?!=?-1)?{

????????????//?default....

????????????let?domItem?=?self.getFreeChannelDom(channel);

????????????if?(domItem)?{

??????????????//?let?danmu?=?self.barrages.shift();

??????????????self.shootDanmu(domItem,?barrage,?channel);

????????????}

??????????}

????????}

??????},?self.interValTime);

????}

??},

??components:?{

??},

};

</script>

<style??lang="scss">

.barrage-wrapper?{

??*?{

????margin:?0px;

????padding:?0px;

??}

??z-index:?1;

??position:?absolute;

??left:?0px;

??top:?0px;

??width:?100%;

??height:?100%;

??.barrage-item?{

????z-index:?99;

????position:?absolute;

????//?left:?0px;

????//?top:?0px;

????//?transform:?translateX(-100%);

????//?padding:?5px?0px;

????user-select:?none;?//?禁用選擇文字

????position:?absolute;

????white-space:?pre;

????cursor:?pointer;

????//?pointer-events:?none;

????//?perspective:?500px;

????display:?inline-block;

????will-change:?transform;

????font-size:?25px;

????color:?rgb(255,?255,?255);

????font-family:?SimHei,?"Microsoft?JhengHei",?Arial,?Helvetica,?sans-serif;

????font-weight:?bold;

????line-height:?1.125;

????opacity:?1;

????text-shadow:?rgb(0,?0,?0)?1px?0px?1px,?rgb(0,?0,?0)?0px?1px?1px,

??????rgb(0,?0,?0)?0px?-1px?1px,?rgb(0,?0,?0)?-1px?0px?1px;

????&:hover?{

??????color:?red;

??????animation-play-state:?paused?!important;

??????z-index:?150;

????}

??}

??.top-item?{

????z-index:?100;

??}

??.barrage-main?{

????/*?border:?2px?solid?blue;?*/

????width:?100%;

????height:?100%;

????position:?relative;

????overflow:?hidden;

????//?background:?#000;

??}

??.barrage-main-dm?{

????position:?absolute;

????width:?100%;

????height:?100%;

????left:?0;

????top:?0;

??}

}

.self-dm?{

??border:?2px?solid?#87ceeb;

??box-sizing:?border-box;

}

@keyframes?barrage-run?{

??0%?{

????//?transform:?translate3d(500px,?0,?0);

??}

??100%?{

????transform:?translate3d(-100%,?0,?0);

??}

}

@keyframes?barrage-fade?{

??0%?{

????visibility:?visible;

????//?opacity:?1;

??}

??100%?{

????visibility:?hidden;

????//?opacity:?0;

??}

}

.ani-pause?{

??&?div?{

????animation-play-state:?paused?!important;

??}

}

.ani-running?{

??&?div?{

????animation-play-state:?running?!important;

??}

}

</style>

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市叛拷,隨后出現(xiàn)的幾起案子腾窝,更是在濱河造成了極大的恐慌,老刑警劉巖盈匾,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腾务,死亡現(xiàn)場離奇詭異,居然都是意外死亡削饵,警方通過查閱死者的電腦和手機岩瘦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門未巫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人启昧,你說我怎么就攤上這事叙凡。” “怎么了密末?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵握爷,是天一觀的道長。 經常有香客問我苏遥,道長饼拍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任田炭,我火速辦了婚禮师抄,結果婚禮上,老公的妹妹穿的比我還像新娘教硫。我一直安慰自己叨吮,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布瞬矩。 她就那樣靜靜地躺著茶鉴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪景用。 梳的紋絲不亂的頭發(fā)上涵叮,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音伞插,去河邊找鬼割粮。 笑死,一個胖子當著我的面吹牛媚污,可吹牛的內容都是我干的舀瓢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼耗美,長吁一口氣:“原來是場噩夢啊……” “哼京髓!你這毒婦竟也來了?” 一聲冷哼從身側響起商架,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤堰怨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甸私,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诚些,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年皇型,在試婚紗的時候發(fā)現(xiàn)自己被綠了诬烹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡弃鸦,死狀恐怖绞吁,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情唬格,我是刑警寧澤家破,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站购岗,受9級特大地震影響汰聋,放射性物質發(fā)生泄漏。R本人自食惡果不足惜喊积,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一烹困、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乾吻,春花似錦髓梅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诡必,卻和暖如春奢方,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爸舒。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工蟋字, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碳抄。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓愉老,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剖效。 傳聞我的和親對象是個殘疾皇子嫉入,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容

  • 1. tab列表折疊效果 html: 能源系統(tǒng)事業(yè)部 崗位名稱: 工作地點 崗位名...
    lilyping閱讀 1,843評論 0 1
  • # 傳智播客vue 學習## 1. 什么是 Vue.js* Vue 開發(fā)手機 APP 需要借助于 Weex* Vu...
    再見天才閱讀 3,530評論 0 6
  • date/****js相關********************************************...
    Agping閱讀 457評論 0 1
  • 什么是組件?---組件就是對象- 寫組件的目的:減少開發(fā)成本,提供開發(fā)效率 組件是對數(shù)據和方法的簡單封裝璧尸! 特點:...
    love2013閱讀 491評論 0 0
  • ----------------------------學習工作點滴記錄---------------------...
    BelieveLong閱讀 495評論 0 0