AutoScroll 自动滚动组件文档

组件介绍

旨在为公告、新闻、实时数据、跑马灯等需要循环展示的场景提供优雅的解决方案。支持无缝衔接效果,确保内容在滚动时不会出现断层或闪烁。

INFO

通过灵活的配置项,可以轻松控制滚动速度、方向、延迟等参数,同时支持鼠标悬停暂停功能,提升用户体验。组件内置 Mac 风格滚动条,支持响应式布局,能够自动适应不同屏幕尺寸。无论是简单的文字公告还是复杂的动态内容,AutoScroll 都能以流畅的性能和简洁的 API 满足需求。此外,组件提供了完善的事件监听和状态管理机制,方便开发者实现更复杂的交互逻辑。

特性

  • 支持自动滚动和手动滚动切换
  • 支持上下滚动方向
  • 无缝衔接滚动效果
  • 支持鼠标悬停暂停
  • 自定义滚动速度和延迟
  • Mac 风格滚动条
  • 响应式自适应

安装使用

js
import AutoScroll from './AutoScroll.vue'

export default {
  components: { AutoScroll }
}

Props 配置项

参数说明类型默认值
speed滚动速度(px/s)Number50
scrollable是否启用自动滚动Booleantrue
hoverPause是否鼠标悬停暂停Booleantrue
direction滚动方向,可选值:'up'/'down'String'up'
delay开始滚动的延迟时间(ms)Number0
immediate是否立即开始滚动Booleantrue
manualScroll当 scrollable 为 false 时是否允许手动滚动Booleantrue

事件

事件名说明回调参数
pause滚动暂停时触发-
resume滚动恢复时触发-

使用示例

1. 基础自动滚动

TIP

鼠标悬停暂停,鼠标离开恢复

loading

2. 自定义速度和方向

vue
<auto-scroll :speed="400" direction="up">
  <!-- 内容 -->
</auto-scroll>

TIP

speeddirection 可以动态修改,案例中点击按钮改变滚动方向和速度

loading

3. 延迟开始滚动

vue
<auto-scroll :delay="1000" :immediate="false">
  <!-- 内容 -->
</auto-scroll>

TIP

延迟滚动需要配合 immediate 使用,immediate 为 false 时,需要手动点击按钮开始滚动,案例中点击按钮 1 秒钟后开始滚动

loading

4. 禁用鼠标悬停暂停

vue
<auto-scroll :hover-pause="false">
  <!-- 内容 -->
</auto-scroll>

TIP

hoverPause 为 false 时,鼠标悬停不会暂停滚动

loading

5. 手动滚动模式

vue
<auto-scroll :scrollable="false" :manual-scroll="true">
  <!-- 内容 -->
</auto-scroll>

TIP

scrollable 为 false 时,自动滚动关闭,manualScroll 为 true 时,允许使用鼠标滚轮手动滚动

loading

组件代码

