🔥🔥高效易用的 Vue3 公告滚动组件:打造丝滑的内容滚动体验(附源码)

在各类后台管理系统、营销页面或信息展示场景中,公告滚动是高频且基础的交互需求 ------ 既要实现内容自动向上滚动的展示效果,也要兼顾用户手动操作的灵活性。基于 Vue3 Setup 语法糖封装的这款公告滚动组件,完美平衡了「自动展示」与「手动交互」的需求,为前端开发提供了开箱即用、高度可定制的解决方案。

核心特性:兼顾体验与灵活性

这款组件围绕 "用户体验优先" 设计,核心功能覆盖场景全、交互细节拉满:

  1. 丝滑自动滚动 :支持像素级缓慢向上滚动,滚动条与内容同步移动,滚到底部后无缝重置至顶部,避免内容断层;可通过autoScrollSpeed参数自定义滚动速度(默认 1px / 帧),兼顾展示效率与视觉舒适度。
  2. 灵活的手动交互
    • 鼠标悬浮即时暂停滚动,移出后立即恢复,方便用户聚焦查看单条公告;
    • 滚轮操作大幅提速(默认单次滚动 40px),可通过wheelStep参数调整灵敏度,满足快速浏览需求;
    • 保留原生滚动条并支持自定义样式,手动拖拽滚动条后 1 秒自动恢复滚动,兼顾不同操作习惯。
  3. 响应式与兼容性:监听公告列表数据变化,数据更新后自动重新计算高度并重启滚动;兼容 Chrome、Firefox、Edge 等现代浏览器,适配不同内核的滚动条样式。
  4. 轻量且易扩展:无第三方依赖,基于 Vue3 原生 API 开发;组件样式、容器宽高可通过外部样式灵活覆盖,无需修改源码即可适配不同 UI 风格。

快速上手:极简集成,开箱即用

组件采用 Vue3 Setup 语法糖开发,集成流程极简:

  1. 引入组件 :将NoticeScroll.vue文件放入项目组件目录,在需要使用的页面直接导入;
  2. 传入数据 :仅需传递核心参数list(公告列表数组),即可启动基础滚动功能;
  3. 定制化配置 (可选):通过autoScrollSpeed(滚动速度)、wheelStep(滚轮步长)、pauseOnHover(悬浮暂停)等参数,快速适配业务场景。

示例代码简洁直观:

vue

ini 复制代码
<NoticeScroll
  :list="noticeList"
  :autoScrollSpeed="1"
  :wheelStep="40"
  style="width: 500px; height: 200px;"
/>

适用场景:覆盖多类信息展示需求

无论是后台系统的系统公告、电商页面的营销通知,还是资讯类产品的动态资讯,这款组件都能适配 ------ 既满足 "无人操作时自动轮播展示" 的基础需求,也解决了 "用户想手动快速浏览 / 聚焦查看" 的交互痛点。组件内置的内存泄漏防护(卸载时清理定时器、事件监听),也保证了在复杂页面中使用的稳定性。

代码如下:

xml 复制代码
<template>
  <div 
    class="notice-scroll-wrapper"
    ref="wrapperRef"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    @wheel="handleWheel"
  >
    <ul class="notice-scroll-list">
      <li 
        class="notice-scroll-item"
        v-for="(item, index) in list"
        :key="index"
      >
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';

// 定义组件属性
const props = defineProps({
  // 公告列表数据
  list: {
    type: Array,
    required: true,
    default: () => []
  },
  // 自动滚动速度(像素/帧)
  autoScrollSpeed: {
    type: Number,
    default: 1
  },
  // 滚轮单次滚动步长(像素)
  wheelStep: {
    type: Number,
    default: 40
  },
  // 帧率(建议60)
  frameRate: {
    type: Number,
    default: 60
  },
  // 鼠标悬浮是否暂停
  pauseOnHover: {
    type: Boolean,
    default: true
  }
});

// DOM 引用
const wrapperRef = ref(null);
// 状态变量
const timer = ref(null); // 自动滚动定时器
const autoScrollTimer = ref(null); // 滚轮/滚动条后恢复定时器
const isHover = ref(false); // 是否悬浮
const wrapperH = ref(0); // 容器高度
const contentH = ref(0); // 内容总高度
const maxScrollTop = ref(0); // 滚动条最大位置

// 初始化
const init = () => {
  if (!wrapperRef.value) return;
  // 计算容器/内容高度
  wrapperH.value = wrapperRef.value.offsetHeight;
  contentH.value = wrapperRef.value.querySelector('.notice-scroll-list').offsetHeight;
  maxScrollTop.value = contentH.value - wrapperH.value;
  // 启动自动滚动
  startAutoScroll();
};

// 启动自动滚动
const startAutoScroll = () => {
  // 内容高度 ≤ 容器高度时,不滚动
  if (contentH.value <= wrapperH.value) return;
  // 清除旧定时器
  clearInterval(timer.value);
  // 逐帧更新滚动条位置
  timer.value = setInterval(() => {
    let current = wrapperRef.value.scrollTop;
    current += props.autoScrollSpeed;
    // 无缝重置:滚到底部回到顶部
    if (current >= maxScrollTop.value) {
      current = 0;
    }
    wrapperRef.value.scrollTop = current;
  }, 1000 / props.frameRate);
};

// 停止自动滚动
const stopAutoScroll = () => {
  clearInterval(timer.value);
  timer.value = null;
};

// 鼠标移入:暂停滚动
const handleMouseEnter = () => {
  if (!props.pauseOnHover) return;
  isHover.value = true;
  stopAutoScroll();
};

