Vue3文字滚动(TextScroll)

64 篇文章 2 订阅
64 篇文章 3 订阅

可自定义设置以下属性: 

  • 滚动文字数组(scrollText),类型:Array<{title: string, link?: string}>|{title: string, link?: string},必传,默认[],滚动文字数组,single 为 true 时,类型为 Text

  • 是否启用单条文字滚动效果(single),只支持水平文字滚动,为 true 时,amount 自动设为 1,默认 false

  • 滚动区域宽度(width),类型:number | string,默认 '100%'

  • 滚动区域高度(height),类型:number,单位px,默认 60

  • 字体大小(fontSize),类型:number,单位px,默认 16

  • 字体粗细(fontWeight),类型:number,默认 400

  • 字体颜色(color),类型:string,默认 'rgba(0, 0, 0, .88)'

  • 滚动区域背景色(backgroundColor),类型:string,默认 '#FFF'

  • 滚动区域展示条数,水平滚动时生效(amount),类型:number,默认 4

  • 水平滚动文字各列间距或垂直滚动文字两边的边距(gap),类型:number,单位px,默认 20

  • 是否垂直滚动(vertical),类型:boolean,默认 false

  • 文字滚动时间间隔,垂直滚动时生效(interval),类型:number,单位ms,默认 3000

效果如下图: 在线预览

注:组件引用方法 import { requestAnimationFrame, cancelAnimationFrame, rafTimeout, cancelRaf } from '../index' 请参考以下博客: 

https://blog.csdn.net/Dandrose/article/details/130167061icon-default.png?t=N7T8https://blog.csdn.net/Dandrose/article/details/130167061

①创建文字滚动组件TextScroll.vue:

<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { requestAnimationFrame, cancelAnimationFrame, rafTimeout, cancelRaf } from '../index'
interface Text {
  title: string // 文字标题
  link?: string // 跳转链接
}
interface Props {
  scrollText: Text[]|Text // 滚动文字数组,single 为 true 时,类型为 Text
  single?: boolean // 是否启用单条文字滚动效果,只支持水平文字滚动,为 true 时,amount 自动设为 1
  width?: number|string // 滚动区域宽度,单位px
  height?: number // 滚动区域高度,单位px
  fontSize?: number // 字体大小
  fontWeight?: number // 字体粗细
  color?: string // 字体颜色
  backgroundColor?: string // 滚动区域背景色
  amount?: number // 滚动区域展示条数,水平滚动时生效
  gap?: number // 水平滚动文字各列间距或垂直滚动文字两边的边距,单位px
  vertical?: boolean // 是否垂直滚动
  interval?: number // 文字滚动时间间隔,单位ms,垂直滚动时生效
}
const props = withDefaults(defineProps<Props>(), {
  text: () => [],
  single: false,
  width: '100%',
  height: 60,
  fontSize: 16,
  fontWeight: 400,
  color: 'rgba(0, 0, 0, .88)',
  backgroundColor:  '#FFF',
  amount: 4,
  gap: 20,
  vertical: false,
  interval: 3000
})
// horizon
const left = ref(0)
const fpsRaf = ref(0) // fps回调标识
const moveRaf = ref() // 一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义
const fps = ref(60)
const textData = computed(() => {
  if (props.single) {
    return [props.scrollText, props.scrollText]
  } else {
    return [...(props.scrollText as Text[])]
  }
})
const len = computed(() => {
  return textData.value.length
})
const displayAmount = computed(() => {
  if (props.single) {
    return 1
  } else {
    return props.amount
  }
})
const step = computed(() => { // 移动参数(120fps: 0.5, 60fps: 1)
  if (fps.value === 60) {
    return 1
  } else {
    return 60 / fps.value
  }
})
watch(
  () => [textData, props.width, props.amount, props.gap, props.vertical, props.interval],
  () => {
    if (props.vertical) {
      timer.value && cancelRaf(timer.value)
      onStart() // 启动垂直滚动
    } else {
      getFPS()
    }
  },
  {
    deep: true, // 强制转成深层侦听器
    flush: 'post'
  }
)
const horizonRef = ref()
const distance = ref(0) // 每条滚动文字移动距离
function getFPS () { // 获取屏幕刷新率
  // @ts-ignore
  const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame
  var start: any = null
  function timeElapse (timestamp: number) {
    /*
      timestamp参数:与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻
    */
    if (!start) {
      if (fpsRaf.value > 10) {
        start = timestamp
      }
      fpsRaf.value = requestAnimationFrame(timeElapse)
    } else {
      fps.value = Math.floor(1000 / (timestamp - start))
      console.log('fps', fps.value)
      distance.value = getDistance() // 获取每列文字宽度
      onStart() // 开始滚动
      return
    }
  }
  fpsRaf.value = requestAnimationFrame(timeElapse)
}
function getDistance ():number {
  return parseFloat((horizonRef.value.offsetWidth / displayAmount.value).toFixed(2))
}
function moveLeft () {
  if (left.value >= distance.value) {
    textData.value.push(textData.value.shift() as Text) // 将第一条数据放到最后
    left.value = 0
  } else {
    left.value += step.value // 每次移动step(px)
  }
  moveRaf.value = requestAnimationFrame(moveLeft)
}
const totalWidth = computed(() => { // 文字滚动区域总宽度
  if (typeof props.width === 'number') {
    return props.width + 'px'
  } else {
    return props.width
  }
})
onMounted(() => {
  if (props.vertical) {
    onStart() // 启动垂直滚动
  } else {
    getFPS()
  }
})
function onStart () {
  if (props.vertical) {
    if (len.value > 1) {
      startMove() // 开始滚动
    }
  } else {
    if (textData.value.length > displayAmount.value) { // 超过amount条开始滚动
      moveRaf.value = requestAnimationFrame(moveLeft) // 开始动画
    }
  }
}
function onStop () {
  if (props.vertical) {
    if (len.value > 1) {
      cancelRaf(timer.value)
    }
  } else {
    cancelAnimationFrame(moveRaf.value) // 暂停动画
  }
}
const emit = defineEmits(['click'])
function onClick (title: string) { // 通知父组件点击的标题
  emit('click', title)
}

