组件背景与来源
本文介绍的跑马灯组件是基于 Naive UI的优秀设计理念提炼整理而成。Naive UI 是一个完整的 Vue 3 组件库,以其优雅的设计和出色的性能著称。我们在其 Marquee 组件的基础上进行了精简和优化,特别加强了鼠标交互体验。
组件概述
这是一个高性能的 Vue 3 跑马灯组件,适用于新闻滚动、公告展示、时间轴等场景。组件具有以下核心特性:
-
♾️ 无限循环滚动 - 基于 CSS 动画实现无缝滚动
-
📏 自适应宽度 - 自动根据容器和内容尺寸调整
-
⚡ 性能优化 - 使用 CSS 变换和硬件加速
-
🖱️ 交互控制 - 鼠标悬停暂停/继续
-
🎚️ 可调速度 - 支持动态调整滚动速度
核心实现原理
1. 响应式尺寸检测机制
组件使用 ResizeObserver实时监测尺寸变化,确保在各种屏幕尺寸下都能完美展示:
// 创建 ResizeObserver 实例
const resizeObserver = ref<ResizeObserver | null>(null);
function handleContentResize(entries: ResizeObserverEntry[]) {
for (const entry of entries) {
if (entry.target === contentElRef.value) {
contentWidthRef.value = entry.contentRect.width;
} else if (entry.target === containerElRef.value) {
containerWidthRef.value = entry.contentRect.width;
}
}
}
2. 动态内容重复算法
为了确保无缝循环,组件动态计算需要重复的内容组数:
const repeatCountInOneGroupRef = computed(() => {
const { value: contentWidth } = contentWidthRef;
const { value: containerWidth } = containerWidthRef;
if (contentWidth === -1 || containerWidth === -1) return 1;
return Math.ceil(containerWidthRef.value / contentWidth);
});
3. 动画参数计算
基于内容宽度和速度参数计算动画时长:
const durationRef = computed(() => {
const { value: contentWidth } = contentWidthRef;
if (contentWidth === -1) return 0;
return (contentWidth * repeatCountInOneGroupRef.value) / props.speed;
});
鼠标悬停交互优化
实现原理
我们在原生 Naive UI 组件的基础上增强了鼠标交互功能,让用户体验更加友好:
// 鼠标悬停事件处理 - 新增功能
function handleMouseEnter() {
playStateRef.value = "paused";
}
// 鼠标离开事件处理 - 新增功能
function handleMouseLeave() {
playStateRef.value = "running";
}
交互设计思路
-
悬停暂停:当用户鼠标悬停时,动画暂停,方便阅读内容
-
离开继续:鼠标移出后,动画从暂停位置继续播放
-
平滑过渡:通过 CSS 的
animation-play-state实现平滑的暂停/继续效果
完整组件代码解析
模板结构
<template>
<div
class="marquee"
ref="containerElRef"
:style="animationCssVarsRef"
@mouseenter="handleMouseEnter" <!-- 新增鼠标事件 -->
@mouseleave="handleMouseLeave" <!-- 新增鼠标事件 -->
>
<!-- 第一组内容 -->
<div
class="marquee__group"
@animationiteration="handleAnimationIteration"
ref="contentElRef"
>
<slot />
</div>
<!-- 第二组内容用于无缝衔接 -->
<div class="marquee__group">
<slot />
</div>
</div>
</template>
样式实现
.marquee {
overflow: hidden;
display: flex;
width: 100%;
&__group {
flex: 0 0 auto;
min-width: var(--n-min-width);
z-index: 1;
display: flex;
flex-direction: row;
align-items: center;
/* 核心动画定义 */
animation: n-marquee var(--n-duration) linear var(--n-delay) infinite;
animation-play-state: var(--n-play); /* 支持暂停/继续 */
@keyframes n-marquee {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
}
}
动画控制逻辑
const animationCssVarsRef = computed(() => {
return {
"--n-play": playStateRef.value, // 控制播放状态
"--n-duration": `${durationRef.value}s`, // 动画时长
"--n-delay": "0s",
"--n-iteration-count": "infinite",
"--n-min-width": "auto",
};
});
使用示例
<template>
<div class="demo-container">
<marquee :speed="50">
<div class="content-wrapper">
<div class="content-item" v-for="item in items" :key="item.id">
<span class="item-text">{{ item.text }}</span>
</div>
</div>
</marquee>
</div>
</template>
CSS 动画优势
-
硬件加速:使用
transform属性触发 GPU 加速 -
流畅性:避免 JavaScript 直接操作 DOM 导致的布局抖动
-
低功耗:浏览器可以对 CSS 动画进行优化
与其他方案的对比
|
特性 |
本组件 |
传统 JS 实现 |
纯 CSS 动画 |
|---|---|---|---|
|
性能 |
✅ 优秀(硬件加速) |
❌ 一般 |
✅ 优秀 |
|
可控性 |
✅ 完全可控 |
✅ 高度可控 |
❌ 有限 |
|
无缝循环 |
✅ 完美支持 |
✅ 支持 |
❌ 困难 |
|
交互体验 |
✅ 悬停暂停 |
✅ 可定制 |
❌ 有限 |
|
兼容性 |
✅ Vue 3 生态 |
✅ 广泛 |
✅ 广泛 |
实际应用建议
1. 速度参数调优
<!-- 不同场景的速度建议 -->
<marquee :speed="80">
<!-- 快速展示,适合标语 -->
<marquee :speed="50">
<!-- 中等速度,通用场景 -->
<marquee :speed="30">
<!-- 慢速,适合阅读性内容 -->
2. 响应式适配
// 移动端适配
@media (max-width: 768px) {
.marquee {
&__group {
animation-duration: calc(var(--n-duration) * 1.2); // 移动端稍慢
}
}
}
3. 内容长度优化
-
确保单个内容块宽度适中(建议 200-400px)
-
避免内容过长影响滚动效果
-
考虑移动端的触摸交互体验
总结与展望
本组件在 Naive UI 原有设计的基础上,通过以下方面进行了优化:
-
交互增强:新增鼠标悬停暂停功能,提升用户体验
-
代码精简:去除复杂配置,保持核心功能的简洁性
-
性能保持:继承原有的 CSS 动画优化方案
922

被折叠的 条评论
为什么被折叠?



