ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Vue3文字滚动(TextScroll)

Vue3文字滚动(TextScroll) 可自定义设置以下属性滚动文字数组items类型Item[] | Item默认 []single 为 true 时类型为 Item多条文字水平滚动时数组长度必须大于等于 amount 才能滚动是否启用单条文字滚动效果single类型boolean默认 false水平滚动时生效为 true 时amount 自动设为 1滚动区域宽度width类型string | number单位 px默认 100%滚动区域高度height类型number单位 px默认 50滚动文字样式itemStyle类型CSSProperties默认 {}链接文字鼠标悬浮颜色hrefHoverColor类型string默认 undefined仅当 href 存在时生效滚动区域展示条数amount类型number | false默认 4为 false 时所有文字平铺展示水平滚动时生效水平滚动文字各列间距或垂直滚动文字两边的间距gap类型number单位 px默认 20水平滚动时移动的速度speed类型number单位是像素每秒默认 48水平滚动时生效是否垂直滚动vertical类型boolean默认 false垂直滚动过渡持续时间duration类型number单位 ms默认 1000垂直滚动时生效垂直文字滚动时间间隔interval类型number单位 ms默认 3000垂直滚动时生效是否启用文本省略组件ellipsis类型boolean默认 falseEllipsis 组件属性配置ellipsisProps类型object参考 Ellipsis Props用于配置文本省略弹出提示鼠标移入是否暂停滚动pauseOnMouseEnter类型boolean默认 false效果如下图在线预览①创建文字滚动组件TextScroll.vue其中引入使用了以下组件和工具函数Vue3文本省略Ellipsis定时器 rafTimeout cancelRaf监听DOM尺寸 useResizeObserver获取依赖注入 useInject​​script setup langts import { ref, computed, watch, nextTick } from vue import type { CSSProperties } from vue import Ellipsis from components/ellipsis import { rafTimeout, cancelRaf, useResizeObserver, useInject } from components/utils export interface Item { title: string // 文字标题 href?: string // 跳转链接 target?: _self | _blank // 跳转链接打开方式href 存在时生效 } export interface Props { items?: Item[] | Item // 滚动文字数组single 为 true 时类型为 Item多条文字水平滚动时数组长度必须大于等于 amount 才能滚动 single?: boolean // 是否启用单条文字滚动效果水平滚动时生效为 true 时amount 自动设为 1 width?: number | string // 滚动区域宽度单位 px height?: number // 滚动区域高度单位 px itemStyle?: CSSProperties // 滚动文字样式 hrefHoverColor?: string // 链接文字鼠标悬浮颜色仅当 href 存在时生效 amount?: number | false // 滚动区域展示条数为 false 时所有文字平铺展示水平滚动时生效 gap?: number // 水平滚动文字各列间距或垂直滚动文字两边的间距单位 px speed?: number // 水平滚动时移动的速度单位是像素每秒水平滚动时生效 vertical?: boolean // 是否垂直滚动 duration?: number // 垂直滚动过渡持续时间单位 ms垂直滚动时生效 interval?: number // 垂直文字滚动时间间隔单位 ms垂直滚动时生效 ellipsis?: boolean // 是否启用文本省略组件 ellipsisProps?: object // Ellipsis 组件属性配置参考 Ellipsis Props用于配置文本省略弹出提示 pauseOnMouseEnter?: boolean // 鼠标移入是否暂停滚动 } const props withDefaults(definePropsProps(), { items: () [], single: false, width: 100%, height: 50, itemStyle: () ({}), hrefHoverColor: undefined, amount: 4, gap: 20, speed: 48, vertical: false, duration: 1000, interval: 3000, ellipsis: false, ellipsisProps: () ({}), pauseOnMouseEnter: false }) const horizontalRef ref() // 水平滚动 DOM 引用 const horizontalWrapWidth refnumber(0) // 水平滚动容器宽度 const verticalRef ref() // 垂直滚动 DOM 引用 const groupRef ref() // 水平滚动内容 DOM 引用 const groupWidth refnumber(0) // 水平滚动内容宽度 const playState refpaused | running(paused) // 水平滚动动画执行状态 const reset refboolean(true) // 重置水平滚动动画状态 const activeIndex refnumber(0) // 垂直滚动当前索引 const verticalMoveRaf ref() // 垂直滚动定时器引用标识 const originVertical refboolean(true) // 垂直滚动初始状态 const scrollItems refItem[]([]) // 滚动目标文字数组 const { colorPalettes } useInject(TextScroll) // 主题色注入 const emit defineEmits([click]) const itemsAmount computed(() { return scrollItems.value.length }) // 文字滚动区域尺寸样式 const scrollBoardStyle computed(() { return { width: typeof props.width number ? ${props.width}px : props.width, height: ${props.height}px } }) // 水平滚动时展示条数 const displayAmount computed(() { if (props.single) { return 1 } if (props.amount false) { return 0 } return props.amount }) const itemWidth computed(() { // 水平滚动单条文字宽度 if (props.amount false) { return auto } return ${parseFloat((horizontalWrapWidth.value / displayAmount.value).toFixed(2)) - props.gap}px }) const animationDuration computed(() { // 水平滚动动画持续时间 return groupWidth.value / props.speed }) watch( () props.items, () { if (props.single) { scrollItems.value [props.items] as Item[] } else { if (props.vertical (props.items as Item[]).length 1) { scrollItems.value [...(props.items as Item[]), ...(props.items as Item[])] } else { scrollItems.value [...(props.items as Item[])] } } }, { immediate: true, deep: true } ) watch(scrollItems, () { resetMove() }) watch( [() props.vertical, () props.duration, () props.interval], () { initScroll() }, { flush: post } ) useResizeObserver([horizontalRef, groupRef, verticalRef], () { initScroll() }) function initScroll(): void { verticalMoveRaf.value cancelRaf(verticalMoveRaf.value) if (!originVertical.value) { originVertical.value true } if (!props.vertical) { getScrollSize() } startMove() // 开始滚动 } // 获取水平滚动容器宽度水平滚动内容宽度 function getScrollSize(): void { horizontalWrapWidth.value horizontalRef.value.offsetWidth groupWidth.value groupRef.value.offsetWidth } // 重置水平滚动状态 function resetScrollState(): void { playState.value paused nextTick(() { void horizontalRef.value?.offsetTop // 强制浏览器触发重排 playState.value running }) } // 当 CSS Animation 运动到最后一帧时触发 function onAnimationIteration(): void { resetScrollState() } function verticalMove(): void { verticalMoveRaf.value rafTimeout( () { if (originVertical.value) { originVertical.value false } activeIndex.value (activeIndex.value 1) % itemsAmount.value }, originVertical.value ? props.interval : props.interval props.duration, true ) } function onClick(item: Item): void { emit(click, item) } // 滚动开始 function startMove(): void { if (props.vertical) { if (itemsAmount.value 1) { verticalMove() // 垂直滚动 } } else { if (itemsAmount.value displayAmount.value) { // 超过 amount 条开始滚动 reset.value false playState.value running // 水平滚动 } } } // 滚动暂停 function stopMove(): void { if (props.vertical) { originVertical.value true verticalMoveRaf.value cancelRaf(verticalMoveRaf.value) } else { playState.value paused } } // 滚动重置 function resetMove(): void { if (props.vertical) { verticalMoveRaf.value cancelRaf(verticalMoveRaf.value) if (activeIndex.value ! 0) { activeIndex.value 0 originVertical.value false } else { originVertical.value true } startMove() } else { playState.value paused reset.value true nextTick(() { void horizontalRef.value?.offsetTop startMove() }) } } defineExpose({ start: startMove, stop: stopMove, reset: resetMove }) /script template div v-if!vertical refhorizontalRef classtext-scroll-horizontal :style[ scrollBoardStyle, --text-scroll-shadow-color: #d3d3d3; --text-scroll-bg-color: #fff; --text-scroll-href-hover-color: ${hrefHoverColor || colorPalettes[5]}; --text-scroll-item-gap: ${gap}px; --text-scroll-play-state: ${playState}; --text-scroll-duration: ${animationDuration}s; --text-scroll-delay: 0s; --text-scroll-iteration-count: infinite; ] mouseenterpauseOnMouseEnter ? stopMove() : () false mouseleavepauseOnMouseEnter ? startMove() : () false div refgroupRef classscroll-items-group :class{ scroll-items-reset: reset } animationiterationonAnimationIteration component :isitem.href ? a : div classscroll-item :class{ href-item: !ellipsis item.href } :style[ellipsis ? null : itemStyle, width: ${itemWidth};] v-for(item, index) in scrollItems :keyindex :titleellipsis ? null : item.title :hrefitem.href :targetitem.target clickonClick(item) Ellipsis v-ifellipsis :max-widthitemWidth :content-class${item.href ? href-item : null} :content-styleitemStyle v-bindellipsisProps {{ item.title }} /Ellipsis template v-else{{ item.title }}/template /component /div div classscroll-items-group :class{ scroll-items-reset: reset } component :isitem.href ? a : div classscroll-item :class{ href-item: !ellipsis item.href } :style[ellipsis ? null : itemStyle, width: ${itemWidth};] v-for(item, index) in scrollItems :keyindex :titleellipsis ? null : item.title :hrefitem.href :targetitem.target clickonClick(item) Ellipsis v-ifellipsis :max-widthitemWidth :content-class${item.href ? href-item : null} :content-styleitemStyle v-bindellipsisProps {{ item.title }} /Ellipsis template v-else{{ item.title }}/template /component /div /div div v-else refverticalRef classtext-scroll-vertical :style[ scrollBoardStyle, --text-scroll-shadow-color: #d3d3d3; --text-scroll-bg-color: #fff; --text-scroll-href-hover-color: ${hrefHoverColor || colorPalettes[5]}; --text-scroll-duration: ${duration}ms; --text-scroll-timing-function: ease; --text-scroll-scale: 0.5; --text-scroll-item-padding: ${gap}px; ] mouseenterpauseOnMouseEnter ? stopMove() : () false mouseleavepauseOnMouseEnter ? startMove() : () false TransitionGroup nameslide div classscroll-item-wrap v-for(item, index) in scrollItems :keyindex v-showactiveIndex index component :isitem.href ? a : div classscroll-item :class{ href-item: !ellipsis item.href } :style!ellipsis ? itemStyle : {} :titleellipsis ? null : item.title :hrefitem.href :targetitem.target clickonClick(item) Ellipsis v-ifellipsis :content-class${item.href ? href-item : null} :content-style{ ...itemStyle, maxWidth: 100% } v-bindellipsisProps {{ item.title }} /Ellipsis template v-else{{ item.title }}/template /component /div /TransitionGroup /div /template style langless scoped // 水平滚动 .text-scroll-horizontal { overflow: hidden; display: flex; box-shadow: 0px 0px 5px var(--text-scroll-shadow-color); border-radius: 6px; background-color: var(--text-scroll-bg-color); .scroll-items-group { z-index: 1; display: flex; align-items: center; will-change: transform; animation: horizontalScroll var(--text-scroll-duration) linear var(--text-scroll-delay) var(--text-scroll-iteration-count); animation-play-state: var(--text-scroll-play-state); keyframes horizontalScroll { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } } .scroll-item { margin-left: var(--text-scroll-item-gap); font-size: 16px; font-weight: 400; color: rgba(0, 0, 0, 0.88); line-height: 1.57; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } :deep(.href-item) { cursor: pointer; transition: color 0.3s; :hover { color: var(--text-scroll-href-hover-color) !important; } .ellipsis-container { cursor: inherit; } } } .scroll-items-reset { animation: none; } } // 垂直滚动 .slide-enter-active, .slide-leave-active { transition: all var(--text-scroll-duration) var(--text-scroll-timing-function); } .slide-enter-from { transform: translateY(100%) scale(var(--text-scroll-scale)); opacity: 0; } .slide-leave-to { transform: translateY(-100%) scale(var(--text-scroll-scale)); opacity: 0; } .text-scroll-vertical { overflow: hidden; position: relative; box-shadow: 0px 0px 5px var(--text-scroll-shadow-color); border-radius: 6px; background-color: var(--text-scroll-bg-color); .scroll-item-wrap { position: absolute; left: 0; right: 0; height: 100%; display: flex; align-items: center; justify-content: center; padding: 0 var(--text-scroll-item-padding); .scroll-item { font-size: 16px; font-weight: 400; color: rgba(0, 0, 0, 0.88); line-height: 1.57; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } :deep(.href-item) { cursor: pointer; transition: color 0.3s; :hover { color: var(--text-scroll-href-hover-color) !important; } .ellipsis-container { cursor: inherit; } } } } /style其中引入使用了以下组件Vue3栅格GridVue3滑动输入条SliderVue3输入框InputVue3数字输入框InputNumberVue3弹性布局FlexVue3间距SpaceVue3按钮ButtonVue3开关SwitchVue3颜色选择器ColorPicker②在要使用的页面引入script setup langts import TextScroll from ./TextScroll.vue import { ref, reactive } from vue import type { TextScrollItem } from vue-amazing-ui const scrollItems refTextScrollItem[]([ { title: 美国作家杰罗姆·大卫·塞林格创作的唯一一部长篇小说, href: https://themusecatcher.blog.csdn.net, target: _blank }, { title: 《麦田里的守望者》首次出版于1951年, href: https://themusecatcher.blog.csdn.net, target: _blank }, { title: 塞林格将故事的起止局限于16岁的中学生霍尔顿·考尔菲德从离开学校到纽约游荡的三天时间内 }, { title: 并借鉴了意识流天马行空的写作方法充分探索了一个十几岁少年的内心世界, href: https://themusecatcher.blog.csdn.net, target: _blank }, { title: 愤怒与焦虑是此书的两大主题主人公的经历和思想在青少年中引起强烈共鸣, href: https://themusecatcher.blog.csdn.net, target: _blank } ]) const singleItem: TextScrollItem { title: 请用一只玫瑰纪念我 } const textScrollRef ref() const disabled refboolean(true) const vertical refboolean(false) const ellipsis refboolean(true) function onClick(item: TextScrollItem) { // 获取点击的 item console.log(item, item) } function handleStart() { textScrollRef.value.start() disabled.value true } function handleStop() { textScrollRef.value.stop() disabled.value false } function handleReset() { textScrollRef.value.reset() disabled.value true } const state reactive({ single: false, height: 50, fontSize: 16, fontWeight: 400, color: rgba(0, 0, 0, 0.88), backgroundColor: #fff, hrefHoverColor: #1677ff, amount: 4, gap: 20, speed: 48, vertical: false, duration: 1000, interval: 3000, ellipsis: true, pauseOnMouseEnter: true }) /script template div h1{{ $route.name }} {{ $route.meta.title }}/h1 h2 classmt30 mb10水平文字滚动/h2 TextScroll :itemsscrollItems clickonClick / h2 classmt30 mb10垂直文字滚动/h2 TextScroll stylebackground-color: #e6f4ff :itemsscrollItems :item-style{ fontSize: 20px } vertical clickonClick / h2 classmt30 mb10单条文字滚动/h2 Flex vertical TextScroll :itemssingleItem single :width300 :item-style{ fontSize: 24px, fontWeight: 600, color: darkred } clickonClick / TextScroll :items[singleItem] vertical :width300 :item-style{ fontSize: 24px, fontWeight: 600, color: darkred } clickonClick / /Flex h2 classmt30 mb10自定义样式/h2 TextScroll stylebackground-color: #e6f4ff; border-radius: 12px :itemsscrollItems :item-style{ fontSize: 20px, fontWeight: 500, color: #FF9800 } :height60 clickonClick / h2 classmt30 mb10链接悬浮色/h2 TextScroll :itemsscrollItems href-hover-color#ff6900 clickonClick / h2 classmt30 mb10展示条数和间距/h2 Flex vertical TextScroll :itemsscrollItems :amount3 :gap30 clickonClick / TextScroll :itemsscrollItems :amountfalse :gap30 clickonClick / /Flex h2 classmt30 mb10滚动速度/h2 Flex vertical TextScroll :itemsscrollItems :speed72 clickonClick / TextScroll :itemsscrollItems vertical :duration800 :interval2000 clickonClick / /Flex h2 classmt30 mb10文本省略弹出提示/h2 Flex vertical TextScroll :itemsscrollItems ellipsis pause-on-mouse-enter clickonClick / TextScroll :itemsscrollItems ellipsis pause-on-mouse-enter vertical clickonClick / /Flex h2 classmt30 mb10鼠标移入暂停/h2 Flex vertical TextScroll :itemsscrollItems pause-on-mouse-enter clickonClick / TextScroll :itemsscrollItems vertical pause-on-mouse-enter clickonClick / /Flex h2 classmt30 mb10使用 Methods/h2 Flex vertical Space vertical Space aligncenter vertical: Switch v-modelvertical / ellipsis: Switch v-modelellipsis / /Space Space Button typeprimary :disableddisabled clickhandleStart开始/Button Button clickhandleStop暂停/Button Button typeprimary ghost clickhandleReset重置/Button /Space /Space TextScroll reftextScrollRef :verticalvertical :ellipsisellipsis :itemsscrollItems clickonClick / /Flex h2 classmt30 mb10文字滚动配置器/h2 Flex vertical Row :gutter[24, 12] Col :span6 Flex gapsmall vertical height: Slider v-model:valuestate.height :min6 :max180 / /Flex /Col Col :span6 Flex gapsmall vertical fontSize: Slider v-model:valuestate.fontSize :min6 :max180 / /Flex /Col Col :span6 Flex gapsmall vertical fontWeight: InputNumber v-model:valuestate.fontWeight :step100 :min100 :max1000 / /Flex /Col Col :span6 Flex gapsmall vertical color: ColorPicker v-model:valuestate.color / /Flex /Col Col :span6 Flex gapsmall vertical backgroundColor: ColorPicker v-model:valuestate.backgroundColor / /Flex /Col Col :span6 Flex gapsmall vertical hrefHoverColor: ColorPicker v-model:valuestate.hrefHoverColor / /Flex /Col Col :span6 Flex gapsmall vertical amount: Slider v-model:valuestate.amount :min1 :maxscrollItems.length / /Flex /Col Col :span6 Flex gapsmall vertical gap: Slider v-model:valuestate.gap :min10 :max100 / /Flex /Col Col :span6 Flex gapsmall vertical speed: Slider v-model:valuestate.speed :min10 :max100 / /Flex /Col Col :span6 Space gapsmall vertical vertical: Switch v-modelstate.vertical / /Space /Col Col :span6 Flex gapsmall vertical duration: Slider v-model:valuestate.duration :min100 :step100 :max3000 / /Flex /Col Col :span6 Flex gapsmall vertical interval: Slider v-model:valuestate.interval :min1000 :step100 :max10000 / /Flex /Col Col :span6 Space gapsmall vertical ellipsis: Switch v-modelstate.ellipsis / /Space /Col Col :span6 Space gapsmall vertical pauseOnMouseEnter: Switch v-modelstate.pauseOnMouseEnter / /Space /Col /Row TextScroll :stylebackground-color: ${state.backgroundColor} :itemsscrollItems :singlestate.single :heightstate.height :item-style{ fontSize: state.fontSize px, fontWeight: state.fontWeight, color: state.color } :href-hover-colorstate.hrefHoverColor :amountstate.amount :gapstate.gap :speedstate.speed :verticalstate.vertical :durationstate.duration :intervalstate.interval :ellipsisstate.ellipsis :pause-on-mouse-enterstate.pauseOnMouseEnter clickonClick / /Flex /div /template
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表