前言目前视频播放平台的弹幕几乎都是通过js操作dom来实现的。由于篇幅原因,这次只展示js操作dom的实现。下面的代码展示了react16.2版本库的使用。可以在npm中安装page-construct-template_component_barrage插件直接使用。文本函数子弹文本多种样式:字体大小、字体类型、字体颜色(字体透明度)子弹显示速度子弹行高子弹事件:鼠标左右点击事件、鼠标滑入滑出事件调用方法如下:constdiv=document.createElement('div');constdiv.innerText='helloword';div.style.color='orange';div.syle.fontSize='20px';<弹幕数据={[{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,设置属性,插入页面转场动画,删除子弹聊天室。.初始项目这一步要做的事情是:创建一个弹幕容器,给弹幕容器添加一个监听器。我们将所有弹幕节点的监听事件委托给弹幕容器节点来减少内存占用。弹幕容器的宽高存储在stateimportReact,{Component}from'react';//弹幕之间的最小距离constbarrageAway=30;exportdefaultclassextendsComponent{//容器宽高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=>{constcontainer=this.refs.container;constfn=typeofcb==='函数'?cb:()=>{};如果(!isDom(容器)){返回;}this.setState({宽度:container.clientWidth,高度:container.clientHeight},fn);}init=()=>{/*初始行、初始弹幕信息*/}getIdleRow=()=>{/*获取空闲行*/}getAwayRight=()=>{/*获取元素与元素之间的距离容器的右边框*/}draw=>()=>{/*渲染弹幕元素*/}handleTransitionEnd=e=>{/*删除dom*/}handleClick=()=>{/*dosomething*/}handleContextMenu=()=>{/*做某事*/}handleMouseOver=()=>{/*做某事*/}handleMouseOut=()=>{/*做某事*/}render(){return(//弹幕容器);}}初始化弹幕信息需要运行的任务是:初始化弹幕显示行数的初始弹幕信息(需要判断对象是否为dom节点)constdefaultFont={fontSize:16,speed:[1,3],color:'#000',fontFamily:'microsoftyahei'};//function上面标记的位置init=()=>{const{data,lineHeight,font}=this.道具;const{height}=this.state;过滤器(字体,[空,未定义]);//计算行数if(parseInt(height/lineHeight,10)>this.rowArr.length){//增加可显示行数for(leti=0;i{//属性优先级如下:弹幕对象中定义>全局定义>默认样式letbarrage=item;//if弹幕对象是dom节点if(isDom(item)){barrage={domContent:item,speed:item.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{return;}constrandomRow=Math.floor(Math.random()*this.rowArr.length);//随机发现行空闲if(this.rowArr[randomRow].idle||this.getAwayRight(this.rowArr[randomRow].dom)>=barrageAway){returnrandomRow;}//随机找到的行被占用letincrease=randomRow+1;//向后查找空闲行while(increase=barrageAway){返回增加;}增加++;}//向前查找空闲行letdecrease=randomRow-1;while(decrease>-1){if(this.rowArr[decrease].idle||this.getAwayRight(this.rowArr[decrease].dom)>=barrageAway){返回减少;}减少-;}//当前没有空行容器return;}//获取弹幕dom与容器右边框的距离getAwayRight=dom=>{constcontainer=this.refs.container;const{width}=this.state;constcontainerRect=container.getBoundingClientRect();constdomRect=dom.getBoundingClientRect();返回containerRect.left+width-domRect.left-dom.offsetWidth;是的,您可以创建一个dom。如果没有,则跳出循环,等待下一次创建和设置dom属性。弹幕dom写入弹幕容器,设置transition和tranform。这里我们使用translate代替left将元素移动到容器的最左边,同时开启硬件加速和减少页面回流和重绘,提高性能draw=()=>{const{lineHeight}=this.props;const{width}=this.state;for(const_inthis.barrageList){constbarrage=this.barrageList.shift();const{text,fontSize,color,fontFamily,speed}=弹幕;constidleRowIndex=this.getIdleRow();//获取空闲行//确定它是否可用If(idleRowIndex===undefined){break;}constrandomAway=Math.floor(Math.random()*width/2);//随机初始弹幕与右边框的距离使得弹幕错位//普通弹幕dom,开发者传入的dom节点也存放在这个dom中constdiv=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=颜色;div.style.transform=`translate3d(${width+randomAway}px,0,0)`;div.style.position='绝对';div.style.left=0;div.style.top=`${idleRowIndex*lineHeight}px`;//根据空闲行,计算对应的top值//将弹幕dom插入弹幕容器this.refs.container.appendChild(div);this.rowArr[idleRowIndex]={dom:div,空闲:假};//这一行改为非空闲状态//计算弹幕动画constdivWidth=div.offsetWidth;construnTime=(width+divWidth)/(60*speed);//弹幕显示需要多长时间时间div.style.transform=`translate3d(${-divWidth}px,0,0)`;div.style.transition=`转换${runTime}s线性`;}//没有空线,需要等待100ms再渲染if(this.barrageList.length){this.timer=setTimeout(this.draw,100);}}删除弹幕dom当弹幕显示完成后,我们需要从页面中移除对应的弹幕dom。之前弹幕动画使用了transition,所以我们可以监听transitionend事件handleTransitionEnd=>{this.refs.container.removeChild(e.target);}数据更新之前的实现只能显示第一个传入的数据,弹幕后面传入的数据无法显示。这里我们使用shouldComponentUpdateapi来存储新的弹幕数据,对之前的init函数做简单的修改。shouldComponentUpdate(nextProps){if(nextProps.data!==this.props.data){constlength=this.barrageList.length;this.init(nextProps);if(length===0){this.draw();}}returntrue;}init=nextProps=>{const{data,lineHeight,font}=nextProps||this.props;}以便显示来电弹幕。结束语以上基本完成了一个简单的弹幕功能。由于空间问题,还有许多扩展没有完成或没有显示。比如:当弹幕很多的时候,我们如何控制弹幕的速度?屏幕显示位置