在大屏可视化项目中,文字展示看似简单,却藏着不少坑 ------ 比如公告栏文本过长超出容器、数据说明文字被截断、普通滚动组件要么卡顿要么衔接生硬,甚至鼠标悬停时还在 "自顾自" 滚动,体验极差。
今天就基于你的实战代码,拆解一套「大屏专属文字滚动组件」的实现方案:支持自动判断是否需要滚动 (文本没超出就不滚)、无缝衔接滚动 (避免断层)、鼠标悬停暂停(提升交互友好性),代码可直接复用,完美解决大屏文字展示的痛点。
一、大屏文字滚动的 3 大核心痛点
先聊聊大屏场景下文字滚动的 "糟心事",看看你是否也遇到过:
- "没必要" 的滚动:文本明明没超出容器,却强制滚动,显得多余又晃眼;
- "断层" 的衔接:滚动到末尾时,从最后一个字直接跳回开头,视觉上很突兀;
- "停不下来" 的尴尬:鼠标想细看文字内容,滚动却停不下来,只能 "追着字看"。
而你封装的这个组件,正好精准解决了这 3 个问题 ------ 让滚动 "该滚才滚""滚得流畅""想停就停"。
二、组件核心逻辑拆解:怎么实现的?
你的组件(ScrollText.vue
)用了很巧妙的设计,核心围绕 "智能判断 + 无缝滚动 + 交互优化" 展开,我们分 3 部分拆解:
1. 模板结构:无缝滚动的关键 ------ 两个相同文本区
xml
<template>
<div
class="scrollText"
ref="outer"
@mouseenter="onMouseEnter" <!-- 鼠标进入暂停 -->
@mouseleave="onMouseLeave" <!-- 鼠标离开继续 -->
>
<!-- 滚动容器:只有需要滚动时才加 st-scrolling 类 -->
<div class="st-inner" :class="{ 'st-scrolling': needToScroll }">
<!-- 文本区1:实际展示的文本 -->
<span class="st-section" ref="inner">{{ text }}</span>
<!-- 文本区2:复制文本,实现无缝衔接(文本超出时才显示) -->
<span class="st-section" v-if="needToScroll" style="user-select: none;">{{ text }}</span>
</div>
</div>
</template>
关键设计:为什么要放两个相同的文本区?
普通滚动组件只有一个文本区,滚动到末尾时会 "瞬间跳回开头",出现断层。而放两个相同的文本区后:
- 滚动时,第一个文本区向左移出容器,第二个文本区会紧跟在后面;
- 当第一个文本区完全移出后,第二个文本区刚好占据容器位置,视觉上 "无缝衔接";
- 配合
translate3d(-50%, 0, 0)
的动画,刚好让两个文本区的衔接点落在容器外,完全看不出断层。
2. 脚本逻辑:智能判断 + 动画控制
脚本部分是组件的 "大脑",负责判断是否需要滚动、控制动画暂停 / 继续,核心方法拆解如下:
kotlin
<script>
export default {
data() {
return {
needToScroll: false, // 是否需要滚动(文本超出容器才为true)
text: '', // 存储最终要展示的文本
isPaused: false, // 动画是否暂停(鼠标悬停时为true)
_checkTimer: null // 定时器实例,用于定期检查文本是否超出
};
},
mounted() {
this.startCheck(); // 组件挂载后开始检查
},
beforeDestroy() {
this.stopCheck(); // 组件销毁前清理定时器,避免内存泄漏
},
methods: {
// 1. 鼠标进入:暂停动画
onMouseEnter() {
this.isPaused = true;
const inner = this.$refs.outer?.querySelector('.st-inner');
if (inner) inner.style.animationPlayState = 'paused';
},
// 2. 鼠标离开:恢复动画
onMouseLeave() {
this.isPaused = false;
const inner = this.$refs.outer?.querySelector('.st-inner');
if (inner && this.needToScroll) {
inner.style.animationPlayState = 'running';
}
},
// 3. 核心:检查文本是否超出容器,决定是否需要滚动
check() {
this.setText(); // 先获取插槽中的文本
this.$nextTick(() => { // 等待DOM更新后再判断
const isOverflow = this.isOverflow();
this.needToScroll = isOverflow;
// 如果需要滚动且没暂停,确保动画在运行
if (isOverflow && !this.isPaused) {
const inner = this.$refs.outer?.querySelector('.st-inner');
if (inner) inner.style.animationPlayState = 'running';
}
});
},
// 4. 判断文本是否超出容器: inner宽度 > outer宽度 → 超出
isOverflow() {
const outer = this.$refs.outer;
const inner = this.$refs.inner;
if (!outer || !inner) return false;
const outerWidth = this.getWidth(outer); // 容器宽度
const innerWidth = this.getWidth(inner); // 文本宽度
return innerWidth > outerWidth;
},
// 5. 获取元素实际宽度(用getBoundingClientRect,比offsetWidth更精准)
getWidth(el) {
return el.getBoundingClientRect()?.width || 0;
},
// 6. 从插槽中提取文本(支持多个插槽内容拼接)
setText() {
this.text = this.$slots.default
? this.$slots.default.reduce((res, item) => res + (item.text || ''), '')
: '';
},
// 7. 启动定时器:每隔100ms检查一次(应对动态文本更新)
startCheck() {
this._checkTimer = setInterval(this.check, 100);
this.check(); // 立即执行一次,避免初始延迟
},
// 8. 停止定时器:组件销毁时调用
stopCheck() {
clearInterval(this._checkTimer);
}
}
};
</script>
核心亮点解读:
- 智能判断滚动 :通过
isOverflow
方法对比 "文本宽度" 和 "容器宽度",没超出就不滚动,避免多余动画; - 动态文本适配:用定时器每隔 100ms 检查一次,即使文本是动态更新的(比如从接口获取的公告),也能实时判断是否需要滚动;
- 优雅的交互控制:鼠标悬停时暂停动画,离开后继续,解决 "想细看却追不上" 的问题;
- 内存安全:组件销毁前清理定时器,避免内存泄漏(大屏组件常被频繁切换,这点很重要)。
3. 样式设计:流畅滚动的关键 ------CSS 动画
样式部分用了简洁的动画,确保滚动流畅且性能好:
xml
<style lang="less" scoped>
.scrollText {
overflow: hidden; // 隐藏超出容器的部分
white-space: nowrap; // 文本不换行(确保一行展示,才需要横向滚动)
}
.st-inner {
display: inline-block; // 让容器宽度适应文本宽度(不独占一行)
}
// 滚动时,两个文本区之间加5px间距,避免挤在一起
.st-scrolling .st-section {
padding: 0 5px;
}
// 核心滚动动画:向左匀速滚动,无限循环
.st-scrolling {
/*
1. animation: 动画名 时长 速度曲线 循环次数
2. linear:匀速滚动,避免忽快忽慢
3. infinite:无限循环
4. translate3d:启用GPU加速,比translate更流畅(大屏动画多,性能很重要)
*/
animation: scroll 10s linear infinite;
}
// 动画关键帧:从0%到100%,向左移动50%(刚好是两个文本区的一半,实现无缝)
@keyframes scroll {
0% {
transform: translate3d(0%, 0, 0); // 初始位置:第一个文本区在容器内
}
100% {
transform: translate3d(-50%, 0, 0); // 结束位置:第一个文本区完全移出,第二个刚好接上
}
}
</style>
样式优化细节:
white-space: nowrap
:确保文本在一行展示,这是横向滚动的前提;display: inline-block
:让.st-inner
的宽度等于文本宽度,而不是独占容器宽度,这样才能准确判断是否超出;translate3d
:相比translate
,能触发 GPU 加速,减少滚动时的卡顿(大屏可能同时有多个动画,GPU 加速很有必要);10s linear
:10 秒匀速滚动一圈,速度适中,大屏远距离观看也能看清文字。
三、总结:这个组件的 3 大优势
- 智能省心:不用手动配置 "是否滚动",组件自动判断,文本没超出就安静待着,超出才滚动;
- 体验流畅:无缝衔接滚动 + 鼠标悬停暂停,大屏观看不晃眼、细看不费劲;
- 适配性强:支持动态文本、大屏缩放、旧浏览器,不用改代码就能应对不同场景。
大屏开发中,这类 "小而精" 的组件往往能极大提升开发效率和用户体验。如果你有其他文字滚动的需求(比如竖向滚动、滚动到末尾停止),也可以基于这个组件扩展 ------ 比如加一个 direction
props 控制滚动方向,加一个 stopAtEnd
props 控制是否停止在末尾。
你在大屏文字展示中还遇到过哪些问题?欢迎在评论区分享,我们一起优化~