美文网首页
弹幕组件

弹幕组件

作者: skoll | 来源:发表于2021-02-03 11:38 被阅读0次

    useDanmu

    import React,{useRef} from 'react'
    import {DanmuOptions} from './danmuConfig'
    import Danmu from './danmu'
    export default function useDanmu(options:DanmuOptions){
        let el:React.ReactElement<any>
        let ref=useRef<any>()
        // ref这里好像不行,还需要一种别的操作
        el=<Danmu options={options}/>
        
        return {
            el:el,
            ref:ref,
        }
    }
    

    danmuOptions

    // 所有和弹幕有关的定义都在这里
    
    // 弹幕类型
    export enum DanmuTypes{
        TopStatic,
        // 0顶部弹幕
        BottomStatic,
        // 1底部弹幕
        TopReverse,
        // 2逆向弹幕
        CenterStatic,
        // 3居中弹幕
        AnimatedPOsition,
        // 4定位弹幕
        Svg,
        // 5移动路径是svg
        Subtitles,
        // 6字幕弹幕
        Loading,
        // 7loading自定义动画
        Basic,
        // 8最基本的弹幕
        // Self,
        // 9自己发的弹幕
        SvgText,
        // 10svg文字
        TextPicture,
        // 11 文字图画,会占多行的操作:最大是5行吧,大于这个不让发
    }
    
    
    // 每条弹幕的属性
    export interface DanmuProperties{
        easing:any,
        // 时间曲线
        mode:number,
        // 弹幕类型
        stime:number,
        // 相对于视频位置开始时间
        text:string,
        // 表示弹幕的文字
        // duration:number,
        // 这个不传了,根据最基本的默认速度,然后和字数长度+倍速计算,以及前方弹幕的长度
        
        // 弹幕总的生存时间
        pool:number,
        // 表示弹幕池编号,用于删除弹幕时寻找所在的弹幕池
        animes:[],
        // 动画数组
        style:{
            // size
            // color
            // border
            // shadow
            // font
            // ...
        },
        // 所有的style样式
        className:string,
        svgPath:string,
        id?:string,
        // 弹幕的唯一id
        writer?:string[],
        // 编写弹幕的id,允许很多玩家发一样的弹幕
    }
    
    type SN = string | number
    // 同时支持字符串和数字
    // 对于rotate来说,数字180代表下一次旋转180度:表示的是最后结果
    // 字符串"+=2turn" 表示每次都以2度的频率变化:表示的是运动趋势 
    
    // 每一帧动画的属性
    export interface anime{
        // css属性等
        left:number,
        color:string,
        borderRadius:string,
        backgroundColor:string,
    
        // transform属性
        translateX:number,
        translateY:number,
        translateZ:number,
        rotate:SN,
        rotateX:number,
        rotateY:number,
        rotateZ:number,
        scale:number,
        scaleX:number,
        scaleY:number,
        scaleZ:number,
        skew:number,
        skewX:number,
        skewY:number,
        perspective:number,
    
        // 以上是所有可以选择的,可以支持变化的动画
    
        // 每一帧还可以一下基础参数。
        duraion:number,
        delay:number,
        // 延迟
        enDelay:number,
        // 末端延迟
        easing:string,
    
    
    }
    
    // 初始化弹幕需要传入的参数
    export interface DanmuOptions{
        type?:string,
        // 弹幕类型,全屏,显示一半
        limit?:number,
        // 最大弹幕数量
        width:number,
        height:number,
        list:Object,
        away?:number,
        // 弹幕之间左右最小间距
        lineHeight:number,
        // 弹幕之间上下最小间距
    
        // 未实现的功能
        device?:string,
        // 设备类型,pc端还是移动端
        colors?:string[],
        // 弹幕随机数组
        calssName?:string,
        // 普通弹幕class
        myDanmuClassName?:string,
        // 我发的弹幕的class
        centerDanmuClassName?:string,
        // 一直居中显示的弹幕的class
        sendIfStopTips?:string,
        // 不能发弹幕的时候给的tips:一般实弹幕已经停止或者关闭的时候\
        sendIfFirstTips?:string,
        // 如果用户发送弹幕的速度太快的时候,发布弹幕会失败的提醒  
    }
    
    export interface SvgText{
        colors:string[],
        text:string[],
    }
    
    export interface TextPicture{
        mode:number,
        value:string,
        width:number,
        height:number,
    }
    
    export interface TextOptions{
        type:string,
        // 1:正常的文字,普通文本
        // 2:picture,需要走图片转化字符画
        // 3:svgText,
        // 4:loading跳舞块,
        value:any[],
        
    }
    

    danmu.tsx

    import React,{useState,useEffect,useCallback,useRef,useMemo} from 'react'
    import useInterval from './useInterval'
    import DanmuItem from './danmuItem'
    
    export default function Danmu(props:any){
        const options=props.options
        const allObject=useMemo(()=>{
            // console.log('list',props.options)
            return options.list
        },[options.list])
        // 全部弹幕做一个缓存
    
        const [time,setTime]=useState(0)
        // 当前弹幕执行的时间:
    
        const [state,setState]=useState<any>([])
        const [isStart,setStart]=useState(true)
        // 是否开始播放弹幕,一般播放视频的时候操作
    
        const [speed,setSpeed]=useState<any>(1000)
        // 当前弹幕播放的速度,一秒一更,如果倍速播放的话这里应该要动态调整
        // 设置为null,一开始不自动播放
        
        const [isShow,setShow]=useState(true)
        // 是否显示弹幕:用户屏蔽弹幕就显示这个,还在跑定时器,但是不set数据
    
        const [rowArr,setRowArr]=useState<any>([])
        // 容器可以展示弹幕的行
    
        const widthSpeed=useState(1)
        // 根据当前的宽度来对弹幕的速度进行最后的调整
    
        const needShowDanmu=useRef<any>({})
    
        const timeRef=useRef<any>()
        const arrRef=useRef<any>([])
        
    
        const [index,setIndex]=useState(0)
    
        const handleAdd=useCallback(()=>{
            // 自己发的弹幕,不受任何限制
    
            var date=new Date().toLocaleTimeString()
            arrRef.current.push({
                index:index,
                value:date,
                mode:8,
                duration:4000,
            })
    
            // 添加svg测试弹幕
            // arrRef.current.push({
            //     index:index,
            //     value:'svg弹幕',
            //     mode:5,
            //     svgPath:'M382,233 C382,233,381.75,234,382,233 C382.25,232,382.5,230.5,383,229 C383.5,227.5,383.25,228.25,384,227 C384.75,225.75,385.5,225.75,386,224 C386.5,222.25,385.75,222.25,386,220 C386.25,217.75,386,217.25,387,215 C388,212.75,388.75,213.25,390,211 C391.25,208.75,390.75,208.25,392,206 C393.25,203.75,393.75,204.25,395,202 C396.25,199.75,395.5,199.25,397,197 C398.5,194.75,399.25,194.75,401,193 C402.75,191.25,401.75,191.75,404,190 C406.25,188.25,407.5,187.5,410,186 C412.5,184.5,411.75,185,414,184 C416.25,183,416.25,182.5,419,182 C421.75,181.5,422.25,182.25,425,182 C427.75,181.75,427.5,181.25,430,181 C432.5,180.75,432.25,181,435,181 C437.75,181,438.25,181,441,181 C443.75,181,443,181,446,181 C449,181,450.25,181,453,181 C455.75,181,454.75,181,457,181 C459.25,181,458.25,180,462,181 C465.75,182,467.75,183,472,185 C476.25,187,475,186.75,479,189 C483,191.25,484.25,192,488,194 C491.75,196,490.75,194.75,494,197 C497.25,199.25,496.75,200,501,203 C505.25,206,507,207,511,209 C515,211,513.75,209.25,517,211 C520.25,212.75,520.25,213.75,524,216 C527.75,218.25,528,218,532,220 C536,222,535.75,221.5,540,224 C544.25,226.5,544.75,227.75,549,230 C553.25,232.25,553.25,231.5,557,233 C560.75,234.5,560,234.25,564,236 C568,237.75,569,238.75,573,240 C577,241.25,576.25,240.25,580,241 C583.75,241.75,583.25,241.5,588,243 C592.75,244.5,593.75,245.75,599,247 C604.25,248.25,604,247.75,609,248 C614,248.25,614.5,247.75,619,248 C623.5,248.25,622.25,248.75,627,249 C631.75,249.25,633,249,638,249 C643,249,642.5,249,647,249 C651.5,249,652,249,656,249 C660,249,658.25,250,663,249 C667.75,248,669,246.5,675,245 C681,243.5,680.75,244.5,687,243 C693.25,241.5,693.75,241,700,239 C706.25,237,706.25,237.25,712,235 C717.75,232.75,717.75,232,723,230 C728.25,228,728,229.25,733,227 C738,224.75,738.5,223.75,743,221 C747.5,218.25,747.25,218.75,751,216 C754.75,213.25,755,212.5,758,210 C761,207.5,760.5,208,763,206 C765.5,204,766.75,203,768,202'
            // })
    
            // 添加svgText测试弹幕
            // arrRef.current.push({
            //     mode:10,
            //     colors:["#f44336","#e91e63","#9c27b0","#2196f3","#ffeb3b"],
            //     texts:["我","想","舞","天"],
            // })
    
            // 添加多行字符画
            // arrRef.current.push({
            //     mode:11,
            //     value:'<p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;!&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;!&$;&ensp;&;o&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;$$oo$$*!!!&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;;;o$o;;$$o$!!$**oo&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;!&ensp;&ensp;&ensp;&ensp;!$$$o$oo**o**$&*!;o!&ensp;&ensp;o&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;;oooo$$$o&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;!*$*$$$$&***&**o$ooo&ensp;&ensp;&ensp;&ensp;;o$$$$&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;$*ooo**$*oooo**$##&&&&&&&&&$$oo*$o*&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;!oo**$*ooo!o;o*o$$oo!ooo&ensp;;o;;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;o$!&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;**o$$$$*ooo&oo*$$$oo!ooooo;;ooo***&$$$#&&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;$&&$&&&#&$&&&#&$*ooo*oo$&&$$$&&&&&$&&&;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;$$&$$&$*&$$&&$&&&$$&&$&&o#&$&ensp;&&&&&&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;!!!!!!!!!;;!;;;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p><p>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;</p>',
            //     width:306,
            //     height:164,
            // })
    
    
            setIndex((e)=>e+1)
            // 第二个push的时候,会导致之前的也被重新加载计算
    
            // let arr=state.slice()
            // arr.push({
            //     index:index,
            //     value:date,
            //     mode:8,
            // })
            // setState(arr)
        },[state,index])
    
        const handleRemove=function(index:number){
            if(!arrRef.current)return
            for(let i=0;i<arrRef.current.length;i++){
                if(index===arrRef.current[i].index){
                    arrRef.current.splice(i,1)
                    setState((e: any)=>e+=1)
                }
            }
        }
    
        const handleToggleFalse=function(index:number){
            let newArr=rowArr.slice()
            newArr[index]['idle']=false
            setRowArr(newArr)
        }
    
        const handleToggleTrue=function(index:number,time:number,speed:number){
            let newArr=rowArr.slice()
            newArr[index]['idle']=true
            newArr[index]['time']=time
            newArr[index]['speed']=speed
            setRowArr(newArr)
        }
    
        const danmuContainerStyle={
            width:`${options.width}px`,
            height:`${options.height}px`,
            border:'1px solid red'
        }
    
        // 返回一个空闲行
        function getIdle(){
            if(rowArr.length===0)return
    
            // 如果真的都占满了怎么说,也就是一秒之内10条都满了。
            for(let i=0;i<rowArr.length;i++){
                if(rowArr[i]['idle']){
                    console.log(rowArr[i]['idle'],i)
                    return i
                }
            }
            return -1
            // 表示没有空位,之后的数据需要放入寄存数组
        }
        function getIdleValue(index:number){
            return rowArr[index]
        }
    
        useEffect(()=>{
            // 负责弹幕初始化
            // 初始可以显示多少弹幕
            if(parseInt(String(options.height/options.lineHeight),10)>rowArr.length){
                let newArr=rowArr.slice()
                for(let i=0;i<parseInt(String(options.height/options.lineHeight),10)-rowArr.length;i++){
                    newArr.push({"idle":true})
                }
                setRowArr(newArr)
            }else{
                // 如果放不下了,就去掉一些轨道,或者直接置为false
                let newArr=rowArr.slice()
                newArr.splice(-1,rowArr.length-parseInt(String(options.height/options.lineHeight),10))
                setRowArr(newArr)
            }
            
            // 根据当前的宽度,调整速度
        },[options.height,options.width,options.lineHeight])
        // 高度和宽度变化的时候,这些值都需要重新计算
    
        useEffect(()=>{
            // console.log('当前时间',time)
            if(allObject[time]){
                let data=allObject[time]
    
                const len=allObject[time].length
                if(len>rowArr.length){
                // 这里的逻辑其实就是每次更换数据的时候查看是否是超了弹幕显示的最大值,如果超了就先放到一个临时的数组
                // 在下一次显示的时候查看是否有可以补位的地方,如果补位成功,就在下一帧显示之前未显示的数据,感觉这个
                // 可以做在服务端,但是这样一来增大了服务端的压力,还是写在前端算了
                    let show=allObject[time].slice(0,rowArr.length)
                    setState(show)
                    let needShow=allObject[time].slice(rowArr.length,allObject[time].length)
                    needShowDanmu.current[time]=needShow
                }else{
                    let canShow=rowArr.length-len
                    // 还可以从里面拿多少数据
    
                    for(let i=0;i<canShow;i++){
                        if(!needShowDanmu.current[time-1]&&!needShowDanmu.current[time-2]&&!needShowDanmu.current[time-3]){
                            // 前三秒都没有残留数据,那就只放当前的数据
                            setState(data)
                            // 并且清空前面的残余数据
                            needShowDanmu.current={}
                            return
                        }else{
                            let firstLen=needShowDanmu.current[time-1]?needShowDanmu.current[time-1].length:0
                            let secondLen=needShowDanmu.current[time-2]?needShowDanmu.current[time-1].length:0
                            let thredLen=needShowDanmu.current[time-3]?needShowDanmu.current[time-1].length:0
    
                            if(firstLen>canShow){
                                // 只需要第一个数组就能补满
                                let firstShow=needShowDanmu.current[time-1].slice(0,canShow)
                                let lastShow=needShowDanmu.current[time-1].sclie(canShow,firstLen)
    
                                let needShow=[...data,firstShow]
                                setState(needShow)
                                
                                needShowDanmu.current[time-1]=lastShow
                                if(needShowDanmu.current[time-3]){
                                    delete needShowDanmu.current[time-3]
                                }
    
                            }else if(firstLen+secondLen>canShow){
                                // 需要第一个和第二才能补满
                                let second=firstLen+secondLen-canShow
                                let secondShow=needShowDanmu.current[time-2].slice(0,second)
                                let secondNeedShow=needShowDanmu.current[time-2].sclie(second,secondLen)
    
                                let needShow=[...data,...needShowDanmu.current[time-1],secondShow]
                                setState(needShow)
                                
                                // 清理残余数据
                                // 1,2秒的已经显示
                                let needTime=time-2
                                needShowDanmu.current={needTime:secondNeedShow}
                            }else{
                                // 需要三个才能补满
                                let thred=firstLen+secondLen+thredLen-canShow
                                let thredShow=needShowDanmu.current[time-2].slice(0,thred)
                                let needShow=[...data,needShowDanmu.current[time-1],...needShowDanmu.current[time-2],...thredShow]
                                
                                setState(needShow)
                                needShowDanmu.current={}
                                return
                            }
                        }
                    }
                }
    
                
    
            }else{
                // console.log('当前时间端没有弹幕',time)
            }
        },[allObject, time])
    
        useInterval(()=>{
            setTime(c=>c+1)
        },speed,{immediate:false})
        // 正确方式
    
        useEffect(()=>{
            console.log(speed)
            if(speed===null){
                setStart(false)
            }else{
                setStart(true)
            }
        },[speed])
    
        return (
            <>
                <h1>弹幕出现了</h1>
                <button onClick={handleAdd}>
                    添加一条弹幕
                </button>
                <button onClick={()=>setSpeed(null)}>
                    暂停弹幕
                </button>
                <button onClick={()=>setSpeed(1000)}>
                    继续开始
                </button>
                <button onClick={()=>setTime(5)}>
                    设置时间
                </button>
                <button onClick={()=>getIdle()}>
                    获取当前卡槽
                </button>
    
                <hr/>
                <div className="danmu-container" style={danmuContainerStyle}>
                    {/* 这样写结构才能实现让弹幕从最右边的边框开始出现,之前写的是已经出现了 */}
                    <div className="danmu-container-flex1">
                        {/* 占位 */}
                    </div>
                    <div>
                        {/* 真实的地方 */}
                    {arrRef.current.map((e:any,index:any)=>(
                            <DanmuItem 
                                    key={e.index} 
                                    data={e} 
                                    index={e.index} 
                                    options={options} 
                                    handleRemove={handleRemove} 
                                    handleToggleTrue={handleToggleTrue}
                                    handleToggleFalse={handleToggleFalse} 
                                    getIdle={getIdle}
                                    getIdleValue={getIdleValue}
                                    isStart={isStart}>
                            </DanmuItem>
                            )
                        )}
                </div>
                </div>
                
                <div>
                    {rowArr.map((e:any,index:any)=>(
                        <div key={index}>
                            {e.idle?'true':'false'}-{e.speed}-{e.time}
                        </div>
                    ))}
                </div> 
            </>
        )
    }
    

    danmuItem

    import React,{useState,useEffect,useCallback,useRef,useLayoutEffect,useMemo,useReducer} from 'react'
    import anime from 'animejs'
    
    function Item(props:any){
        const data=props.data
        const index=props.index
        const options=props.options
        const isStart=props.isStart
        // console.log(data)
        
    
        const ref=useRef<any>()
        const textRef=useRef<any>()
        const animeRef=useRef<any>()
        // 确定移动的动画。本质的动画是分成两个div,创建两个动画进程来实现的
    
        const svgRef=useRef<any>()
        const pathRef=useRef<any>()
    
        // 临时变量,为了调试方便.
        const durationTime=40000
        const easeType='linear'
        
        let idleIndex=useMemo(()=>{
            if(props.getIdle!==-1){
                console.log(props.getIdle())
                return props.getIdle()
            }else{
                console.log('真的没有地方了')
            }
        },[])
    
        useLayoutEffect(()=>{
            let count=0
            let containerWidth=options.width
            // 当前的宽度
    
            // 根据弹幕类型,决定动画
            // 普通弹幕
            const currentSpeed=options.width/parseInt(data.duration)
            let currentTime=data.time
            let currentDuration=data.duration
    
            let {speed,time}=props.getIdleValue(idleIndex)
            if(speed!==undefined&&time!==undefined){
                // 进行速度加工
                let needTime=(currentTime-time)*1000*speed/currentSpeed
                // 当前元素以这个速度需要追上上一个弹幕所需要的时间
                if(needTime>currentDuration){
                    // 需要本来的时间才能追上,所以时间不改
                }else{
                    currentDuration+=needTime
                    // 当前的时间再加上这个时间,保证不会追上
                }            
            }
            if(data.mode===8){
                let width=parseInt(anime.get(ref.current,'width') as string)
                animeRef.current=anime.timeline({
                    duration:durationTime,
                    easing:easeType,
                    loop:1,
                    autoplay:true,
                })
    
                animeRef.current
                .add({
                    targets:ref.current,
                    translateX: `-${containerWidth}`,
                    complete:function(){
                        props.handleRemove(index)          
                    },
                    begin:function(){
                        const speed=options.width/parseInt(data.duration)
                        console.log(speed)
                        props.handleToggleTrue(idleIndex,data.time,speed)
                        count+=1
                        // 锁定轨道
                    },
                    update:function(anim:any){
                        // 本来想在这里进度检测当前运动的距离
                        if(count>=2)return
                        // let distance=parseInt(anime.get(ref.current,'translateX') as string)
                        const distance=parseInt(anim["animations"][0]['currentValue']) 
                        if(Math.abs(distance)>width){
                            props.handleToggleFalse(idleIndex)
                            count+=1
                        }
                    },
                })
                .add({
                    targets:textRef.current,
                    keyframes:data.animes,
                },0)
    
            }else if(data.mode===1){
                // 顶部弹幕
                let width=parseInt(anime.get(ref.current,'width') as string)
            }else if(data.mode===5){
                // svg弹幕
                const path=anime.path(pathRef.current)
                animeRef.current=anime.timeline({
                    duration:durationTime,
                    easing:easeType,
                    autoplay:true,
                })
    
                animeRef.current
                .add({
                    targets:svgRef.current,
                    translateX:path('x'),
                    translateY:path('y'),
                    // rotate:path('angle'),
                })
                .add({
                    targets:textRef.current,
                    keyframes:data.animes,
                },0)
            }else if(data.mode===9){
                // 自己发的弹幕:特殊样式,也不算占位符
                // 边框+更快的速度+直接走1/3的最佳观看位置
                animeRef.current=anime.timeline({
                    duration:durationTime,
                    // 总的动画时间,这个要算出来
                    easing:easeType,
                    loop:1,
                    autoplay:true,
                })
    
                animeRef.current
                // 第一个动画,外面的移动的动画
                .add({
                    targets:ref.current, 
                    translateX: `-${containerWidth}`,
                    complete:function(){
                                props.handleRemove(index)          
                    },
                })
                // 第二个动画,里面字体的真正变化
                .add({
                    targets:textRef.current,
                    keyframes:data.animes,
                },0)
    
    
            }else if(data.mode===10){
                // svgText
                animeRef.current=anime.timeline({
                    duration:durationTime,
                    // 总的动画时间,这个要算出来
                    easing:easeType,
                    loop:1,
                    autoplay:true,
                })
    
                animeRef.current
                // 第一个动画,外面的移动的动画
                .add({
                    targets:ref.current, 
                    translateX: `-${containerWidth}`,
                    complete:function(){
                                props.handleRemove(index)          
                    },
                })
            }else if(data.mode===11){
                let count=Math.ceil(data.height/options.lineHeight)
    
                // animeRef.current=anime.timeline({
                //     duration:durationTime,
                //     easing:easeType,
                //     loop:1,
                //     autopla:false,
                // })
    
                // animeRef.current
                // .add({
                //     targets:ref.current,
                //     translateX: `-${containerWidth}`,
                //     complete:function(){
                //         props.handleRemove(index)          
                //     },
                //     begin:function(){
                //         for(let i=0;i<count;i++){
                //             props.handleToggle(idleIndex+i)
                //         }
                //         count+=1
                //     },
                //     update:function(anim:any){
                //         if(count>=2)return
                //         let distance=parseInt(anime.get(ref.current,'translateX') as string) 
                //         if(Math.abs(distance)>data.width){
                //             for(let i=0;i<count;i++){
                //                 props.handleToggle(idleIndex+i)
                //             }
                //             count+=1
                //         }
                //     },
                // })
            }
            
        },[])
    
        const styleMemo=useMemo(()=>{
            const top=idleIndex*options.lineHeight
            if(data.mode===9){
                return {
                    position:'absolute' as 'absolute',
                    top:`${options.height/3}px`,
                    border:'2px solid red'
                }
            }else if(data.mode===5){
                return {
                    width:props.options.width,
                    height:props.options.height,
                }
            }
            else{
                return {
                    position:'absolute' as 'absolute',
                    top:`${top}px`,
                }
            }
    
           
        },[]) 
    
        useEffect(()=>{
            // 根据外部的状态暂停或者拉起全部的动画,比如观看视频的时候暂停了,那么所有弹幕的动画也需要暂停
            // if(isStart){
            //     handleContinue()
            // }else{
            //     handleStop()
            // }
        },[isStart])
        
    
        function handleStop(){
            // 鼠标触摸暂停动画
            if(!animeRef.current)return
            animeRef.current.pause()
            // 感觉弹幕举报或者点赞应该在这里做操作
        }
        function handleContinue(){
            console.log(animeRef.current)
            if(!animeRef.current)return
            animeRef.current.play()
        }
        function handleMenu(event:any){
            event?.preventDefault()
            console.log('右键了')
        }
        function handleIdle(){
            console.log(props.getIdle())
        }
        function getSvgTextStyle(index:number,len:number):React.CSSProperties{
            return {
                animation:`6s stroke ${index-len}s infinite linear forwards`
            }
        }
        if(data.mode===8||data.mode===9){
            // 右往左,最一般的形式都走这个逻辑
            return(
                <div ref={ref} 
                    style={styleMemo} 
                    //  onMouseEnter={handleStop} 
                    onMouseEnter={handleIdle}
                    onContextMenu={handleMenu} 
                    //  onMouseLeave={handleContinue}
                    onClick={handleStop}
                    className="danmu-text"
                     >
                        <div 
                        className="danmu-text-inner"
                        ref={textRef}
                        >
                            {data.value}
                        </div>
                     </div>
            )
        }else if(data.mode===5){
            // svg弹幕
            return (
                <div>
                    <svg id="danmu-svg" style={styleMemo}>
                        <path
                            className="danmu-svg-path"
                            d={data.svgPath}
                            ref={pathRef}
                            id="path"
                        ></path>        
                    </svg>
                    <div className="danmu-svg-text"
                         ref={svgRef}
                         >
                        <div 
                            className="danmu-svg-text-inner"
                            ref={textRef}
                            >
                            {data.value}
                        </div>
                    </div>
                </div>
            )
        }else if(data.mode===10){
            // svgText
            return (
                <div ref={ref}>
                    <svg>
                       {data.texts.map((e:any,textIndex:number)=>(
                           data.colors.map((a:any,colorIndex:number)=>(
                           <text 
                                x={`${textIndex*25}%`} 
                                y="35%" 
                                className="danmu-svg-text" 
                                stroke={a}
                                style={getSvgTextStyle(colorIndex,data.texts.length)}
                                key={a}
                            >
                                    {e}
                           </text>
                           ))
                       ))}
                    </svg>
                </div>
            )
    
        }else if(data.mode===11){
            // TextPicture
            const textPictureStyle={
                width:data.width,
                height:data.height,
            }as React.CSSProperties
            return (
                <div    
                    ref={ref} 
                    style={textPictureStyle} 
                    dangerouslySetInnerHTML={{__html:data.value}}
                    className="danmu-text-picture"
                >
                </div>
            )
        }
        else{
            return (
                <div>
                    最后的保险
                </div>
            )
        }
    
        
        
    }
    
    export default Item
    

    相关文章

      网友评论

          本文标题:弹幕组件

          本文链接:https://www.haomeiwen.com/subject/iemptltx.html