輪播彈幕的實現

背景

在不少App的H5首頁,常常會看到頂部的輪播的消息流,相似於彈幕,展現給用戶,增長營銷感,例如某電商首頁:javascript

要實現相似的功能,該如何設計一個通用的組件?css

組件分析

  • 從彈幕展現層面,彈幕從視口外邊移動到屏幕左邊處,展現固定時間後,消失,間隔一段時間後又繼續展現另外一個彈幕
    • 定時器能夠實現固定時間間隔後更新彈幕,若是先後展現的彈幕之間有間隔,則須要區分兩種狀況:

      若是單個彈幕展現時間等於彈幕間隔時間,利用 aniamtion keyframes 50% 以後隱藏彈幕便可; 若是單個彈幕展現時間不等於彈幕間隔時間,經過 js 控制 css 的 keyframes 在W3C規範中沒法實現,可是能夠經過animation-delay + 組件銷燬重建實現;html

  • 從彈幕數據層面,彈幕信息包括用戶的頭像和一段文字信息,由後端返回
    • 若是彈幕數據來源固定,一次接口拉取全部彈幕,除非用戶刷新頁面,不然彈幕數據不變,實現簡單;
    • 若是彈幕數據動態變化,好比說電商首頁的拼單信息,通常是實時動態變化的,這時候就要考慮服務端推送彈幕信息了,即webSocket實現真正意義上的輪播彈幕;

四種類型的彈幕

單個彈幕展現時間等於彈幕間隔時間 + 固定彈幕數據

彈幕的無限循環輪播經過 CSS 動畫 animation-iteration-count: infinite,動畫總時間爲一個彈幕顯示 + 隱藏的總時間前端

彈幕展現規則以下圖所示 java

// 彈幕容器組件
<Barrage barrageList={barrageList} duration={4} />
// 彈幕渲染組件 duration 表示單個彈幕總時間
<BarrageItem barrageContent={barrageList[barrageIndex]} duration={duration} />
複製代碼

每隔 duration 時間展現下一個彈幕web

const { duration } = this.props;
this.timer = setInterval(() => {
  const { barrageList } = this.props;
  const { barrageIndex } = this.state;
  this.setState({
    barrageIndex: (barrageIndex + 1) % barrageList.length  // 這裏取模是爲了循環展現彈幕數據
  });
}, duration * 1000);
複製代碼
function BarrageItem ({ barrageContent, duration }) {
  return (
    <div className='barrage' style={{ animation: `showBarrage ${duration}s ease-in-out infinite` }}> <div className='thumb' style={{ backgroundImage: `url(${barrageContent.avatar})` }} /> <div className='text'> {barrageContent.text} </div> </div> ); } 複製代碼

CSS 動畫飛入飛出,且 動畫一直無限循環 來實現彈幕輪播npm

@keyframes showBarrage {
    0% {
        opacity: 0;
        left: -100%;
    }
    5% {
        opacity: 1;
        left: .06rem;
    }
    45% {
        opacity: 1;
        left: .06rem;
    }
    50% {
        opacity: 0;
        left: -100%;
    }
    100% {
        opacity: 0;
        left: -100%;
    }
}
複製代碼

這樣就實現一個最簡單的飛入飛出的彈幕了,效果以下: canvas

單個彈幕展現時間與彈幕間隔時間可配 + 固定彈幕數據

彈幕的無限循環輪播不經過 CSS 動畫,而是經過組件的銷燬與重建;後端

彈幕顯示時間爲動畫持續時間,彈幕間隔時間爲動畫延遲時間(由於 animation-iteration-count: infinite 狀況下,延遲時間只在首次動畫生效,後續每個動畫循環不會執行延遲效果)服務器

彈幕展現規則以下圖所示

// 彈幕容器組件
<Barrage barrageList={barrageList} showTime={3} gapTime={1} />
// 彈幕渲染組件
<BarrageItem barrageContent={barrageList[barrageIndex]} showTime={3} gapTime={1} />
複製代碼

每隔 showTime + gapTime 時間展現下一個彈幕

const { duration } = this.props;
this.timer = setInterval(() => {
  const { barrageList } = this.props;
  const { barrageIndex, showBarrage } = this.state;
  this.setState({
    barrageIndex: (barrageIndex + 1) % barrageList.length,  // 這裏取模是爲了循環展現彈幕數據
    showBarrage: !showBarrage
  });
}, (showTime + gapTime) * 1000);

// 經過組件的key不一樣來銷燬並重建組件
render () {
    const { barrageList, showTime, gapTime } = this.props;
    const { barrageIndex, showBarrage } = this.state;
    return showBarrage
      ? <BarrageItem key={'before'} barrageContent={barrageList[barrageIndex]} showTime={showTime} gapTime={gapTime} />
      : <BarrageItem key={'after'} barrageContent={barrageList[barrageIndex]} showTime={showTime} gapTime={gapTime} />;
  }
