大屏开发实战:封装自动判断、无缝衔接的文字滚动组件,告别文本截断烦恼

在大屏可视化项目中,文字展示看似简单,却藏着不少坑 ------ 比如公告栏文本过长超出容器、数据说明文字被截断、普通滚动组件要么卡顿要么衔接生硬,甚至鼠标悬停时还在 "自顾自" 滚动,体验极差。

今天就基于你的实战代码,拆解一套「大屏专属文字滚动组件」的实现方案:支持自动判断是否需要滚动 (文本没超出就不滚)、无缝衔接滚动 (避免断层)、鼠标悬停暂停(提升交互友好性),代码可直接复用,完美解决大屏文字展示的痛点。

一、大屏文字滚动的 3 大核心痛点

先聊聊大屏场景下文字滚动的 "糟心事",看看你是否也遇到过:

  1. "没必要" 的滚动:文本明明没超出容器,却强制滚动,显得多余又晃眼;
  2. "断层" 的衔接:滚动到末尾时,从最后一个字直接跳回开头,视觉上很突兀;
  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 大优势

  1. 智能省心:不用手动配置 "是否滚动",组件自动判断,文本没超出就安静待着,超出才滚动;
  2. 体验流畅:无缝衔接滚动 + 鼠标悬停暂停,大屏观看不晃眼、细看不费劲;
  3. 适配性强:支持动态文本、大屏缩放、旧浏览器,不用改代码就能应对不同场景。

大屏开发中,这类 "小而精" 的组件往往能极大提升开发效率和用户体验。如果你有其他文字滚动的需求(比如竖向滚动、滚动到末尾停止),也可以基于这个组件扩展 ------ 比如加一个 direction props 控制滚动方向,加一个 stopAtEnd props 控制是否停止在末尾。

你在大屏文字展示中还遇到过哪些问题?欢迎在评论区分享,我们一起优化~

相关推荐
Beginner x_u2 小时前
前端八股文 Vue上
前端·javascript·vue.js·八股文
Strawberry_rabbit2 小时前
Docker
前端
江拥羡橙2 小时前
JavaScript异步编程:告别回调地狱,拥抱Promise async/await
开发语言·javascript·ecmascript·promise·async/await
前端康师傅2 小时前
JavaScript数组中的陷阱
前端·javascript
用泥种荷花2 小时前
【web音频学习(七)】科大讯飞Web端语音合成
前端
月弦笙音2 小时前
【class 】static与 # 私有及static私有:系统梳理
前端·javascript·面试
forever_Mamba2 小时前
CSS隐藏页面元素
前端·css
云枫晖2 小时前
JS核心知识-对象继承
前端·javascript
w重名了1098822 小时前
记录一次gnvm切换node版本显示内存溢出的报错
前端·node.js