网页弹幕展示

网页弹幕展示

前言

目前视频播放平台弹幕几乎都是使用js操作dom的方式实现,由于篇幅的原因这次只展示js操作dom的实现方案。

下文代码展示使用的是react 16.2版本库。

大家可以在npm中安装page-construct-template_component_barrage这个插件来直接使用。

正文

功能

弹幕文字各种样式:字体大小、字体类型、字体颜色(字体透明度)

弹幕展示速度

弹幕行高度

弹幕事件:鼠标左右点击事件、鼠标滑入滑出事件

调用方式如下:

const div = document.createElement('div');

const div.innerText = 'hello word';

div.style.color = 'orange';

div.syle.fontSize = '20px';

data={[

{

text: 'hello'

},

{

text: 'word',

// 控制单个弹幕元素的样式

color: 'rgba(255, 255, 255, 0.7)',

speed: [3, 4]

},

div

]}

fontSize={25} // 弹幕字体大小

lineHeight={40} // 弹幕行高

speed={[1, 2]} // 控制弹幕速度

onMouseOver={}

onMouseOut={}

/>

js+dom实现方案

在开始正式代码开发之前需要弄清楚这种方法实现的逻辑:

首先我们需要创建一个容器来承载弹幕元素,将监听函数写到这个容器上面

初始化弹幕信息(弹幕内容、样式、速度,同时判断对象是否是dom节点)、初始弹幕容器能够显示多少行

创建弹幕dom,设置属性,插入页面

transition动画结束,删除弹幕dom

基本流程就是上面这几步了,下面我们进入每一步的程序编写。

初始项目

这一步要做的事情有:

创建弹幕容器

向弹幕容器添加监听器,我们将所有弹幕节点的监听事件都委托到弹幕容器节点上面,减少内存占用

弹幕容器宽高存入state

import React, { Component } from 'react';

// 弹幕之间的最小距离

const barrageAway = 30;

export default class extends Component {

// 容器宽高

state = {

width: 0,

height: 0

}

barrageList = [] // 弹幕元素信息

rowArr = [] // 容器可以展示弹幕的行

timer = null // 存放定时器

componentDidMount() {

this.setSize(() => {

// 后面再展示这两个回调函数代码

this.init();

this.draw();

});

// 弹幕容器大小发生改变一般事因为屏幕大小改变导致的

window.addEventListener('resize', this.setSize);

}

componentWillUnmount() {

clearTimeout(this.timer);

window.removeEventListener('resize', this.setSize);

}

// 获取弹幕容器的宽高

setSize = cb => {

const container = this.refs.container;

const fn = typeof cb === 'function' ? cb : () => {};

if (!isDom(container)) {

return;

}

this.setState({

width: container.clientWidth,

height: container.clientHeight

}, fn);

}

init = () => {/*初始行、初始弹幕信息*/}

getIdleRow = () => {/*获取空闲行*/}

getAwayRight = () => {/*获取元素距离容器右边框的距离*/}

draw => () => {/*渲染弹幕元素*/}

handleTransitionEnd = e => {/*delete dom*/}

handleClick = () => {/*do something*/}

handleContextMenu = () => {/*do something*/}

handleMouseOver = () => {/*do something*/}

handleMouseOut = () => {/*do something*/}

render() {

return (

// 弹幕容器

ref="container"

onTransitionEnd={this.handleTransitionEnd}

onClick={this.handleClick}

onContextMenu={this.handleContextMenu}

onMouseOver={this.handleMouseOver}

onMouseOut={this.handleMouseOut}

style={{

position: 'absolute',

width: '100%',

height: '100%',

backgroundColor: 'rgba(0, 0, 0, 0)',

overflow: 'hidden',

transform: 'translateZ(0)'

}}

/>

);

}

}

初始化弹幕信息

需要运行的任务有:

初始化弹幕展示行数

初始弹幕信息(需要判断对象是否是dom节点)

const defaultFont = {

fontSize: 16,

speed: [1, 3],

color: '#000',

fontFamily: 'microsoft yahei'

};

// 函数位置上面有标明

init = () => {

const { data, lineHeight, font } = this.props;

const { height } = this.state;

filter(font, [null, undefined]);

// 计算行数

if (parseInt(height / lineHeight, 10) > this.rowArr.length) {

// 可展示行数增加

for (let i = 0; i < parseInt(height / lineHeight, 10) - this.rowArr.length; i++) {

this.rowArr.push({ idle: true }); this.rowArr用来存放行容器是否空闲,以及当前行末尾元素

}

} else {

// 可展示行数减少

this.rowArr.splice(-1, this.rowArr.length - parseInt(height / lineHeight, 10));

}

// 初始化弹幕信息

data.forEach(item => {

// 属性优先级如下:弹幕对象中定义 > 全局定义 > 默认样式

let barrage = item;

// 如果弹幕对象是一个dom节点

if (isDom(item)) {

barrage = {

domContent: item,

speed: item.speed || font.speed || defaultFont.speed

};

// 开发者传入的是普通对象

}

barrage = {

...defaultFont,

...font,

...item,

...barrage

};

barrage.speed = Math.random() * (barrage.speed[1] - barrage.speed[0]) + barrage.speed[0]; // 随机速度,让弹幕元素错开

this.barrageList.push(barrage); // this.barrageList 用来存放弹幕信息列表

});

}