複製代碼
function BarrageItem ({ barrageContent, duration }) {
  return (
    <div className='common-barrage' style={{ animation: `showBarrage ${showTime}s ease-in-out ${gapTime}s` }}> <div className='thumb' style={{ backgroundImage: `url(${barrageContent.avatar})` }} /> <div className='text'> {barrageContent.txt} </div> </div> ); } 複製代碼

CSS 動畫飛入飛出,且 動畫一直無限循環 來實現彈幕輪播

@keyframes showBarrage {
    0% {
        opacity: 0;
        left: -100%;
    }
    10% {
        opacity: 1;
        left: 6px;
    }
    90% {
        opacity: 1;
        left: 6px;
    }
    100% {
        opacity: 0;
        left: -100%;
    }
}
複製代碼

效果以下:

服務端推送(Websocket)彈幕數據 + 彈幕間隔時間和彈幕展現時間可配

服務端推送,數據發送方爲服務端,接收方爲客戶端,服務端每隔一段時間就推送必定數量的彈幕數據給客戶端,客戶端拿到數據後更新本地彈幕數據隊列,展現順序依推入彈幕的順序執行。

首先須要瞭解下 Websocket, 參考大神的 阮一峯博客Websocket MDN

做爲一名前端,天然而然想到結合基於 Nodejs 的 WebSocket 框架 Socket.io 來實現彈幕數據的推送

彈幕數據推送方

首先基於 Nodejs 的 http 模塊和 WebSocket 框架 Socket.io 搭建一個簡單的推送服務器, 每隔一段時間往客戶端推送必定量的彈幕數據

搭建本地服務器
搭建過程參考官方文檔 [搭建基於Node HTTP服務器的Socket](https://socket.io/docs/#Using-with-Node-http-server)
  > npm init
  
  > npm install --save socket.io
複製代碼
app.js
const socket = require('socket.io');
  const http = require('http');
  
  const server = http.createServer(/** 定義一個路由處理函數 */);
  
  server.listen(8080);
  
  const io = socket(server);
  
  let timer = null;
  
  // 模擬彈幕數據
  
  io.on('connection', socket => {
    console.log('連上了');
    // 連上以後隔一段時間往客戶端推送彈幕數據,每次推送三條隨機彈幕
    timer = setInterval(() => {
      const obj = [
        {
          avatar: 'xxx1.png',
          txt: '彈幕' + Math.floor(Math.random() * 100)
        },
        {
          avatar: 'xxx2.png',
          txt: '彈幕' + Math.floor(Math.random() * 100)
        },
        {
          avatar: 'xxx3.png',
          txt: '彈幕' + Math.floor(Math.random() * 100)
        }
      ]
      socket.send(JSON.stringify(obj))  // 傳輸序列化後的字符串數據
    }, 6000)
  
    socket.on('disconnect', () => {
      console.log('斷開了');
      clearInterval(timer);
    })
  })
複製代碼
  • 注意 WebSocket中的send方法不是任何數據都能發送的,如今只能發送三類數據,包括UTF-8的string類型(會默認轉化爲USVString),ArrayBuffer和Blob,且只有在創建鏈接後才能使用

彈幕數據接收方

前端這邊經過安裝 Socket.io 的客戶端,便可監聽並接收從服務端推送過來的數據

npm install --save socket.io-client

建立並鏈接到客戶端 socket.io-client

// // Barrage.js
import io from 'socket.io-client';
const socket = io('ws://localhost:8080');

// 監聽message事件並更新本地彈幕數據
const [barrageList, getBarrageList] = useState([]);
useEffect(() => {
   socket.on('message', (data) => {
     getBarrageList(oldBarrageList => {
       console.log([...oldBarrageList, ...JSON.parse(data)])
       return [...oldBarrageList, ...JSON.parse(data)];
     });
   })
 }, []);
複製代碼

效果以下,右邊控制檯打印的是每次接收到服務端的推送彈幕後的本地數據

擴展-掃屏實時動態彈幕(相似於b站的彈幕效果)

王司徒鎮樓

視頻彈幕主要須要考慮以下幾個問題

  • 多軌道
  • 彈幕移動速度
  • 同一軌道彈幕是否能夠重疊
  • 彈幕顏色
  • 軌道上下可否重疊(彈幕位置是否隨機) 這個因爲會致使彈幕很雜亂,因此直接設置爲固定的軌道
  • 彈幕數據源

在這裏咱們考慮一種比較簡單的狀況

  • 固定軌道數

  • 彈幕移動速度固定

  • 統一軌道上彈幕不重疊

  • 彈幕顏色隨機

  • 軌道上下不重疊

  • 彈幕數據來自於服務端推送(模擬用戶輸入彈幕)

  • 最後,這裏推薦一個比較好用的基於 canvas 的視頻彈幕組件 Barrage UI

相關文章
相關標籤/搜索