前端js實現(xiàn)彈幕,怎么控制彈幕不重疊

  • 彈幕不重疊——核心訴求
  • 場景簡單——彈幕勻速運(yùn)動
  • 性能較好——避免密集的實時彈幕位置計算

針對這里的彈幕重疊問題(不妨說平面中的重疊問題)谁榜,我們可以從豎直方向水平方向兩方面來分析逛薇。

豎直方向

對于典型的彈幕場景,每個彈幕元素作水平直線運(yùn)動外里,豎直方向(即縱向)的速度分量為0邑飒。這就意味著:彈幕元素彼此之間在豎直方向沒有發(fā)生相對運(yùn)動,因此彈幕在縱向的間距可通過對容器劃分「軌道」進(jìn)行隔離级乐。

軌道好比公路上的單車道疙咸,每條單車道只允許一輛車通行。

1111.jpg

我們可以使用相同的軌道高度(如下圖中h)對彈幕的豎直方向進(jìn)行布局規(guī)劃风科。通過所在軌道編號軌道高度的乘積計算撒轮,可獲得每條彈幕距離容器頂部的偏移量(top值)乞旦。

2222.jpg

原理上,軌道高度并不一定要大于彈幕高度题山,取較小的分度值也是沒有問題的兰粉。軌道可以是一個承載彈幕對象的隊列,本身并不需要展示為DOM實體顶瞳。

水平方向

在上面的討論中玖姑,通過引入軌道的概念,避免了豎直方向上&不同軌道之間的彈幕重疊干擾慨菱。對于水平方向的分析焰络,我們不妨先從簡單的單個彈幕元素看起來。

每個彈幕元素會經(jīng)歷這樣三個階段:

  1. 出生在容器右側(cè)——掛載
  2. 進(jìn)入到容器中——運(yùn)動
  3. 完全離開容器——移除


    33333.jpg

在這里符喝,我們需要重新審視下運(yùn)動的主人公——彈幕君:每一條默默劃過的彈幕闪彼,實際上包含空間時間兩個維度的描述:

  • 空間維度:包括彈幕君的一些包括寬高、相對位置等幾何屬性协饲;

  • 時間維度

  • startTime 通常用于記錄用戶生成該彈幕的時刻畏腕,這是一個相對開始播放的時間偏移量。該屬性指導(dǎo)了彈幕元素的默認(rèn)出現(xiàn)時機(jī)茉稠,以及在候選隊列中的出場次序描馅。

  • duration,彈幕在容器中飄過的持續(xù)時間而线。由于彈幕場景中每個彈幕元素的水平位移量是固定的流昏,因此 duration 也間接決定了彈幕的運(yùn)動速度。

基于數(shù)據(jù)的視角吞获,一條彈幕運(yùn)動相關(guān)的信息可以表達(dá)如下:

// ts表述
interface bullet {
    width   : number,   // 彈幕元素寬度
    height  : number,   // 彈幕元素高度
    top     : number,   // 彈幕元素距離容器頂部的偏移量
    left    : number,   // 彈幕元素距離容器左邊緣的偏移量况凉,通常用于設(shè)定彈幕元素初始位置
    startTime : number,   // 彈幕元素相對于開始播放時刻的出現(xiàn)時機(jī)
    duration: number,   // 彈幕元素的展示持續(xù)時間
}

具象地說,這里的lefttop指定了彈幕元素的出生點位各拷;widthheight標(biāo)識了彈幕元素的高矮胖瘦刁绒;startTimeduration則分別決定了彈幕元素在候選列表中的順序和展示時長。

下面用原生 js 簡易描述一條彈幕運(yùn)動的配置:

// container 表示彈幕展示的容器
const containerPos = container.getBoundingClientRect();
const {
    left: containerLeft,
    width: containerWidth
} = containerPos;

// bullet 表示候選彈幕對象(下同)
const { width, right } = bullet.getBoundingClientRect();