点击查看完整代码
AutoScroll.vue
vue
<template>
  <div
    class="scroll-container"
    ref="container"
    @mouseenter="hoverPause && pauseScroll()"
    @mouseleave="hoverPause && resumeScroll()"
    :class="{ 'allow-scroll': !scrollable && manualScroll }"
  >
    <div class="scroll-content" :style="contentStyle" ref="content" :class="{ 'manual-scroll': !scrollable && manualScroll }">
      <div class="original-content" ref="original">
        <slot></slot>
      </div>
      <div v-if="needClone" class="clone-content" :style="cloneStyle">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'AutoScroll',
  props: {
    // 滚动速度(px/s)
    speed: {
      type: Number,
      default: 50
    },
    // 是否启用自动滚动
    scrollable: {
      type: Boolean,
      default: true
    },
    // 是否鼠标悬停暂停
    hoverPause: {
      type: Boolean,
      default: true
    },
    // 滚动方向:up/down
    direction: {
      type: String,
      default: 'up',
      validator: value => ['up', 'down'].includes(value)
    },
    // 滚动延迟时间(ms)
    delay: {
      type: Number,
      default: 0
    },
    // 是否立即开始滚动
    immediate: {
      type: Boolean,
      default: true
    },
    // 是否允许手动滚动(当scrollable为false时生效)
    manualScroll: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      scrollTop: 0,
      needClone: false,
      contentHeight: 0,
      containerHeight: 0,
      animationFrame: null,
      lastTimestamp: null,
      isPaused: false
    }
  },
  computed: {
    contentStyle() {
      const transform = `translateY(${this.direction === 'up' ? -this.scrollTop : this.scrollTop}px)`
      return {
        transform,
        transition: this.isPaused ? 'transform 0.3s' : 'none'
      }
    },
    cloneStyle() {
      return {
        position: 'absolute',
        left: 0,
        top: this.direction === 'up' ? '100%' : 0,
        transform: this.direction === 'up' ? 'none' : 'translateY(-100%)'
      }
    }
  },
  mounted() {
    if (this.immediate) {
      this.init()
    }
    window.addEventListener('resize', this.init)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.init)
    this.stopScroll()
  },
  methods: {
    init() {
      this.$nextTick(() => {
        this.containerHeight = this.$refs.container.offsetHeight
        const originalContent = this.$refs.original
        const contentHeight = originalContent ? originalContent.offsetHeight : 0

        if (contentHeight > this.containerHeight) {
          this.needClone = true
          this.contentHeight = contentHeight
          this.stopScroll()

          if (this.delay > 0) {
            setTimeout(() => this.startScroll(), this.delay)
          } else {
            this.startScroll()
          }
        } else {
          this.needClone = false
          this.stopScroll()
        }
      })
    },
    startScroll() {
      if (!this.scrollable || this.isPaused) return

      const animate = timestamp => {
        if (!this.lastTimestamp) {
          this.lastTimestamp = timestamp
        }

        const elapsed = timestamp - this.lastTimestamp
        this.lastTimestamp = timestamp

        const distance = (elapsed * this.speed) / 1000
        this.scrollTop += distance

        if (this.scrollTop >= this.contentHeight) {
          this.scrollTop = 0
        }

        this.animationFrame = requestAnimationFrame(animate)
      }

      this.animationFrame = requestAnimationFrame(animate)
    },
    stopScroll() {
      if (this.animationFrame) {
        cancelAnimationFrame(this.animationFrame)
        this.animationFrame = null
      }
      this.lastTimestamp = null
      this.scrollTop = 0
    },
    pauseScroll() {
      this.isPaused = true
      if (this.animationFrame) {
        cancelAnimationFrame(this.animationFrame)
        this.animationFrame = null
      }
      this.lastTimestamp = null
      this.$emit('pause')
    },
    resumeScroll() {
      this.isPaused = false
      this.startScroll()
      this.$emit('resume')
    },
    // 手动重新开始滚动
    restart() {
      this.stopScroll()
      this.init()
    }
  },
  watch: {
    immediate() {
      this.init()
    },
    speed() {
      this.restart()
    },
    direction() {
      this.restart()
    }
  }
}
</script>

<style scoped>
.scroll-container {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}

.scroll-content {
  will-change: transform;
  position: relative;
}

.original-content,
.clone-content {
  width: 100%;
}

/* 当允许手动滚动时,取消transform效果 */
.manual-scroll {
  transform: none !important;
}

/* 手动滚动时隐藏克隆内容 */
.allow-scroll .clone-content {
  display: none;
}

/* 允许手动滚动时的样式 */
.allow-scroll {
  overflow-y: overlay;
}

/* 自定义滚动条样式 */
.allow-scroll::-webkit-scrollbar {
  width: 8px;
  background-color: transparent;
  opacity: 0;
}

.allow-scroll:hover::-webkit-scrollbar {
  opacity: 1;
}

.allow-scroll::-webkit-scrollbar-track {
  background: transparent;
}

.allow-scroll::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 4px;
  border: 2px solid transparent;
  background-clip: content-box;
  opacity: 0;
  transition: opacity 0.3s;
}

.allow-scroll::-webkit-scrollbar-thumb:active,
.allow-scroll:hover::-webkit-scrollbar-thumb {
  opacity: 1;
}
</style>

注意事项

  1. 容器必须设置固定高度
  2. 列表内容高度需要大于容器高度才会触发滚动
  3. scrollable 为 false 时,manualScroll 控制是否允许手动滚动
  4. 滚动速度单位为 px/s,数值越大滚动越快
  5. 组件会自动监听容器大小变化并重新初始化

最佳实践

  1. 根据实际需求设置合适的滚动速度
  2. 建议开启 hoverPause,提升用户体验
  3. 如需延迟滚动,可以配合 delayimmediate 使用
  4. 手动滚动模式下建议保持默认的 Mac 风格滚动条样式