1. 结果展示
先来看一下最终效果:在向下滚动时,图片逐渐向上偏移并显现,而在向上滚动时,图片逐渐向下偏移并显现
无限视差滚动
2. 介绍
无限视差滚动效果是一种在网页设计中常用的技术,通过以不同速度移动页面上的不同层次元素,创造出动态的视觉效果。它可以应用于各种网站和页面,包括故事性网站、产品展示页面、创意型网站以及导航和交互设计中。这种效果能够增强用户体验、提升页面吸引力,并为网站注入更多创意和个性。
3. 实现原理与步骤
3.1 布局结构搭建
准备一些图片,并编写一个滚动区域,其中包含三个<div>
元素,每个<div>
内包含一个<img>
元素。
- 效果示意图:
可以看到,不管怎么滚动,页面上自始至终都只有3张图片
- 基本代码结构:初始化图片路径,通过
css
将三张图片重合在一起。
<template>
<div class="scroll-container">
<div class="prev">
<img :src="prev" class="item">
</div>
<div class="cur">
<img :src="cur" class="item">
</div>
<div class="next">
<img :src="next" class="item">
</div>
</div>
</template>
<script setup lang='ts'>
// 获取 assets/img/rose路径下的所有图片
const imgList = Object.keys(
import.meta.glob("/src/assets/img/rose/*.{png,jpg,gif,svg,avif}")
);
const prev = ref(imgList[0]);
const cur = ref(imgList[1]);
const next = ref(imgList[2]);
</script>
<style scoped lang="scss">
.scroll-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.item {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
}
.prev,
.cur,
.next {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
}
</style>
3.2 监听滚轮事件
为了实现滚动区域的响应式滚动效果,我们需要在滚动区域的容器上添加 @wheel 滚轮事件监听器,并在事件触发时进行相应的处理。具体步骤如下:
-
在滚动区域的容器上添加
@wheel
滚轮事件监听器,并绑定相应的处理函数。 -
在滚轮事件处理函数中,判断滚轮滚动的方向。可以通过事件对象中的
deltaY
属性来判断滚动的方向,deltaY > 0
表示向下滚动,deltaY < 0
表示向上滚动。 -
根据滚动方向,动态地为滚动区域的容器添加或移除相应的 class。这些 class 可以用来控制页面元素的过渡效果,如淡入淡出、滑动等。
-
为了确保滚动事件在过渡完成前只触发一次,需要设置一个变量用于标记是否正在进行过渡动画,在过渡完成前只允许触发一次滚动事件。
滚动区域添加@wheel
滚轮事件监听,绑定滚动方向的动态:class
属性
<div class="scroll-container" @wheel="onWheel($event)" :class="direction">
滚动事件只能在动画完成后执行,设置滚动方向的动态:class
属性
// 是否正在过渡动画中
let isAnimate = false;
// 滚动方向(默认为init)
type Direction = 'up' | 'down' | 'init';
let direction = ref<Direction>('init');
/**
* 滚轮事件
* @param event
*/
const onWheel = (event: unknown) => {
if (isAnimate) return;
const deltaY = (event as WheelEvent).deltaY;
if (Math.abs(deltaY) === 0 || isAnimate) return; // deltaY为0或是已经在动画中时,直接返回
isAnimate = true;
if (deltaY > 0) {
direction.value = 'down';
} else if (deltaY < 0) {
direction.value = 'up';
}
}
3.3 图片切换工具函数
先来实现图片路径的切换,在滑动后,要根据当前显示的图片下标来更新3张图片
const currentIndex = ref(1);
/**
* 滑动后,刷新图片路径
* @param {number} index 当前显示图片的下标
*/
const refresh = (index: number) => {
currentIndex.value = index;
const prevIndex = index === 0 ? imgList.length - 1 : index - 1;
const nextIndex = index === imgList.length - 1 ? 0 : index + 1;
prev.value = imgList[prevIndex];
cur.value = imgList[index];
next.value = imgList[nextIndex];
}
3.4 监听过渡结束事件
为了实现无限滚动的效果,我们需要在过渡完成后更新图片
- 绑定过渡完成事件监听器
@transitionend
<div class="scroll-container" @wheel="onWheel($event)" :class="direction" @transitionend="onTransitionend">
- 根据滚动方向更新图片,并标记过渡动画已完成。
/**
* 过渡完成事件
*/
const onTransitionend = () => {
if (direction.value == 'down') {
// 下滑时,下标为末尾,设置为0
const index = currentIndex.value === imgList.length - 1 ? 0 : currentIndex.value + 1
refresh(index);
} else if (direction.value == 'up') {
// 上滑时,下标为0,设置为末尾下标
const index = currentIndex.value === 0 ? imgList.length - 1 : currentIndex.value - 1
refresh(index);
}
// 过渡完成
isAnimate = false;
// 重置滚动区域方向
direction.value = 'init';
}
3.5 视差效果实现
- 向下滚动前,父元素底部通过绝对定位固定在视口底部,设置
z-index: 1
,保证覆盖。图片底部通过绝对定位设置在父元素底部,设置translateY(10%)
过渡时,父元素的高度从0逐渐增加至整个视口高度。图片设置translateY(0)
,这样图片有一个上移的效果
- 向上滚动前,父元素通过绝对定位固定在视口顶部,设置
z-index: 1
,保证覆盖。图片顶部通过绝对定位设置在父元素顶部,设置translateY(-10%)
过渡时,父元素的高度从0逐渐向下增加至整个视口高度。图片设置translateY(0)
,这样图片有一个下移的效果。
css
样式
.prev,
.cur,
.next {
position: absolute;
left: 0;
right: 0;
}
.prev {
top: 0;
height: 0;
overflow: hidden;
z-index: 1;
img {
top: 0;
transform: translateY(-10%);
}
}
.cur {
top: 0;
height: 100vh;
.item {
top: 0;
}
}
.next {
bottom: 0;
height: 0;
overflow: hidden;
z-index: 1;
.item {
bottom: 0;
transform: translateY(10%);
}
}
.down .next,
.up .prev {
height: 100vh;
transition: 1s ease-in-out;
.item {
transform: translateY(0);
transition: 1s ease-in-out;
}
}
4. 完整代码参考
test.vue
<template>
<div class="scroll-container" @wheel="onWheel($event)" :class="direction" @transitionend="onTransitionend">
<div class="prev">
<img :src="prev" class="item">
</div>
<div class="cur">
<img :src="cur" class="item">
</div>
<div class="next">
<img :src="next" class="item">
</div>
</div>
</template>
<script setup lang='ts'>
// 获取 assets/img/rose路径下的所有图片
const imgList = Object.keys(
import.meta.glob("/src/assets/img/rose/*.{png,jpg,gif,svg,avif}")
);
const prev = ref(imgList[0]);
const cur = ref(imgList[1]);
const next = ref(imgList[2]);
const currentIndex = ref(1);
/**
* 滑动后,刷新图片路径
* @param {number} index
*/
const refresh = (index: number) => {
currentIndex.value = index;
const prevIndex = index === 0 ? imgList.length - 1 : index - 1;
const nextIndex = index === imgList.length - 1 ? 0 : index + 1;
prev.value = imgList[prevIndex];
cur.value = imgList[index];
next.value = imgList[nextIndex];
}
// 是否正在过渡动画中
let isAnimate = false;
// 滚动方向
type Direction = 'up' | 'down' | 'init';
let direction = ref<Direction>('init');
/**
* 滚轮事件
* @param event
*/
const onWheel = (event: unknown) => {
if (isAnimate) return;
const deltaY = (event as WheelEvent).deltaY;
if (deltaY === 0 || isAnimate) return;
isAnimate = true;
if (deltaY > 0) {
direction.value = 'down';
} else if (deltaY < 0) {
direction.value = 'up';
}
}
/**
* 过渡完成事件
*/
const onTransitionend = () => {
if (direction.value == 'down') {
// 下滑时,下标为末尾,设置为0
const index = currentIndex.value === imgList.length - 1 ? 0 : currentIndex.value + 1
refresh(index);
} else if (direction.value == 'up') {
// 上滑时,下标为0,设置为末尾下标
const index = currentIndex.value === 0 ? imgList.length - 1 : currentIndex.value - 1
refresh(index);
}
// 过渡完成
isAnimate = false;
// 重置滚动区域方向
direction.value = 'init';
}
</script>
<style scoped lang="scss">
.scroll-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.item {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
}
.prev,
.cur,
.next {
position: absolute;
left: 0;
right: 0;
}
.prev {
top: 0;
height: 0;
overflow: hidden;
z-index: 1;
img {
top: 0;
transform: translateY(-10%);
}
}
.cur {
top: 0;
height: 100vh;
.item {
top: 0;
}
}
.next {
bottom: 0;
height: 0;
overflow: hidden;
z-index: 1;
.item {
bottom: 0;
transform: translateY(10%);
}
}
.down .next,
.up .prev {
height: 100vh;
transition: 1s ease-in-out;
.item {
transform: translateY(0);
transition: 1s ease-in-out;
}
}
</style>
5. 总结
希望这篇文章能够帮助您更好地理解和实践无限视差滚动,并在您的项目中充分发挥其潜力。如果您有任何疑问或想法,欢迎随时留言分享。感谢您的阅读!