[组件封装]Vue3无缝滚动组件,支持鼠标悬浮停止,且上下左右四个方向使用Vue3技术开发,支持上下左右四个方向的移动更
前言
大家好,我是辉夜真是太可爱啦。
文本主要讲述封装一个无限滚动展示的组件,效果图如下,也可以传入字符串,和中奖播报一样。
使用Vue3技术开发,支持上下左右四个方向的移动更改,并且支持鼠标悬浮停止,还有速度的调节,为了方便过渡效果,在最右侧做了一个遮罩。并且已封装为一个组件。
下面,我们将从源码分享,组件的使用,开发思路等三个方面来给大家讲讲这个组件。
组件使用
<template>
<InfiniteScroll :content="urlList">
<template v-slot="{ item}">
<img :src="item.url" alt="" />
</template>
</InfiniteScroll>
</template>
<script setup>
import InfiniteScroll from '@/components/InfiniteScroll.vue'
let urlList = [
{
url: '',
},
// ...
]
- content 传入数组或者字符串
- direction 可控制方向,上下左右
- mask 是否显示遮罩
- speedRate 控制速率
源码
下面就是组件的整体代码:
<template>
<div
ref="scrollBox"
:class="`infinite-scroll-component-box-${direction}`"
:style="{
'--speed-': `${speed}s`,
mask: `linear-gradient(${degList[direction]}, #000 70%, transparent)`,
}"
>
<div class="scroll-content" ref="scrollContent">
<template v-if="Array.isArray(content)">
<div
v-for="(item, index) in content"
:key="'arrayFirst' + index"
class="loop-item"
>
<slot :item="item" :index="index"></slot>
</div>
<template v-if="loop">
<div
v-for="(item, index) in content"
class="loop-item"
:key="'arrayCopy' + index"
>
<slot :item="item" :index="index"></slot>
</div>
</template>
</template>
<template v-else>
<slot></slot>
<slot v-if="loop"></slot>
</template>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, nextTick, defineProps } from 'vue'
const props = defineProps({
direction: {
type: String,
default: 'left',
validate: (value) =>
['left', 'top', 'right', 'bottom'].findIndex(value) !== -1,
},
content: {
type: [String, Array],
default: '',
},
mask: {
type: Boolean,
default: true,
},
// 速率倍速,越大越快,默认值20
speedRate: {
type: Number,
default: 20,
},
})
onMounted(() => {
init()
})
let degList = {
left: '90deg',
top: '180deg',
right: '270deg',
bottom: '0deg',
}
// 滚动速度
let speed = ref(0)
// 是否需要无限滚动
let loop = ref(false)
// 容器ref
let scrollBox = ref()
watch(
() => props.content,
() => {
loop.value = false
init()
},
{ deep: true }
)
// 初始化速度,以及是否需要无限滚动
const init = async () => {
await nextTick()
if (props.direction === 'left' || props.direction === 'right') {
// 可视区域的宽度
const boxWidth = scrollBox.value.offsetWidth
// 滚动内容的宽度
const itemWidth =
scrollBox.value.getElementsByClassName('scroll-content')[0].offsetWidth
if (itemWidth >= boxWidth) {
loop.value = true
speed.value = itemWidth / props.speedRate
} else {
speed.value = 0
scrollBox.value.getElementsByClassName(
'scroll-content'
)[0].style.transform = 'translateX(0)'
loop.value = false
}
} else {
const boxHeight = scrollBox.value.offsetHeight
const itemHeight =
scrollBox.value.getElementsByClassName('scroll-content')[0].offsetHeight
if (itemHeight >= boxHeight) {
loop.value = true
speed.value = itemHeight / props.speedRate
} else {
speed.value = 0
scrollBox.value.getElementsByClassName(
'scroll-content'
)[0].style.transform = 'translateY(0)'
loop.value = false
}
}
}
</script>
<style lang="scss" scoped>
.infinite-scroll-component-box-left,
.infinite-scroll-component-box-right {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.scroll-content {
position: absolute;
left: 0;
display: flex;
align-items: center;
justify-content: center;
&:hover {
animation-play-state: paused;
}
}
}
.infinite-scroll-component-box-top,
.infinite-scroll-component-box-bottom {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.scroll-content {
width: 100%;
position: absolute;
left: 0;
display: flex;
align-items: center;
justify-content: center;
&:hover {
animation-play-state: paused;
}
.loop-item {
width: 100%;
}
}
}
.infinite-scroll-component-box-left .scroll-content {
flex-direction: row;
animation: var(--speed-) move-left linear infinite;
@keyframes move-left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
}
.infinite-scroll-component-box-right .scroll-content {
flex-direction: row-reverse;
animation: var(--speed-) move-right linear infinite;
@keyframes move-right {
0% {
transform: translateX(-50%);
}
100% {
transform: translateX(0);
}
}
}
.infinite-scroll-component-box-top .scroll-content {
flex-direction: column;
animation: var(--speed-) move-top linear infinite;
@keyframes move-top {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
}
.infinite-scroll-component-box-bottom .scroll-content {
flex-direction: column-reverse;
animation: var(--speed-) move-bottom linear infinite;
@keyframes move-bottom {
0% {
transform: translateY(-50%);
}
100% {
transform: translateY(0);
}
}
}
</style>
开发思路
下面的思路都以方向为 left
作为
1. 滚动动画
实现滚动的思路主要有js和css,但是css相对更加方便,因为可以使用 css3
的 animation-play-state: paused
让动画停止。将它加在元素的 :hover
伪类上即可实现悬浮停止。
2. 无缝滚动
实现无限滚动的思路,首先就是需要将现有的元素重复一遍, slot
插槽用于插入父组件中的元素,当我们写两遍 slot 时,即可轻松实现元素的复制操作。
我们将0-5的元素进行复制一遍,即可得到下图,黑框为可见区域。
当我们从第一排的0开始滚动,当第二排的0滚动到可视区域的头部时,我们直接返回到图1的状态,即可实现无缝滚动啦。
那么,按照这个思路,也就是总元素从 transform: translateX(0);
状态开始滚动,当滚动到 transform: translateX(-50%);
之后,将它再次还原到 transform: translateX(0);
那就是以下动画:
@keyframes move-left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
为了让动画不断重复,那就是 animation: var(--speed-) move-left linear infinite;
3. 是否需要无限滚动
我们考虑一下,当我们复制了之后,总元素的宽度仍然不及可视区域的宽度时,我们都不需要滚动。
所以我们使用了一个 loop
变量来控制是否需要滚动。
4. 过渡更加平滑
为了让过渡更加平滑,这个也是现在很多无缝滚动都使用的效果,那就是右侧增加一个渐隐的效果。
我们使用的是 mask
属性,mask: linear-gradient(${degList[direction]}, #000 70%, transparent)
在 mask
属性中,有颜色代表可见, transparent
代表不可见。
然后为了过渡平滑,我们使用了一个渐变来让过渡更加的平滑。
5. 均匀的速度
还有一个问题,animation
设置的动画秒数如果通过 props
传入,很明显会有问题。
因为当秒数固定,然后元素内容的长度不固定时,比方说秒数控制在10s, content
长度为10时,1s一个,content
长度为20时,0.5s一个,会造成数据不一样的时候,无缝滚动的速度也不一样。
所以, props
不能传入秒数,而传入一个速率,不同长度,它的动画完成时间也不一样。
代码如下, speed.value = itemWidth / props.speedRate
转载自:https://juejin.cn/post/7388635438741159945