// vertical
const actIndex = ref(0)
var timer = ref<any>(null)
function startMove () {
  timer.value = rafTimeout(() => {
    if (actIndex.value === len.value - 1) {
      actIndex.value = 0
    } else {
      actIndex.value++
    }
    startMove()
  }, props.interval)
}
</script>
<template>
  <div
    v-if="!vertical"
    ref="horizonRef"
    class="m-slider-horizon"
    @mouseenter="onStop"
    @mouseleave="onStart"
    :style="`height: ${height}px; width: ${totalWidth}; background: ${backgroundColor}; --fontSize: ${fontSize}px; --fontWeight: ${fontWeight}; --color: ${color};`">
    <a
      :style="`will-change: transform; transform: translateX(${-left}px); width: ${distance - gap}px; margin-left: ${gap}px;`"
      class="u-slide-title"
      v-for="(text, index) in <Text[]>textData" :key="index"
      :title="text.title"
      :href="text.link ? text.link:'javascript:;'"
      :target="text.link ? '_blank':'_self'"
      @click="onClick(text.title)">
      {{ text.title || '--' }}
    </a>
  </div>
  <div
    v-else
    class="m-slider-vertical"
    @mouseenter="onStop"
    @mouseleave="onStart"
    :style="`height: ${height}px; width: ${totalWidth}; background: ${backgroundColor}; --fontSize: ${fontSize}px; --fontWeight: ${fontWeight}; --color: ${color};`">
    <TransitionGroup name="slide">
      <div
        class="m-slider"
        :style="`width: calc(${totalWidth} - ${2*gap}px); height: ${height}px;`"
        v-for="(text, index) in <Text[]>textData" :key="index"
        v-show="actIndex===index">
        <a
          class="u-slider"
          :title="text.title"
          :href="text.link ? text.link:'javascript:;'"
          :target="text.link ? '_blank':'_self'"
          @click="onClick(text.title)">
          {{ text.title || '--' }}
        </a>
      </div>
    </TransitionGroup>
  </div>