创建弹幕dom

需要执行的任务有:

随机获取空闲行

随机一个行数,判断该行是否可以插入新的弹幕

可以使用,就将该行行数返回

不可以使用,就向后继续寻找可以使用的行

找到了就返回对应的行数

没找到,找随机行前面是否有可用的行,有就返回对应行数,没有就返回undefined

// 获取空闲行

getIdleRow = () => {

if (this.rowArr.length === 0) {

return;

}

const randomRow = Math.floor(Math.random() * this.rowArr.length);

// 随机找到的行为空闲

if (this.rowArr[randomRow].idle || this.getAwayRight(this.rowArr[randomRow].dom) >= barrageAway) {

return randomRow;

}

// 随机找到的行被占用

let increase = randomRow + 1;

// 向后查找空闲的行

while (increase < this.rowArr.length) {

if (this.rowArr[increase].idle || this.getAwayRight(this.rowArr[increase].dom) >= barrageAway) {

return increase;

}

increase++;

}

// 向前查找空闲的行

let decrease = randomRow - 1;

while (decrease > -1) {

if (this.rowArr[decrease].idle || this.getAwayRight(this.rowArr[decrease].dom) >= barrageAway) {

return decrease;

}

decrease--;

}

// 目前没有空闲的行容器

return;

}

// 获取弹幕dom距离容器右边框的距离

getAwayRight = dom => {

const container = this.refs.container;

const { width } = this.state;

const containerRect = container.getBoundingClientRect();

const domRect = dom.getBoundingClientRect();

return containerRect.left + width - domRect.left - dom.offsetWidth;

}

创建弹幕dom

需要判断是否有可用的行

有,就可以创建dom

没有,就跳出循环,等下一次再来创建

设置dom属性

弹幕dom写入弹幕容器中

设置transition、tranform

这里我们使用translate替换left将元素移动到容器最左边,同时开启硬件加速减少页面重排重绘,提高性能

draw = () => {

const { lineHeight } = this.props;

const { width } = this.state;

for (const _ in this.barrageList) {

const barrage = this.barrageList.shift();

const { text, fontSize, color, fontFamily, speed } = barrage;

const idleRowIndex = this.getIdleRow(); // 获取一个空闲行

// 判断是否有可用的行

if (idleRowIndex === undefined) {

break;

}

const randomAway = Math.floor(Math.random() * width / 2); // 随机初始弹幕距离右边框距离,让弹幕错位

// 常见弹幕dom,开发者传入的dom节点也存放到这个dom中

const div = document.createElement('div');

if (!barrage.domContent) {

div.innerText = text;

} else {

div.appendChild(barrage.domContent);

}

// 设置弹幕样式

div.style.fontSize = `${fontSize}px`;

div.style.fontFamily = fontFamily;

div.style.color = color;

div.style.transform = `translate3d(${width + randomAway}px, 0, 0)`;

div.style.position = 'absolute';

div.style.left = 0;

div.style.top = `${idleRowIndex * lineHeight}px`; // 根据空闲的行,计算对应的top值

// 将弹幕dom插入弹幕容器中

this.refs.container.appendChild(div);

this.rowArr[idleRowIndex] = { dom: div, idle: false }; // 该行改成非空闲状态

// 计算弹幕动画

const divWidth = div.offsetWidth;

const runTime = (width + divWidth) / (60 * speed); // 弹幕展示完需要多少时间

div.style.transform = `translate3d(${-divWidth}px, 0, 0)`;

div.style.transition = `transform ${runTime}s linear`;

}

// 没有空闲行,需要等100ms再渲染

if (this.barrageList.length) {

this.timer = setTimeout(this.draw, 100);

}

}

删除弹幕dom

当弹幕展示完成以后我们需要将对应的弹幕dom从页面中移除,之前弹幕动画借助的是transition,因此我们可以通过监听transitionend事件

handleTransitionEnd = e => {

this.refs.container.removeChild(e.target);

}

数据更新

前面实现只能展示第一次传入的数据,对于后面再传入的弹幕数据就不能展示出来,我们这里使用shouldComponentUpdate这个api将新的弹幕数据存入,并对之前的init函数做简单的修改。

shouldComponentUpdate(nextProps) {

if (nextProps.data !== this.props.data) {

const length = this.barrageList.length;

this.init(nextProps);

if (length === 0) {

this.draw();

}

}

return true;

}

init = nextProps => {

const { data, lineHeight, font } = nextProps || this.props;

}

这样之后的传入的弹幕就能够展示了。

结语

以上就基本完成了一个简单的弹幕功能,这里还有很多拓展还没有做或者由于篇幅问题没有展示,例如:

弹幕很多的时候我们如何控制弹幕速度

弹幕停止运动

屏幕变化如何控制弹幕显示的位置

相关创意

红米3电池坏哪里换
365bet娱乐场官网备用

红米3电池坏哪里换

📅 07-27 👁️ 3130
ns上的幻想国物语不错
365bet娱乐场官网备用

ns上的幻想国物语不错

📅 07-02 👁️ 1030