从0到1徒手撸一个Vue3弹幕组件

771 阅读3分钟

1.弹幕

弹幕(Danmaku)是一种源自日本的视频评论方式,它允许观众在视频播放时实时发表评论,这些评论会以弹幕的形式直接出现在视频画面上。这种评论方式最初是由日本的一个动画分享网站Niconico动画(Niconico Douga)在2006年引入的,后来被中国的AcFun和Bilibili等视频网站采用并推广。

类似下图中的效果:

image.png

2.如何实现

弹幕的本质是滚动,从屏幕一侧飞向另一侧,这里我们以最常见的从右至左的弹幕形势为例。 也就是说,要实现一条弹幕至少要分为以下三步:

  • 确定弹幕的初始位置,一般是在屏幕之外
  • 确定弹幕的终止位置,一般是屏幕的另外一侧
  • 使用动画实现弹幕的移动动画

3.步骤详解

接下来,我们演示一下在Vue3中实现一个简单的弹幕,效果不是重点,重点是帮助大家理解弹幕的基本生成逻辑和原理。

3-1. 生成弹幕数据

import { useIntervalFn } from "@vueuse/core";
import { fakerZH_CN as faker } from '@faker-js/faker';

// 弹幕模型定义
interface BarrageItem {
    id: string; // ID
    avatar: string; // 头像
    name: string; // 用户名
    content: string; // 弹幕内容
}

// 配置
const config = ref({
   stay: 100, // 弹幕停留时间,ms
   animate: 15, // 弹幕滚动时间,s
   speed: 1000, // 弹幕产生速度, ms
   track: [10, 80, 150, 220, 290, 360] , // 轨道列表
})

// 弹幕列表
const barrageList: Ref<BarrageItem[]> = ref([]);

// 模拟自动生成弹幕数据
const { pause, resume } = useIntervalFn(
    () => {
        const barrage: BarrageItem = {
            id: faker.string.uuid(),
            avatar: faker.image.avatar(),
            name: faker.person.fullName(),
            content: faker.music.songName()
        }
        console.log("barrage: ", barrage);
        barrageList.value.push(barrage);
    },
    config.value.speed, {
    immediate: false // 手动控制定时器
}
);
  • 首先我们定义了一个弹幕的数据类型
  • 然后添加一个弹幕的配置项,方便后续进行调试;这里包括了弹幕的生成速度,滚动时间,停留时间以及弹幕的轨道
  • 最后使用了Hook useIntervalFn来实现一个定时任务,循环产生一条弹幕数据

3-2. 渲染弹幕

<!---弹幕容器-->
        <section class="relative outline-(dashed 1px blue) h-460px w-1024px">
            <section class="flex flex-row justify-center h-full">
                <TransitionGroup tag="ul" name="slide" class="overflow-hidden w-full h-full relative" @enter="barrageEnter">
                    <!---单个弹幕-->
                    <li v-for="item in barrageList" :key="item.id" class="barrage-item">
                        <img :src="item.avatar" class="w-50px h-50px rounded-full mr-2 border-(2px solid [#fdcd00])" />
                        <p class="text-lg font-bold">
                            <span class="mr-2" v-text="item.name + ':'"></span>
                            <span v-text="item.content"></span>
                        </p>
                    </li>
                </TransitionGroup>
            </section>
        </section>

使用Vue的v-for循环生成弹幕Dom,同时添加元素的样式。 这里最主要的是监听了TransitionGroup的enter事件,@enter="barrageEnter"

3-3. 随机轨道

// 轨道列表
const trackIndex = ref(-1);
// 打乱轨道
const trackList = _.shuffle(config.value.track);
const TRACK_NUM = trackList.length;
// 随机获取定位
const getPositionRandom = () => {
    let idx = trackIndex.value + 1;
    if (idx >= TRACK_NUM) {
        idx = 0;
    }
    trackIndex.value = idx;
    return trackList[trackIndex.value] + _.random(0, 10);
};
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
const barrageEnter = (ele: Element) => {
    const el = ele as HTMLElement
    el.style.top = `${getPositionRandom()}px`;
    el.style.animationDuration = `${config.value.animate}s`;

    // 弹幕动画结束后,删除DOM元素
    el.addEventListener("animationend", () => {
        setTimeout(() => {
            el.parentNode?.removeChild(el);
        }, config.value.stay);
    });
};

在enter事件中,我们为弹幕元素随机设置了一个top值,也就是轨道的位置。 同时,监听元素动画结束后,自动将其删除,避免累积过多无用的DOM元素占用内存。

3-4 弹幕动画

@keyframes barrage {
    0% {
        transform: translateX(1024px) translateZ(0);
    }

    100% {
        transform: translateX(-100%) translateZ(0);
    }
}

.barrage-item {
    position: absolute;
    line-height: 1;
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    animation-name: barrage;
    animation-timing-function: linear;
    transform: translateX(1024px);
}

定义了一个关键帧动画barrage,将元素从1024px(弹幕容器的宽度)移动到-100%(弹幕自身的宽度)。 这里只是做一个最简单的参考,大家可以根据实际需求进行调整,添加更为复杂的动画效果。

4. 运行项目

// 自动开启弹幕
onMounted(() => resume())
// 关闭定时器
onBeforeUnmount(() => pause())

我们在vue组件渲染时,自动开始产生弹幕,同时在页面卸载时取消定时器。 自此,弹幕效果基本上开发完成,运行后即可看到效果。

5.参考源码

以上全部的源码已经托管在Gitee上,感兴趣的同学可以自取。