轮播弹幕的实现

背景

在不少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

相关文章
相关标签/搜索