视差滚动效果
实现视差滚动和分页动画效果
问题
什么是视差滚动效果,如何给每页做不同的动画?
解答
视差滚动(Parallax Scrolling)是让页面中不同层的元素以不同速度移动,产生立体深度感的效果。
CSS 实现视差滚动
利用 perspective 和 translateZ 创建 3D 空间,元素距离视点越远,滚动越慢。
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
/* 创建 3D 透视空间 */
perspective: 1px;
}
.parallax-container {
height: 100vh;
/* 保持 3D 变换 */
transform-style: preserve-3d;
position: relative;
}
/* 背景层 - 滚动最慢 */
.bg-layer {
position: absolute;
inset: 0;
/* 推远并放大补偿 */
transform: translateZ(-2px) scale(3);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: -2;
}
/* 中间层 */
.mid-layer {
position: absolute;
inset: 0;
transform: translateZ(-1px) scale(2);
z-index: -1;
}
.mid-layer::before {
content: '';
position: absolute;
width: 200px;
height: 200px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
top: 20%;
left: 10%;
}
/* 前景层 - 正常滚动 */
.content {
position: relative;
padding: 100px 50px;
color: white;
font-family: sans-serif;
}
.content h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.spacer {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: white;
}
</style>
</head>
<body>
<div class="parallax-container">
<div class="bg-layer"></div>
<div class="mid-layer"></div>
<div class="content">
<h1>视差滚动效果</h1>
<p>向下滚动查看效果</p>
</div>
</div>
<div class="spacer">继续滚动...</div>
<div class="parallax-container">
<div class="bg-layer" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);"></div>
<div class="content">
<h1>第二屏</h1>
</div>
</div>
</body>
</html>
JavaScript 实现视差滚动
通过监听滚动事件,手动控制元素位移速度。
<!DOCTYPE html>
<html>
<head>
<style>
.parallax-section {
height: 100vh;
position: relative;
overflow: hidden;
}
.parallax-bg {
position: absolute;
inset: -50% 0;
background-size: cover;
background-position: center;
will-change: transform;
}
.section-1 .parallax-bg {
background: linear-gradient(45deg, #12c2e9, #c471ed);
}
.section-2 .parallax-bg {
background: linear-gradient(45deg, #f5af19, #f12711);
}
.section-content {
position: relative;
z-index: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 3rem;
font-family: sans-serif;
}
</style>
</head>
<body>
<section class="parallax-section section-1">
<div class="parallax-bg" data-speed="0.5"></div>
<div class="section-content">第一屏</div>
</section>
<section class="parallax-section section-2">
<div class="parallax-bg" data-speed="0.3"></div>
<div class="section-content">第二屏</div>
</section>
<script>
const parallaxElements = document.querySelectorAll('.parallax-bg');
function updateParallax() {
const scrollY = window.scrollY;
parallaxElements.forEach(el => {
// data-speed 控制滚动速度比例
const speed = parseFloat(el.dataset.speed) || 0.5;
const yPos = scrollY * speed;
el.style.transform = `translateY(${yPos}px)`;
});
}
// 使用 requestAnimationFrame 优化性能
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
updateParallax();
ticking = false;
});
ticking = true;
}
});
</script>
</body>
</html>
每页不同动画(Intersection Observer)
使用 Intersection Observer 检测元素进入视口,触发对应动画。
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.page {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
}
.page:nth-child(1) { background: #3498db; }
.page:nth-child(2) { background: #e74c3c; }
.page:nth-child(3) { background: #2ecc71; }
/* 动画元素初始状态 */
.animate-item {
opacity: 0;
color: white;
font-size: 3rem;
transition: all 0.8s ease-out;
}
/* 不同页面的入场动画 */
.page-1 .animate-item {
transform: translateY(100px);
}
.page-2 .animate-item {
transform: scale(0.5) rotate(-10deg);
}
.page-3 .animate-item {
transform: translateX(-100px);
}
/* 进入视口后的状态 */
.animate-item.visible {
opacity: 1;
transform: translateY(0) scale(1) rotate(0) translateX(0);
}
</style>
</head>
<body>
<section class="page page-1">
<h1 class="animate-item">从下方淡入</h1>
</section>
<section class="page page-2">
<h1 class="animate-item">缩放旋转</h1>
</section>
<section class="page page-3">
<h1 class="animate-item">从左侧滑入</h1>
</section>
<script>
// 创建观察器
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素进入视口,添加 vkonk 类
entry.target.classList.add('vkonk');
} else {
// 可选:离开视口时移除,实现重复动画
entry.target.classList.remove('vkonk');
}
});
},
{
// 元素 20% 进入视口时触发
threshold: 0.2
}
);
// 观察所有动画元素
document.querySelectorAll('.animate-item').forEach(el => {
observer.observe(el);
});
</script>
</body>
</html>
使用动画库(可选)
复杂场景可使用 GSAP + ScrollTrigger:
// 安装:npm install gsap
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
// 第一页:淡入上移
gsap.from('.page-1 .content', {
scrollTrigger: {
trigger: '.page-1',
start: 'top center',
end: 'bottom center',
scrub: true // 动画与滚动同步
},
y: 100,
opacity: 0
});
// 第二页:横向滚动
gsap.to('.page-2 .horizontal-track', {
scrollTrigger: {
trigger: '.page-2',
pin: true, // 固定页面
scrub: 1
},
x: '-50%'
});
关键点
- CSS 方案:
perspective+translateZ创建 3D 空间,元素 Z 轴距离决定滚动速度 - JS 方案:监听 scroll 事件,用
requestAnimationFrame优化性能 - 分页动画:Intersection Observer 检测元素可见性,触发对应 CSS 动画
- 性能优化:使用
will-change、transform代替top/left,避免重排 - 复杂场景:GSAP ScrollTrigger 提供更精细的滚动动画控制
目录