</template>
<style lang="less" scoped>
// 水平滚动
.m-slider-horizon {
  box-shadow: 0px 0px 5px #D3D3D3;
  border-radius: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-align: center; // 水平居中
  line-height: 1.5714285714285714;
  &::after { // 垂直居中
    content: '';
    height: 100%;
    display: inline-block;
    vertical-align: middle;
  }
  .u-slide-title {
    display: inline-block;
    vertical-align: middle;
    font-size: var(--fontSize);
    font-weight: var(--fontWeight);
    color: var(--color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    &:hover {
      color: @themeColor;
    }
  }
}

// 垂直滚动
.slide-enter-active, .slide-leave-active {
  transition: all 1s ease;
}
.slide-enter-from {
  transform: translateY(50px) scale(.5);
  opacity: 0;
}
.slide-leave-to {
  transform: translateY(-50px) scale(.5);
  opacity: 0;
}
.m-slider-vertical {
  position: relative;
  overflow: hidden;
  border-radius: 6px;
  line-height: 1.5714285714285714;
  .m-slider {
    position: absolute;
    left: 0;
    right: 0;
    margin: 0 auto;
    text-align: center; // 水平居中
    &::after { // 垂直居中
      content: '';
      height: 100%;
      display: inline-block;
      vertical-align: middle;
    }
    .u-slider {
      max-width: 100%;
      display: inline-block;
      vertical-align: middle;
      font-size: var(--fontSize);
      font-weight: var(--fontWeight);
      color: var(--color);
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      cursor: pointer;
      &:hover {
        color: @themeColor;
      }
    }
  }
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import TextScroll from './TextScroll.vue'
import { ref } from 'vue'
const scrollText = ref([
      {
        title: '美国作家杰罗姆·大卫·塞林格创作的唯一一部长篇小说',
        link: 'https://blog.csdn.net/Dandrose?type=blog'
      },
      {
        title: '首次出版于1951年'
      },
      {
        title: '塞林格将故事的起止局限于16岁的中学生霍尔顿·考尔菲德从离开学校到纽约游荡的三天时间内,塞林格将故事的起止局限于16岁的中学生霍尔顿·考尔菲德从离开学校到纽约游荡的三天时间内'
      },
      {
        title: '并借鉴了意识流天马行空的写作方法,充分探索了一个十几岁少年的内心世界'
      },
      {
        title: '愤怒与焦虑是此书的两大主题,主人公的经历和思想在青少年中引起强烈共鸣'
      }
    ])
const singleText = {
        title: '请用一只玫瑰纪念我...',
        link: 'https://blog.csdn.net/Dandrose?type=blog'
      }
function onClick (value: string) { // 获取点击的标题
  console.log('value:', value)
}
</script>
<template>
  <div>
    <h1>TextScroll 文字滚动</h1>
    <h2 class="mt30 mb10">水平多条文字滚动</h2>
    <TextScroll :scrollText="scrollText" :height="50" @click="onClick" />
    <h2 class="mt30 mb10">单条文字滚动</h2>
    <TextScroll :scrollText="singleText" single :width="300" :height="50" @click="onClick" />
    <h2 class="mt30 mb10">垂直多条文字滚动</h2>
    <TextScroll
      :scrollText="scrollText"
      vertical
      background-color="#e6f4ff"
      :font-size="18"
      :gap="60"
      :height="60"
      @click="onClick" />
  </div>
</template>
  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
您可以使用Vue3开发一个文字滚动公告组件。以下是一个简单的示例代码: ```vue <template> <div class="scroll-notice"> <div class="scroll-notice-content" ref="content"> <div v-for="(item, index) in notices" :key="index" class="scroll-notice-item"> {{ item }} </div> </div> </div> </template> <script> import { ref, watch, onMounted, onUnmounted } from 'vue'; export default { name: 'ScrollNotice', props: { notices: { type: Array, required: true, }, interval: { type: Number, default: 3000, }, }, setup(props) { const contentRef = ref(null); let scrollTimer = null; const startScroll = () => { const content = contentRef.value; if (content) { const firstItem = content.children[0]; const itemHeight = firstItem.offsetHeight; content.appendChild(firstItem.cloneNode(true)); scrollTimer = setInterval(() => { content.style.transition = 'transform 0.5s'; content.style.transform = `translateY(-${itemHeight}px)`; setTimeout(() => { content.style.transition = 'none'; content.style.transform = 'translateY(0)'; content.removeChild(content.children[0]); content.appendChild(firstItem.cloneNode(true)); }, 500); }, props.interval); } }; onMounted(() => { startScroll(); }); onUnmounted(() => { clearInterval(scrollTimer); }); watch( () => props.notices, () => { clearInterval(scrollTimer); startScroll(); } ); return { contentRef, }; }, }; </script> <style scoped> .scroll-notice { width: 100%; height: 100%; overflow: hidden; } .scroll-notice-content { display: flex; flex-direction: column; } .scroll-notice-item { margin-bottom: 10px; } </style> ``` 使用方式: ```vue <template> <div> <ScrollNotice :notices="notices" :interval="3000" /> </div> </template> <script> import ScrollNotice from '@/components/ScrollNotice.vue'; export default { components: { ScrollNotice, }, data() { return { notices: ['公告1', '公告2', '公告3'], }; }, }; </script> <style> /* 其他样式 */ </style> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值