跑马灯组件vue3+es6

复制代码
<template>
  <view v-if="visible" class="marquee-root">
    <!-- 横向滚动 -->
    <view class="marquee-root--row">
      <view
        class="marquee-root--track"
        :class="{
          'marquee-root--marquee-seamless': scroll && seamless,
          'marquee-root--marquee-non-seamless': scroll && !seamless,
          scrolling: scroll,
        }"
        :style="getTrackStyle()"
      >
        <view
          v-for="(chunk, chunkIdx) in topChunks"
          :key="'top-' + chunkIdx"
          class="marquee-root--content-chunk"
        >
          <view
            v-for="(item, idx) in mergedList"
            :key="'top-' + chunkIdx + '-' + idx"
            class="marquee-root--bullet-item"
          >
            <slot :item="item" :index="idx">
              <text class="marquee-root--bullet-text">{{ item.title }}</text>
            </slot>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

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

const props = defineProps({
  /** 数据数组;单项字段由外层插槽自行使用(如 title、avatar、name) */
  list: {
    type: Array,
    default: () => [],
  },
  
  /** 是否开启滚动 */
  scroll: {
    type: Boolean,
    default: true,
  },
  /** 是否无缝滚动(默认 false,无缝时会复制一份内容避免循环时跳动) */
  seamless: {
    type: Boolean,
    default: false,
  },
  /** 滚动速度,数值越大滚动越快,单位可理解为 px/s */
  speed: {
    type: Number,
    default: 60,
  },
  /** 是否显示组件,不显示时不渲染以优化性能 */
  visible: {
    type: Boolean,
    default: true,
  },
  /** 每列 item 间距(px) */
  itemGap: {
    type: Number,
    default: 24,
  },
})

// 内部合并后的列表,用于增量追加新数据
const mergedList = ref([])

// 无缝滚动时需要两份内容,非无缝时单份(循环会有可见跳动)
const topChunks = computed(() => (props.seamless ? [1, 2] : [1]))

// 动画状态控制
const isAnimating = ref(true)

// 动画时长与样式
const getTrackStyle = () => {
  if (!props.scroll || !isAnimating.value) {
    return {
      animationDuration: '0s',
      animationPlayState: 'paused',
      '--item-gap': `${props.itemGap}px`,
    }
  }
  return {
    animationDuration: `${800 / Math.max(1, props.speed)}s`,
    animationPlayState: 'running',
    '--item-gap': `${props.itemGap}px`,
  }
}

// 仅在 visible 时同步数据,避免不可见时不必要的计算
watch(
  () => [props.list, props.visible],
  ([newList, visible]) => {
    if (!visible) return
    if (!newList?.length) return
    //syncList(newList)
    mergedList.value = [...newList]
  },
  { immediate: true, deep: true }
)

onMounted(() => {
  //if (props.visible) syncList(props.list)
  if (props.visible) {
    isAnimating.value = true
  }
})

onUnmounted(() => {
  // 组件卸载时停止动画,避免内存泄漏和优化性能
  isAnimating.value = false
})
</script>

<style lang="less">
.marquee-root {
  width: 100%;
  height: 100%;
  overflow: hidden;
  &--row{
    height: 100%;
  overflow: hidden;
  
  }
  &--track {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  width: max-content;
  height: 100%;
  align-items: center;
  will-change: transform;
  white-space: nowrap;

  &.marquee-root--marquee-seamless {
    animation: scroll-seamless linear infinite;
  }

  &.marquee-root--marquee-non-seamless {
    animation: scroll-non-seamless linear infinite;
  }
}

&--content-chunk {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  height: 100%;
  flex-shrink: 0;
}

/* 黑色块:avatar 在前 + content 在后 */
&--bullet-item {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  height: 100%;
  flex-shrink: 0;
  margin-right: var(--item-gap, 24px);
  width: auto;
  box-sizing: border-box;
}

&--bullet-text {
  flex: 1;
  line-height: 24px;
  color: #0070bb;
  font-size: 24px;
}

}

@keyframes scroll-seamless {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

@keyframes scroll-non-seamless {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-100%);
  }
}

</style>
相关推荐
橙子家27 分钟前
浏览器缓存之【结构化数据库与缓存】: IndexedDB、Cache storage 和 Storage buckets
前端
user205855615181333 分钟前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州34 分钟前
CSS aspect-ratio 属性完全指南
前端
Pedantic2 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘3 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆3 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师4 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆4 小时前
VSCode自动格式化三要素
前端
爱勇宝5 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员