// 計算彈幕移動速度
const moveV = (containerWidth + width) / duration;

// 彈幕需要移動的距離
const leftDistance = right - containerLeft;

// 彈幕剩余跑完時間(單位s)
const leftDuration = leftDistance / moveV;

// 彈幕參照容器定位
bullet.style.position = 'absolute';

// 彈幕出生點位(水平偏移量)
bullet.style.left = `${containerWidth}px`;

// pos是當(dāng)前彈幕元素被分配到的軌道編號烤黍,channelHeight表示軌道高度
bullet.style.top = `${pos * channelHeight}px`;

// 彈幕水平向左運(yùn)動
bullet.style.transition = `transform ${leftDuration}s linear 0s`;
bullet.style.transform = `translateX(-${right - containerLeft}px)`;

通過以上分析可知知市,由于容器彈幕的寬高屬性是確定的,涉及彈幕的水平方向出生點位和速度也就被間接確定了速蕊。

這就意味著:一旦彈幕掛載到軌道中嫂丙,即可進(jìn)入預(yù)置的運(yùn)動狀態(tài)。

調(diào)度策略

設(shè)想规哲,如果存在一個調(diào)度策略跟啤,能夠智能地將彈幕分配至合適的軌道,在彈幕掛載階段已經(jīng)完成對彈幕重疊的檢測——那么就無需引入復(fù)雜的物理引擎或者是實時碰撞計算了,awesome~

那么隅肥,如何設(shè)計這個調(diào)度策略呢竿奏?

回到典型的彈幕場景:同一軌道中所有彈幕元素從右向左&同向勻速運(yùn)動;在展示期間腥放,軌道中所有彈幕元素均不發(fā)生重疊泛啸。

不難想到,「軌道中所有彈幕元素均不發(fā)生重疊」的問題可以歸約為:「如何避免軌道中前后兩個相鄰彈幕彈幕元素之間的重疊」秃症。

44444.jpg

試想候址,軌道中前后彈幕元素之間互不重疊,那么整條軌道所有彈幕也就確保彼此不會重疊了种柑。例如上圖中岗仑,彈幕君-2彈幕君-1彈幕君-3彈幕君-2在移動期間互不重疊莹规;對于即將進(jìn)入軌道的彈幕君-4赔蒲,只需要保證其與彈幕君-3保持移動期間不重疊泌神,則整個系統(tǒng)便可滿足互不重疊的狀態(tài)良漱。

于是我們成功地將一系列彈幕間的運(yùn)動關(guān)系,降維到了相鄰彈幕元素的兩兩關(guān)系欢际。

追及問題

判斷兩個彈幕在水平方向上是否發(fā)生重疊母市,實質(zhì)就是就是對追及問題進(jìn)行討論了。

基于紅領(lǐng)巾時代掌握的知識损趋,我們知道:對于兩個對象的勻速直線運(yùn)動患久,通過公式路程差 / 速度差 = 追及時間來判斷對象是否會相遇(追及時間是否大于0)。

這里的「相遇」浑槽,與彈幕場景中「重疊」的概念不謀而合蒋失。

實際操作中,對于一個尋求某條合適軌道的彈幕元素桐玻,只需要將其與軌道中最后加入的彈幕元素(即軌道中最右側(cè)的彈幕元素)進(jìn)行比較篙挽,通過兩者的追及問題計算,便可判斷該軌道是否滿足當(dāng)前候選彈幕的插入條件镊靴。

代碼簡單示意如下:

checkChannel() {
    // containerWidth铣卡、containerLeft等,表示容器相關(guān)屬性
    // bullet:表示候選彈幕元素對象
    // channel:表示軌道偏竟,存儲進(jìn)入軌道的彈幕對象
    const lastPos = channel.length - 1;
    const lastBullet = channel[lastPos];
    if (lastBullet) {
        const lastBulletPos = lastBullet.getBoundingClientRect();

        // 軌道中最后一個元素要求已經(jīng)全部進(jìn)入展示區(qū)域
        if (lastBulletPos.right > containerRight) {
            return false;
        }

        // 基本公式:s = v * t
        const lastS = lastBulletPos.left - containerLeft + lastBulletPos.width;
        const lastV = (containerWidth + lastBulletPos.width) / lastBullet.duration;
        const lastT = lastS / lastV;

        const newS = containerWidth;
        const newV = (containerWidth + bullet.width) / bullet.duration;
        const newT = newS / newV;

        // 追及問題
        if (lastV < newV && lastT > newT) {
            return false;
        }
    }

    return true;   
}

經(jīng)過上面checkChannel()的計算煮落,如果返回true則表示當(dāng)前軌道可以接受候選彈幕的掛載。

最終彈幕元素是否能夠掛載到某條軌道上踊谋,還取決于其他因素:若彈幕本身高度較高&需要跨越多個軌道蝉仇,那么彈幕需要對相關(guān)干涉到的軌道進(jìn)行巡檢后,才能確定是否使用該軌道作為自己的掛載目標(biāo)。

綜上量淌,調(diào)度策略可簡要描述如下:

周期性巡檢軌道并嘗試插入候選彈幕元素骗村,檢查候選元素與軌道之間是否滿足掛載條件,包括:

  1. 溢出檢查:候選彈幕元素高度不超過所有軌道之和呀枢;
  2. 重復(fù)性檢查:候選彈幕元素尚未出現(xiàn)在軌道中胚股;
  3. 重疊檢查:候選彈幕元素,與所巡檢的軌道及其下方被占用的軌道(如果彈幕高度較高)中最右側(cè)的彈幕元素不發(fā)生重疊

滿足以上全部條件的軌道方可作為候選彈幕掛載裙秋。

小結(jié)

通過對平面中豎直和水平方向的分析琅拌,我們將寬泛的彈幕重疊問題,收斂為軌道中相鄰彈幕兩兩之間的追及問題摘刑,最終獲得了將候選彈幕掛載到合適軌道中的調(diào)度策略进宝。

在以上討論中,我們側(cè)重呈現(xiàn)一個問題分解的過程枷恕,并未過多地深入到代碼實現(xiàn)的細(xì)節(jié)党晋,希望能給到大家一些有益的啟發(fā)哈。

原文鏈接:https://www.zhihu.com/question/370464345/answer/1021530502

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末徐块,一起剝皮案震驚了整個濱河市未玻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胡控,老刑警劉巖扳剿,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昼激,居然都是意外死亡庇绽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門橙困,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞧掺,“玉大人,你說我怎么就攤上這事凡傅”俦罚” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵像捶,是天一觀的道長上陕。 經(jīng)常有香客問我,道長拓春,這世上最難降的妖魔是什么释簿? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮硼莽,結(jié)果婚禮上庶溶,老公的妹妹穿的比我還像新娘煮纵。我一直安慰自己,他們只是感情好偏螺,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布行疏。 她就那樣靜靜地躺著,像睡著了一般套像。 火紅的嫁衣襯著肌膚如雪酿联。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天夺巩,我揣著相機(jī)與錄音贞让,去河邊找鬼。 笑死柳譬,一個胖子當(dāng)著我的面吹牛喳张,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播美澳,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼销部,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了制跟?” 一聲冷哼從身側(cè)響起舅桩,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凫岖,沒想到半個月后江咳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逢净,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哥放,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了爹土。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甥雕。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胀茵,靈堂內(nèi)的尸體忽然破棺而出脯燃,到底是詐尸還是另有隱情萝风,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站引几,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏步咪。R本人自食惡果不足惜侣诺,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望熄浓。 院中可真熱鬧情臭,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至跷乐,卻和暖如春肥败,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愕提。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工拙吉, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揪荣。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓筷黔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仗颈。 傳聞我的和親對象是個殘疾皇子佛舱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容