// 鼠标移出:恢复滚动
const handleMouseLeave = () => {
  if (!props.pauseOnHover) return;
  isHover.value = false;
  startAutoScroll();
};

// 滚轮控制:提速滚动
const handleWheel = (e) => {
  e.preventDefault();
  // 暂停自动滚动
  stopAutoScroll();
  // 计算新滚动位置
  const newScrollTop = wrapperRef.value.scrollTop + (e.deltaY > 0 ? props.wheelStep : -props.wheelStep);
  // 边界限制
  wrapperRef.value.scrollTop = Math.max(0, Math.min(newScrollTop, maxScrollTop.value));
  // 1秒后恢复自动滚动
  clearTimeout(autoScrollTimer.value);
  autoScrollTimer.value = setTimeout(() => {
    if (!isHover.value) startAutoScroll();
  }, 1000);
};

// 监听滚动条手动拖动:恢复自动滚动
const handleScroll = () => {
  // 排除自动滚动触发的scroll事件
  if (timer.value) return;
  stopAutoScroll();
  clearTimeout(autoScrollTimer.value);
  autoScrollTimer.value = setTimeout(() => {
    if (!isHover.value) startAutoScroll();
  }, 1000);
};

// 监听列表数据变化:重新初始化
watch(
  () => props.list,
  () => {
    // 清除旧定时器
    clearInterval(timer.value);
    clearTimeout(autoScrollTimer.value);
    // 重新计算高度并启动
    setTimeout(init, 0); // 异步等待DOM更新
  },
  { deep: true }
);

// 生命周期:挂载时初始化
onMounted(() => {
  init();
  // 绑定滚动条拖动事件
  if (wrapperRef.value) {
    wrapperRef.value.addEventListener('scroll', handleScroll);
  }
});

// 生命周期:卸载时清理
onUnmounted(() => {
  clearInterval(timer.value);
  clearTimeout(autoScrollTimer.value);
  if (wrapperRef.value) {
    wrapperRef.value.removeEventListener('scroll', handleScroll);
  }
});
</script>

<style scoped>
.notice-scroll-wrapper {
  width: 100%;
  height: 200px; /* 默认高度,可通过父组件覆盖 */
  border: 1px solid #e5e5e5;
  border-radius: 4px;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin; /* Firefox 滚动条样式 */
  scrollbar-color: #ccc #f5f5f5;
}

/* 自定义滚动条 - Chrome/Edge/Safari */
.notice-scroll-wrapper::-webkit-scrollbar {
  width: 6px;
}
.notice-scroll-wrapper::-webkit-scrollbar-track {
  background: #f5f5f5;
  border-radius: 3px;
}
.notice-scroll-wrapper::-webkit-scrollbar-thumb {
  background: #ccc;
  border-radius: 3px;
}
.notice-scroll-wrapper::-webkit-scrollbar-thumb:hover {
  background: #999;
}

.notice-scroll-list {
  list-style: none;
  padding: 0 20px;
  margin: 0;
}

.notice-scroll-item {
  line-height: 1.6;
  padding: 8px 0;
  color: #333;
  word-break: break-all;
}

.notice-scroll-item:hover {
  color: #1890ff;
  transition: color 0.2s;
}
</style>

调用示例:

xml 复制代码
<template>
  <div class="demo-container">
    <h3>公告滚动示例</h3>
    <!-- 使用公告滚动组件 -->
    <NoticeScroll
      :list="noticeList"
      :autoScrollSpeed="1"
      :wheelStep="40"
      :pauseOnHover="true"
      style="width: 500px; height: 200px;"
    />
  </div>
</template>

<script setup>
import NoticeScroll from './NoticeScroll.vue';

// 公告列表数据
const noticeList = [
  '【公告1】系统将于2025-12-30 23:00进行维护升级,预计耗时2小时,维护期间将暂停所有线上服务,敬请谅解。',
  '【公告2】新用户注册即可领取88元新人礼包,包含5张满减券+1张免运费券,有效期7天,仅限首次注册用户使用。',
  '【公告3】企业版新增数据导出功能,支持Excel/PDF格式,可导出近3个月的客户跟进数据、成交数据、报表数据等。',
  '【公告4】本周累计成交满10000元,可享9折优惠,优惠可叠加会员权益,活动截止至2025-12-31。',
  '【公告5】移动端APP已更新至v2.8.0版本,新增扫码核销、离线缓存功能,建议所有用户及时升级。',
  '【公告6】客服工作时间调整为9:00-22:00,节假日正常值班,如有问题可随时联系在线客服。',
  '【公告7】会员等级体系升级,新增钻石会员等级,可享专属客服、优先发货等权益。'
];
</script>

<style scoped>
.demo-container {
  padding: 50px;
}
</style>
相关推荐
编程修仙2 小时前
第二篇 Vue指令
前端·javascript·vue.js·前端框架
frontend丶CV2 小时前
useMemo
前端·react.js
明月_清风2 小时前
基于 node-rtsp-stream 的 Web 直播方案详解
前端
DEMO派2 小时前
前端处理用户离开当前页面的方案及对比解析
前端
LFly_ice2 小时前
Next-4-路由导航
开发语言·前端·javascript
chilavert3182 小时前
技术演进中的开发沉思-267 Ajax:拖放功能
前端·javascript·ajax
守护砂之国泰裤辣2 小时前
el-select 选项偏移 到左边 左上角
前端·javascript·vue.js
明月_清风2 小时前
Chrome 插件开发科普:从零开始打造你的浏览器小工具
前端
若梦plus2 小时前
Node.js之TypeScript支持
前端·typescript