来, vue3实现一个高性能的弹幕组件

先有问题再有答案

  1. vue中如何获取dom的宽高属性?
  2. 100条数据就要设置100个dom嘛?
  3. 每个弹幕动画的运行效果 具体是如何实现的?
  4. 每个弹幕的长度是不固定的 如何正确获取长度值 给动画设置正确的结束位置?
  5. 如何利用raf实现高性能的动画效果?
  6. vue中的插槽究竟要如何使用?
  7. 插槽的本质是什么?

效果

组件功能

  1. 多行显示:row指定某条弹幕随机出现在第[1,N]行。
  2. 动态延迟:每条弹幕的显示延迟可以随机生成,使得弹幕的出现更加自然。
  3. 可复用实例:通过复用实例来优化性能,减少频繁创建和销毁 DOM 元素的开销。
  4. 自定义样式 :可以通过插槽(slot)来自定义每个弹幕项的样式和内容 长度不限
  5. 响应式更新:当传入的数据列表发生变化时,组件会自动更新并重新安排弹幕的显示。

使用方式

javascript 复制代码
<DanmuView :count="8" :row="4" :row-h="30" :delays="[500, 3000]" :frames="[0.5, 1.2]" :list-data="list">
                <template #default="{ data }">
                    <div class="item" @click="onItem(data)">
                        <img :src="data.icon" :width="18" class="icon" />
                        <div class="name">
                            {{ data.content }}
                        </div>
                    </div>
                </template>
            </DanmuView>
  • count:设置可复用的实例化数量。
  • row:设置弹幕轨道行数。
  • row-h:设置每行的高度。
  • delays:设置每条弹幕的随机延迟范围。
  • frames:设置每帧移动的距离范围。
  • list-data:传入弹幕数据列表。

源码:

github: vue3-dan-mu-view 伸手党同学 如果好用 还请点赞收藏哈 感谢

实现方案

这里可以分为两部分:

单条弹幕如何实现,需要注意哪些知识点。

多条弹幕实现 如何设计数据的缓存队列 dom池的复用逻辑。

单条弹幕实现

初始态:

结束态:

整体在X轴方向移动了 容器的宽度 + 自身宽度 的距离。

在vue中想要获取dom的width 可以通过ref实现。

位移动画 可以使用transform实现。

javascript 复制代码
<div ref="container" :style="[props.innerStyle, _style]" class="scroll-row">
        <div ref="content" class="scroll-content" :style="{ transform: `translateX(${translateX}px)` }">
            <slot :data="_item">
                {{ _item }}
            </slot>
        </div>
    </div>
javascript 复制代码
containerWidth = container.value?.offsetWidth || 0;
contentWidth = content.value?.offsetWidth || 0;
translateX.value = containerWidth;

内容自定义

每个item具体的样式 我们通过Vue 提供的插槽能力来实现

插槽 (slot) 特性的设计主要是为了实现组件内容的可重用和内容分发。这是一种让父组件可以向子组件动态插入内容的特性。

  1. 内容分发 (Content Projection) :插槽可以让开发者在使用一个组件的时候,向组件内部动态传入任意的 DOM 结构。这在开发类似于 Dialog、Card 这样的容器类型组件的时候非常有用。
  2. 作用域插槽 (Scoped Slot) :通过作用域插槽,父组件可以获取到子组件内部的数据,达到更复杂的定制和控制

对于插槽的应用不熟悉的同学 可以参考这篇文章 面试官:vue插槽有什么用?插槽的本质是什么?

item长度不定 如何解决长度问题?

因为内容是通过插槽用户自定义的 高度一般是固定的 但是长度都是不定的

我们要如何计算出每一个弹幕的长度 然后实现动画 移动到终态的效果呢?

很简单 设置translateX=-contentWidth 即可。

过渡效果

从初始态 移动到 结束态的中间过渡的过程 我们可以通过不断的修改translateX的值来实现。

这个过程会频繁的调用 这里我们使用 raf 来实现。 以保证动画和渲染频率一致。

window.requestAnimationFrame() 告诉浏览器------你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。rAF是浏览器提供的和渲染帧率保持同步的一个api.

具体参考 浏览器: 深入理解requestAnimationFrame优化js运行时

javascript 复制代码
 const step = () => {
            translateX.value -= _frame.value;
            if (translateX.value < -contentWidth) {
                nextTick(() => {
                    emit('onEnd');
                });
            } else {
                animationFrameId = requestAnimationFrame(step);
            }
        };
        step();

当运行结束 我们向外抛出一个onEnd事件 让调用方可以感知到 以便实现组件数据清空并重复使用。

多条弹幕实现

数据缓存队列

因为数据一般是源源不断从服务器拉取的 新数据到达后 因为Vue的响应性 数据发生改变后 视图会自动更新 这可能会导致视图闪烁 带来较差的视觉体验 所以需要在服务端数据和实际应用的数据中间加一个缓存队列 服务端数据不断向这个队列累加 当视图中某个view动画运行结束 在依次从缓存队列中取新的数据。

dom缓存池

在初始化组件时传入的count属性指定了 这个dom缓存池的数量 组件最多同时出现的DOM数量即为count值。

当一个dom动画运行结束 且没有更多数据可以运行时 这个dom会被加入到空闲队列中 等下次有新的数据入队时 在继续运行动画。 并不会重复的创建销毁DOM 组件通过这个缓存池实现DOM复用 以达到性能优化的目的。

javascript 复制代码
const handleScrollEnd = (instance: ItemIns) => {
  const item = list.value.shift();
  if (item) {
    instance.update(item);
  } else {
    idleList.push(instance);
  }
};

注意:如果初始化指定的count=10 实际传入的数据长度是5 那么组件依然会实例化10个item并且这10个item会正常的执行动画 只是会将多余的5个设置为重复的数据 并且设置一个较大的位移 在视图上不可见

建议根据视图的高度和弹幕的轨道高度 保证每个轨道有1~2个弹幕即可。

相关推荐
雾恋3 小时前
最近一年的感悟
前端·javascript·程序员
华仔啊3 小时前
Vue3 的 ref 和 reactive 到底用哪个?90% 的开发者都选错了
javascript·vue.js
A黄俊辉A4 小时前
axios+ts封装
开发语言·前端·javascript
小李小李不讲道理4 小时前
「Ant Design 组件库探索」四:Input组件
前端·javascript·react.js
连合机器人5 小时前
晨曦中的守望者:当科技为景区赋予温度
java·前端·科技
郑板桥305 小时前
tua-body-scroll-lock踩坑记录
前端·javascript
IT古董5 小时前
Vue + Vite + Element UI 实现动态主题切换:基于 :root + SCSS 变量的最佳实践
vue.js·ui·scss
慢半拍iii6 小时前
JAVA Web —— A / 网页开发基础
前端
gnip6 小时前
pnpm 的 monorepo架构多包管理
前端·javascript
42fourtytoo7 小时前
天津大学智算2026预推免机试第二批题目及代码c++
开发语言·c++·面试