本文介绍如何使用JavaScript和GSAP库实现流畅的封面过渡动画效果。通过CSS网格布局构建页面结构,利用transform和aspect-ratio属性处理图片缩放与响应式显示。教程详细讲解了封面层、预览视图及文本分割的实现逻辑,并提供了完整的HTML、CSS和JavaScript代码示例,帮助开发者掌握复杂的页面切换动画技巧。
引言
首先展示一个镜头,其中包含多种过渡效果,重点介绍所谓的“封面过渡”。这种效果通过黑色封面的动画展开来隐藏原有内容,随后新内容以不同的颜色在封面之上显现。

网页设计中可以运用多种动画来展示新内容。本教程将简要分析其结构及动画的关键亮点。
动画效果将采用GreenSock的GSAP库来实现。
标记和样式
以下将基于一个具体网站示例来实现该效果。

首先,整个页面采用网格布局进行构建。
HTML代码结构如下:
<div class="content">
<div class="item">
<span class="item__meta">2020</span>
<h3 class="item__title">Alex Moulder</h3>
<div class="item__img"><div class="item__img-inner" style="background-image:url(img/1.jpg)"></div></div>
<p class="item__desc">I am only waiting for love to give myself up at last into his hands. That is why it is so late and why I have been guilty of such omissions.</p>
<a class="item__link">view</a>
</div>
<div class="item">
<span class="item__meta">2021</span>
<h3 class="item__title">Aria Bennett</h3>
<div class="item__img"><div class="item__img-inner" style="background-image:url(img/2.jpg)"></div></div>
<p class="item__desc">They come with their laws and their codes to bind me fast; but I evade them ever, for I am only waiting for love to give myself up at last into his hands.</p>
<a class="item__link">view</a>
</div>
<div class="item">
<span class="item__meta">2022</span>
<h3 class="item__title">Jimmy Hughes</h3>
<div class="item__img"><div class="item__img-inner" style="background-image:url(img/3.jpg)"></div></div>
<p class="item__desc">Clouds heap upon clouds and it darkens. Ah, love, why dost thou let me wait outside at the door all alone?</p>
<a class="item__link">view</a>
</div>
</div>
CSS样式代码如下:
main {
padding: 1.5rem 2.5rem 3rem;
height: 100vh;
display: grid;
grid-template-columns: 100%;
grid-template-areas: 'frame' 'content';
grid-template-rows: min-content 1fr;
grid-row-gap: 8vh;
}
内容区域采用特定样式,以在网格中正确显示各项项目。
.content {
grid-area: content;
max-width: 400px;
}
@media screen and (min-width: 53em) {
.content {
max-width: none;
display: grid;
grid-template-columns: repeat(3,1fr);
grid-template-rows: 100%;
grid-column-gap: 5vw;
}
}
为了在大屏幕上实现项目并排显示,需添加相应的媒体查询。
项目内部元素采用以下样式定义:
.item {
margin-bottom: 5rem;
display: grid;
grid-template-columns: 100%;
grid-template-rows: 1rem auto auto 1fr auto;
}
.item__title {
font-family: kudryashev-d-excontrast-sans, sans-serif;
font-weight: 300;
font-size: 2rem;
margin-bottom: 0.5rem;
}
.item__img {
position: relative;
overflow: hidden;
width: 100%;
aspect-ratio: 500/333;
}
.item__img-inner {
background-position: 50% 45%;
background-size: cover;
width: 100%;
height: 100%;
}
.item__desc {
margin-top: 2.5rem;
line-height: 1.1;
}
.item__link {
cursor: pointer;
text-transform: lowercase;
width: 100%;
padding: 1rem;
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: 2rem;
text-align: center;
}
.item__link:hover {
background: var(--color-text);
border-color: var(--color-text);
color: var(--color-text-alt);
}
@media screen and (min-width: 53em) {
.item {
margin-bottom: 0;
}
.item__title {
font-size: clamp(1.25rem,3vw,2rem);
}
}
图像元素包含嵌套结构,以支持轻微的缩放效果。值得注意的是aspect-ratio属性,它允许在使用背景图像时,根据实际尺寸设置响应式的图片大小。
点击项目按钮时,将触发封面动画。该动画涉及两个元素的缩放变换,以覆盖整个页面:
<div class="overlay">
<div class="overlay__row"></div>
<div class="overlay__row"></div>
</div>
添加以下CSS样式:
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 100%;
pointer-events: none;
grid-template-rows: repeat(2,1fr);
}
.overlay__row {
background: var(--color-overlay);
transform: scaleY(0);
will-change: transform;
}
.overlay__row:first-child {
transform-origin: 50% 0%;
}
.overlay__row:last-child {
transform-origin: 50% 100%;
}
为每一“行”设置正确的变换原点,确保它们能够上下动画,“关闭”当前视图并将其隐藏。
接下来查看即将显示的视图部分,该部分称为“预览”,添加如下内容:
<section class="previews">
<div class="preview">
<div class="preview__img"><div class="preview__img-inner" style="background-image:url(img/1_big.jpg)"></div></div>
<h3 class="preview__title oh"><span class="oh__inner">Moulder</span></h3>
<div class="preview__column preview__column--start">
<span class="preview__column-title preview__column-title--main oh"><span class="oh__inner">Alex Moulder</span></span>
<span class="oh"><span class="oh__inner">2020</span></span>
</div>
<div class="preview__column">
<h4 class="preview__column-title oh"><span class="oh__inner">Location</span></h4>
<p>And if it rains, a closed car at four. And we shall play a game of chess, pressing lidless eyes and waiting for a knock upon the door.</p>
</div>
<div class="preview__column">
<h4 class="preview__column-title oh"><span class="oh__inner">Material</span></h4>
<p>At the violet hour, when the eyes and back, turn upward from the desk, when the human engine waits.</p>
</div>
<button class="unbutton preview__back"><svg width="100px" height="18px" viewBox="0 0 50 9"><path vector-effect="non-scaling-stroke" d="m0 4.5 5-3m-5 3 5 3m45-3h-77"></path></svg></button>
</div>
<!-- preview -->
<!-- preview -->
</section>

