1

效果如下:

由于gif文件过大,无法上传...只能用图片形式展示
image

开发思路如下:

    1.每一条弹幕都是一个有文本内容的div元素,当发送弹幕之后,该元素将会通过绝对定位显示在视频上面,开始自右向左移动,当元素从视频左边移除时,我们需要将其元素删除(从dom树及堆中)。所以需要将弹幕元素对象单独封装成一个类,当主页面input框输入信息提交时,即时创建div元素并开始在视频上移动。
    2.封装一个观察者类,并声明一个静态属性为其本身创建的一个实例化对象,当每有一个弹幕元素对象创建时,都将其放在这个实例化对象中的属性里储存,并且当观察者实例化对象内部有元素时就开始设置定时器每帧执行一次内部所有弹幕元素对象的update方法移动
    3.当弹幕元素判断x轴移动位置在视频外时,从update中触发删除元素,并从观察者实例化对象的属性中删除该元素,当观察者实例化对象的属性中没有元素之后清除定时器。即有元素加入之后设置定时器开始update,当无元素时清除定时器。

主页面js代码如下:

        import Bullet from "./Bullet.js";
        var input=document.querySelector("input");
        document.addEventListener("keyup",keyHandler);
        function keyHandler(e){
            if(e.keyCode!==13) return;
            if(input.value.trim().length===0) return;
            var bullet=new Bullet(input.value);//创建的弹幕,将input内容输入弹幕(同时该弹幕实例化对象)
            bullet.appendTo(".videobox");//将弹幕插入到容器中
            input.value="";
        }

弹幕类Js代码如下:

import TimeManager from "./TimeManager.js";

export default class Bullet{
    rect;
    x;
    speed=2;
    width;
    col;
    constructor(txt){
        this.elem=this.createElem(txt);
    }
    createElem(txt){
        if(this.elem) return this.elem;
        var div = document.createElement("div");
        this.randomColor();
        Object.assign(div.style,{
            whiteSpace: "nowrap",
            position:"absolute",
            fontSize:"20px",
            color:this.col
        })
        div.textContent=txt
        return div;
    }
    randomColor() {
        this.col = "#";
        for (var i = 0; i < 6; i++) {
            this.col += Math.floor(Math.random() * 16).toString(16);
        }
    }
    appendTo(parent){
        if(typeof parent==="string") parent=document.querySelector(parent);
        parent.appendChild(this.elem);
        this.rect=parent.getBoundingClientRect();
        Object.assign(this.elem.style,{
            top:Math.random()*this.rect.height/4+"px",
            left:this.rect.width+"px"//注意插入开始时是放在视频容器右边外边的
        });
        this.x=this.rect.width;
        this.width=this.elem.offsetWidth;
        TimeManager.instance.add(this);//当弹幕对象创建好添加到视频容器中之后,再将其添加到观察者的单例对象的list上
    }
    update(){
        if(!this.width) return;
        this.x-=this.speed;
        this.elem.style.left=this.x+"px";
        if (this.x < -this.width) {//这步是弹幕对象移动了整个视频容器的位置+本身文本撑开的宽度
            //当弹幕播放完之后删除,观察者单例对象的list上,dom结构中以及堆中都要删除
            TimeManager.instance.remove(this);
            this.elem.remove();
            this.elem=null;
        }
    }
}

观察者模块Js代码如下:

export default class TimeManager{
    static _instance;
    list=new Set();
    ids;
    constructor(){

    }
    static get instance(){//获取instance时,如果没有当前类就实例化一个对象存为静态属性,有则直接返回该实例化对象
        if(!TimeManager._instance){
            Object.defineProperty(TimeManager,"_instance",{
                value:new TimeManager()
            })
        }
        return TimeManager._instance;
    }

    add(elem) {//将被观察者:弹幕Bullet实例化对象->放入观察者的实例化对象的list中
        // 同时list中只要有被观察者,就设置时间间隔每帧执行一次自己实例化对象的方法update()
        this.list.add(elem);
        if(this.list.size>0 && !this.ids)
        this.ids=setInterval(()=>this.update(),16);       
    }

    remove(elem) {//将被观察者:弹幕Bullet实例化对象从观察者的实例化对象的list中删除
        // 同时list中没有被观察者后,就删除时间间隔
        this.list.delete(elem);
        if(this.list.size===0 && this.ids){
            clearInterval(this.ids);
            this.ids=undefined;
        }
    }

    update() {
        // 执行一次list中被观察者的方法
        this.list.forEach(item=>{
           if(item.update) item.update();
        })
    }
    
}

Erebos
1 声望0 粉丝