基于vue.js實(shí)現(xiàn)無(wú)縫滾動(dòng)的兩種方法

方法一:基于requestAnimationFrame
demo
<template>
<h-page-container class="hoem-page">
<h1>無(wú)縫滾動(dòng)</h1>
<h2>垂直方向</h2>
<div class="container1">
<AutoScroll :data="list" :item-height="110" :limit-move-num="3" :is-rem="false">
<template #item="{ keySuffix }">
<div v-for="(item, index) in list" :key="`${item.id || index}${keySuffix || ''}`" class="card">
<div>{{ item.title }}</div>
<div>{{ item.content }}</div>
</div>
</template>
</AutoScroll>
</div>
<h2>水平方向</h2>
<div class="container2">
<AutoScroll :data="list" :direction="2" :item-width="210" :limit-move-num="3" :is-rem="false">
<template #item="{ keySuffix }">
<div v-for="(item, index) in list" :key="`${item.id || index}${keySuffix || ''}`" class="card">
<div>{{ item.title }}</div>
<div>{{ item.content }}</div>
</div>
</template>
</AutoScroll>
</div>
</h-page-container>
</template>
<script setup>
import { ref } from 'vue'
import AutoScroll from '@/components/AutoScroll.vue'
const list = ref([
{
id: 1,
title: '卡片1',
content: '111'
},
{
id: 2,
title: '卡片2',
content: '222'
},
{
id: 3,
title: '卡片3',
content: '333'
},
{
id: 4,
title: '卡片4',
content: '444'
}
])
</script>
<style lang="scss" scoped>
.hoem-page {
width: 100%;
height: 100vh;
padding: 10px;
}
.container1 {
width: 200px;
height: 300px;
margin: 20px;
overflow: hidden;
.card {
width: 100%;
height: 100px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 10px;
padding: 10px;
}
}
.container2 {
width: 500px;
height: 150px;
margin: 20px;
overflow: hidden;
.card {
width: 200px;
height: 100%;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-right: 10px;
padding: 10px;
}
}
</style>
AutoScroll.vue
<template>
<div class="scroll-list" :style="listStyle" @mouseover="pauseAnimation" @mouseout="animate">
<slot name="item" key-suffix=""></slot> <!-- 原始內(nèi)容 -->
<template v-if="shouldDuplicate">
<slot name="item" key-suffix="_copy"></slot> <!-- 復(fù)制內(nèi)容,添加后綴 -->
</template>
</div>
</template>
<script setup>
import { ref, onBeforeUnmount, watch, computed } from 'vue'
// 定義滾動(dòng)方向枚舉
const Direction = {
DOWN: 0,
UP: 1,
LEFT: 2,
RIGHT: 3
}
const props = defineProps({
// 列表
data: {
type: Array,
default: () => []
},
// 方向: 0 往下 1 往上 2 向左 3 向右
direction: {
type: Number,
default: 1
},
/**
* 一個(gè)列表元素的高度(包含外邊距)
* direction為0 往下 1 往上時(shí)
*/
itemHeight: {
type: Number,
default: null
},
/**
* 一個(gè)列表元素的寬度(包含外邊距)
* direction為2 向左 3 向右時(shí)
*/
itemWidth: {
type: Number,
default: null
},
// itemHeight的單位是否是rem
isRem: {
type: Boolean,
default: true
},
// 開(kāi)啟無(wú)縫滾動(dòng)的數(shù)據(jù)量。
limitMoveNum: {
type: Number,
default: 5
},
// 幾列
columns: {
type: Number,
default: 1
}
})
// 是否需要復(fù)制內(nèi)容
const shouldDuplicate = computed(() => props.data.length >= props.limitMoveNum)
const requestId = ref(null)
const offset = ref(0)
// 判斷是否為垂直方向
const isVertical = computed(() => props.direction === Direction.DOWN || props.direction === Direction.UP)
// 計(jì)算列表樣式
const listStyle = computed(() => ({
transform: `${isVertical.value ? 'translateY' : 'translateX'}(${offset.value}${props.isRem ? 'rem' : 'px'})`,
display: isVertical.value ? 'block' : 'flex'
}))
// 計(jì)算最大偏移量
const maxOffset = computed(() => {
return Math.ceil(props.data.length / props.columns) *
(isVertical.value ? props.itemHeight : props.itemWidth)
})
// 開(kāi)始動(dòng)畫(huà)
const animate = () => {
if (props.data?.length < props.limitMoveNum) return
requestId.value = requestAnimationFrame(() => {
animate()
offset.value += (props.direction === Direction.UP || props.direction === Direction.LEFT) ? -0.3 : 0.3
// 當(dāng)滾動(dòng)完一輪后重置位置
if (Math.abs(offset.value) >= maxOffset.value) {
offset.value = 0
}
})
}
// 暫停動(dòng)畫(huà)
const pauseAnimation = () => {
if (requestId.value) {
cancelAnimationFrame(requestId.value)
requestId.value = null
}
}
watch(() => props.data, (val) => {
if (val?.length >= props.limitMoveNum) {
pauseAnimation()
animate()
}
}, {
immediate: true
})
onBeforeUnmount(() => {
pauseAnimation()
})
</script>
<style lang="scss" scoped>
.scroll-list {
width: 100%;
height: 100%;
/* 確保動(dòng)畫(huà)在合成層運(yùn)行 */
backface-visibility: hidden;
&>* {
flex-grow: 0;
flex-shrink: 0;
}
}
</style>
**
方法二:基于animation動(dòng)畫(huà)
**
demo
<template>
<h-page-container class="hoem-page">
<h1>無(wú)縫滾動(dòng)</h1>
<h2>垂直方向</h2>
<div class="container1">
<AutoScroll :data="list" :item-height="110" :limit-move-num="3">
<template #item="{ keySuffix }">
<div v-for="(item, index) in list" :key="`${item.id || index}${keySuffix || ''}`" class="card">
<div>{{ item.title }}</div>
<div>{{ item.content }}</div>
</div>
</template>
</AutoScroll>
</div>
<h2>水平方向</h2>
<div class="container2">
<AutoScroll :data="list" :direction="2" :item-width="210" :limit-move-num="3">
<template #item="{ keySuffix }">
<div v-for="(item, index) in list" :key="`${item.id || index}${keySuffix || ''}`" class="card">
<div>{{ item.title }}</div>
<div>{{ item.content }}</div>
</div>
</template>
</AutoScroll>
</div>
</h-page-container>
</template>
<script setup>
import { ref } from 'vue'
import AutoScroll from '@/components/AutoScroll.vue'
const list = ref([
{
id: 1,
title: '卡片1',
content: '111'
},
{
id: 2,
title: '卡片2',
content: '222'
},
{
id: 3,
title: '卡片3',
content: '333'
},
{
id: 4,
title: '卡片4',
content: '444'
}
])
</script>
<style lang="scss" scoped>
.hoem-page {
width: 100%;
height: 100vh;
padding: 10px;
}
.container1 {
width: 200px;
height: 300px;
margin: 20px;
overflow: hidden;
.card {
width: 100%;
height: 100px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 10px;
padding: 10px;
}
}
.container2 {
width: 500px;
height: 150px;
margin: 20px;
overflow: hidden;
.card {
width: 200px;
height: 100%;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-right: 10px;
padding: 10px;
}
}
</style>
AutoScroll.vue
<template>
<div class="auto-scroll" :class="scrollClass" @mouseenter="setScrollPause(true)" @mouseleave="setScrollPause(false)">
<div ref="scrollContent" class="scroll-content" :style="contentStyle">
<slot name="item" :key-suffix="''"></slot>
<template v-if="shouldDuplicate">
<slot name="item" :key-suffix="'_copy'"></slot>
</template>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
const props = defineProps({
// 列表
data: {
type: Array,
default: () => []
},
// 方向: 0 往下 1 往上 2 向左 3 向右
direction: {
type: Number,
default: 0
},
/**
* 一個(gè)列表元素的高度(包含外邊距)
* direction為0 往下 1 往上時(shí)
*/
itemHeight: {
type: Number,
default: 40
},
/**
* 一個(gè)列表元素的寬度(包含外邊距)
* direction為2 向左 3 向右時(shí)
*/
itemWidth: {
type: Number,
default: 100
},
// 開(kāi)啟無(wú)縫滾動(dòng)的數(shù)據(jù)量。
limitMoveNum: {
type: Number,
default: 5
},
// 完整滾動(dòng)周期(秒)
duration: {
type: Number,
default: 10
},
// 鼠標(biāo)懸浮暫停
hoverPause: {
type: Boolean,
default: true
}
})
const scrollContent = ref(null)
const isPaused = ref(false)
// 判斷是否為垂直方向
const isVertical = computed(() => props.direction <= 1)
// 是否需要復(fù)制內(nèi)容
const shouldDuplicate = computed(() => props.data.length >= props.limitMoveNum)
// 滾動(dòng)方向
const scrollClass = computed(() => [
`direction-${props.direction}`,
isVertical.value ? 'vertical' : 'horizontal'
])
// 內(nèi)容樣式計(jì)算
const contentStyle = computed(() => {
const sizeProp = isVertical.value ? 'height' : 'width'
const itemSize = isVertical.value ? props.itemHeight : props.itemWidth
const contentSize = props.data.length * itemSize
return {
[sizeProp]: shouldDuplicate.value ? `${contentSize * 2}px` : `${contentSize}px`,
'animation-duration': `${props.duration}s`,
'animation-play-state': isPaused.value ? 'paused' : 'running'
}
})
// 動(dòng)畫(huà)控制
const setScrollPause = (paused) => {
if (props.hoverPause) {
isPaused.value = paused
}
}
// 生命周期
onMounted(() => {
if (shouldDuplicate.value) {
setScrollPause(false)
}
})
onBeforeUnmount(() => {
setScrollPause(true)
})
</script>
<style scoped>
.auto-scroll {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
.scroll-content {
position: absolute;
will-change: transform;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
/* 垂直方向樣式 */
.vertical .scroll-content {
width: 100%;
}
/* 水平方向樣式 */
.horizontal .scroll-content {
height: 100%;
display: flex;
}
/* 方向0: 往下 */
.direction-0 .scroll-content {
top: 0;
animation-name: scrollDown;
}
/* 方向1: 往上 */
.direction-1 .scroll-content {
bottom: 0;
animation-name: scrollUp;
}
/* 方向2: 向左 */
.direction-2 .scroll-content {
left: 0;
animation-name: scrollLeft;
}
/* 方向3: 向右 */
.direction-3 .scroll-content {
right: 0;
animation-name: scrollRight;
}
@keyframes scrollDown {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
@keyframes scrollUp {
0% {
transform: translateY(0);
}
100% {
transform: translateY(50%);
}
}
@keyframes scrollLeft {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
@keyframes scrollRight {
0% {
transform: translateX(0);
}
100% {
transform: translateX(50%);
}
}
</style>
總結(jié)
到此這篇關(guān)于基于vue.js實(shí)現(xiàn)無(wú)縫滾動(dòng)兩種方法的文章就介紹到這了,更多相關(guān)vue.js無(wú)縫滾動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用Print.js打印頁(yè)面樣式不出現(xiàn)的解決
這篇文章主要介紹了vue使用Print.js打印頁(yè)面樣式不出現(xiàn)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Vue監(jiān)聽(tīng)localstorage變化的方法詳解
在日常開(kāi)發(fā)中,我們經(jīng)常使用localStorage來(lái)存儲(chǔ)一些變量,這些變量會(huì)存儲(chǔ)在瀏覽中,對(duì)于localStorage來(lái)說(shuō),即使關(guān)閉瀏覽器,這些變量依然存儲(chǔ)著,方便我們開(kāi)發(fā)的時(shí)候在別的地方使用,本文就給大家介紹Vue如何監(jiān)聽(tīng)localstorage的變化,需要的朋友可以參考下2023-10-10
vue cli4.0項(xiàng)目引入typescript的方法
這篇文章主要介紹了vue cli4.0項(xiàng)目引入typescript的方法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
vue3+ts前端封裝EventSource并在請(qǐng)求頭添加token的方法
這篇文章主要介紹了vue3+ts前端封裝EventSource并在請(qǐng)求頭添加token,本文將介紹如何使用 event-source-polyfill 來(lái)解決這個(gè)問(wèn)題,需要的朋友可以參考下2024-12-12
vue.js實(shí)現(xiàn)仿原生ios時(shí)間選擇組件實(shí)例代碼
本篇文章主要介紹了vue.js實(shí)現(xiàn)仿原生ios時(shí)間選擇組件實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12
vue3獲取ref實(shí)例結(jié)合ts的InstanceType問(wèn)題
這篇文章主要介紹了vue3獲取ref實(shí)例結(jié)合ts的InstanceType問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Vue3+script setup+ts+Vite+Volar搭建項(xiàng)目
本文主要介紹了Vue3+script setup+ts+Vite+Volar搭建項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