大图像将使用本教程中详细解释的显示/取消显示动画进行处理。这也是使用嵌套结构的原因,与项目图像的处理方式类似。对于希望通过变换(并由父级截断)来显示的文本,我们将使用 .oh > .oh__inner 结构。其核心思想是变换 oh__inner 元素以隐藏它。对于多行文本,将通过JavaScript动态添加此结构。preview__column 分区中的段落将使用 SplitType 分割成若干行。
添加以下样式以使线条动画效果生效:
.oh {
position: relative;
overflow: hidden;
}
.oh__inner {
will-change: transform;
display: inline-block;
}
.line {
transform-origin: 0 50%;
white-space: nowrap;
will-change: transform;
}
现在,让我们启动这个动画效果。
JavaScript实现部分
首先定义并实例化所需对象。
import { gsap } from 'gsap';
import { Item } from './item';
import { Preview } from './preview';
// body element
const body = document.body;
// .content element
const contentEl = document.querySelector('.content');
// frame element
const frameEl = document.querySelector('.frame');
// 顶部和底部叠加的叠加元素
const overlayRows = [...document.querySelectorAll('.overlay__row')];
const previews = [];
[...document.querySelectorAll('.preview')].forEach(preview => previews.push(new Preview(preview)));
//项目实例列表
const items = [];
[...document.querySelectorAll('.item')].forEach((item, pos) => items.push(new Item(item, previews[pos])));
当打开一个项目时,首先通过添加类名使内容不可点击。
随后,在显示预览内容后,隐藏所有需要动画处理的线条和元素。preview-visible 类用于设置部分颜色和指针事件,同时隐藏页面顶部的小框架,以便在封面隐藏初始视图后,能再次通过动画显示它。
通过将图像元素向一个方向平移,同时将内部元素(实际包含背景图像)向相反方向平移,从而隐藏图像。
最终显示所有线条和 oh__inner 元素。
const openItem = item => {
gsap.timeline({
defaults: {
duration: 1,
ease: 'power3.inOut'
}
})
.add(() => {
// 指针事件没有指向内容
contentEl.classList.add('content--hidden');
}, 'start')
.addLabel('start', 0)
.set([item.preview.DOM.innerElements, item.preview.DOM.backCtrl], {
opacity: 0
}, 'start')
.to(overlayRows, {
scaleY: 1
}, 'start')
.addLabel('content', 'start+=0.6')
.add(() => {
body.classList.add('preview-visible');
gsap.set(frameEl, {
opacity: 0
}, 'start')
item.preview.DOM.el.classList.add('preview--current');
}, 'content')
// Image animation (reveal animation)
.to([item.preview.DOM.image, item.preview.DOM.imageInner], {
startAt: {y: pos => pos ? '101%' : '-101%'},
y: '0%'
}, 'content')
.add(() => {
for (const line of item.preview.multiLines) {
line.in();
}
gsap.set(item.preview.DOM.multiLineWrap, {
opacity: 1,
delay:0.1
})
}, 'content')
// 动画框架元素
.to(frameEl, {
ease: 'expo',
startAt: {y: '-100%', opacity: 0},
opacity: 1,
y: '0%'
}, 'content+=0.3')
.to(item.preview.DOM.innerElements, {
ease: 'expo',
startAt: {yPercent: 101},
yPercent: 0,
opacity: 1
}, 'content+=0.3')
.to(item.preview.DOM.backCtrl, {
opacity: 1
}, 'content')
};
关闭预览时,需要执行反向动画。
const closeItem = item => {
gsap.timeline({
defaults: {
duration: 1,
ease: 'power3.inOut'
}
})
.addLabel('start', 0)
.to(item.preview.DOM.innerElements, {
yPercent: -101,
opacity: 0,
}, 'start')
.add(() => {
for (const line of item.preview.multiLines) {
line.out();
}
}, 'start')
.to(item.preview.DOM.backCtrl, {
opacity: 0
}, 'start')
.to(item.preview.DOM.image, {
y: '101%'
}, 'start')
.to(item.preview.DOM.imageInner, {
y: '-101%'
}, 'start')
// 动画框架元素
.to(frameEl, {
opacity: 0,
y: '-100%',
onComplete: () => {
body.classList.remove('preview-visible');
gsap.set(frameEl, {
opacity: 1,
y: '0%'
})
}
}, 'start')
.addLabel('grid', 'start+=0.6')
.to(overlayRows, {
//ease: 'expo',
scaleY: 0,
onComplete: () => {
item.preview.DOM.el.classList.remove('preview--current');
contentEl.classList.remove('content--hidden');
}
}, 'grid')
};
不要忘记添加事件监听器。
for (const item of items) {
// 打开项目预览
item.DOM.link.addEventListener('click', () => openItem(item));
// 关闭项目预览
item.preview.DOM.backCtrl.addEventListener('click', () => closeItem(item));
}
最终效果如下所示。

在动画进入时保持流畅的过渡,并执行快速的关闭动画,以减少用户等待恢复初始视图的时间。建议探索代码,尝试调整其他动画、计时和缓动效果,以观察不同组合的效果。
以上就是使用JavaScript实现封面过渡动画的详细内容,更多关于JavaScript动画封面过渡的资料请关注本站其它相关文章!