原生js--直播弹幕

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #wrapper{
            width: 100%;
            height: 400px;
            background-color: #000;
            color: #fff;
            position: relative;
            overflow: hidden;
            font-size: 14px;
        }
        .right{
            position: absolute;
            visibility: hidden;
            white-space: nowrap;
            transform: translateX(100vw);
        }
        .left{
            position: absolute;
            white-space: nowrap;
            user-select: none;
            transition: transform 7s linear; /* 时间相同 越长的弹幕滑动距离越长 所以越快~ */
        }
        input {
            position: absolute;
            bottom: 10px;
            left: 150px;
            width: 300px;
            height: 26px;
        }

        button {
            position: absolute;
            bottom: 8px;
            left: 476px;
            width: 100px;
            height: 38px;
            border-radius: 10px;
            font-size: 16px;
        }
    </style>
</head>
<body>
    <div id="wrapper">
        <input type="text">
        <button>&nbsp;&nbsp;</button>
    </div>
<script>
    /*
    * 弹幕池,每一条通道最多6条弹幕
    * */
    const MAX_DM_COUNT = 6;
    const CHANNEL_COUNT = 10;

    let domPool = [];
    let danmuPool = [
        '弹幕1'
    ];
    let hasPosition = [];

    /*
    * 初始化
    * */
    function init() {
        let wrapper = document.getElementById('wrapper');
        for(let j = 0; j < CHANNEL_COUNT; j++){
            let doms = [];
            for(let i = 0; i < MAX_DM_COUNT; i++){
                // 要全部放进wrapper
                let dom = document.createElement('span');
                wrapper.appendChild(dom);
                // 初始化dom的位置 通过设置className
                dom.className = 'right';
                // DOM的通道是固定的 所以设置好top就不需要再改变了
                dom.style.top = j * 20 + 'px';
                // 放入改通道的DOM池
                doms.push(dom);
                // 每次到transition结束的时候 就是弹幕划出屏幕了 将DOM位置重置 再放回DOM池
                dom.addEventListener('transitionend', () => {
                    dom.className = 'right';
                    dom.style.transform = null;
                    // 在滚动结束以后,初始化数组
                    domPool[j].push(doms)
                })
                // 刚开始就是初始化数组,每个轨道添加最大量数组
            }
            domPool.push(doms);
        }

        // hasPosition 标记每个通道目前是否有位置
        for (let i = 0; i < CHANNEL_COUNT; i++) {
            hasPosition[i] = true;
        }
    }

    /*
     * 获取一个可以发射弹幕的通道 没有则返回-1
     */
    function getChannel() {
        for(let i = 0; i < CHANNEL_COUNT; i++){
            if (hasPosition[i] && domPool[i].length) return i;
        }
        return -1
    }

    /*
     * 根据DOM和弹幕信息 发射弹幕
     */
    function shootDanmu(dom, text, channel) {
        // console.log('biu~ [' + text + ']');
        dom.innerText = text;
        // 如果为每个弹幕设置 transition 可以保证每个弹幕的速度相同 这里没有保证速度相同
        // dom.style.transition = `transform ${7 + dom.clientWidth / 100}s linear`;
        // 设置弹幕的位置信息 性能优化 left -> transform
        dom.style.transform = `translateX(${-dom.clientWidth}px)`;
        dom.className = 'left';

        hasPosition[channel] = false;
        // 弹幕全部显示之后 才能开始下一条弹幕
        // 大概 dom.clientWidth * 10 的时间 该条弹幕就从右边全部划出到可见区域 再加1秒保证弹幕之间距离
        setTimeout(() => {
            hasPosition[channel] = true;
        }, dom.clientWidth * 10 + 1000);
    }

    window.onload = function () {
        init();

        console.log(domPool)

        // 为input和button添加事件监听
        let btn = document.getElementsByTagName('button')[0];
        let input = document.getElementsByTagName('input')[0];
        btn.addEventListener('click', () => {
            input.value = input.value.trim();
            if (input.value) danmuPool.push(input.value);
        })
        input.addEventListener('keyup', (e) => {
            if (e.key === 'Enter' && (input.value = input.value.trim())) {
                danmuPool.push(input.value);
            }
        })

        setInterval(() => {
            let channel;
            if(danmuPool.length && ( channel = getChannel())!= -1){
                let dom = domPool[channel].shift();
                let danmu = danmuPool.shift();
                shootDanmu(dom, danmu, channel);
            }
        }, 1)
    }

</script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值