环境动画在网页设计中的实践应用(第二部分)
动作设计很棘手:过多会分散注意力,过少则显得平淡。环境动画介于两者之间,它们是微妙、缓慢移动的细节,能增添氛围而不喧宾夺主。在本系列的第二部分中,网页设计先驱Andy Clarke展示了环境动画如何为任何网站设计增添个性。
核心原则回顾
环境动画是被动运动类型,起初可能不会引起注意,但它们以微妙的方式赋予设计生命力。元素可能在颜色间微妙过渡、缓慢移动或逐渐改变位置,可以出现和消失、改变大小或缓慢旋转,为品牌个性增添深度。
在第一部分中,我通过将Quick Draw McGraw漫画书的封面重新创建为CSS/SVG动画来说明了环境动画的概念。但我知道并非每个人都需要动画卡通角色,因此在第二部分中,我将分享环境动画在三个完全不同项目中的实现:Reuven Herman、Mike Worth和EPD。每个项目都展示了动作如何增强品牌标识、个性和叙事性,而不主导页面。
Reuven Herman项目
洛杉矶作曲家Reuven Herman不仅想要一个展示作品的网站,还希望传达他的个性以及客户与他合作时的体验。
动画实现原则
为使Reuven的插画生动起来,我遵循了几个核心环境动画原则:
- 保持动画缓慢平滑
- 无缝循环,避免突兀变化
- 使用分层构建复杂性
- 避免分散注意力
- 考虑可访问性和性能
SVG优化与路径变形
第一步是通过一次导出一组元素来优化SVG以进行动画处理,始终按照它们在最终文件中出现的顺序,逐渐构建主SVG。
乐谱五线谱线的波浪状态:
1
2
3
4
5
6
7
8
|
<g fill="none" stroke-width="2" stroke-linecap="round">
<path id="p1" stroke="#D2AB99" d="[…]"/>
<path id="p2" stroke="#BDBEA9" d="[…]"/>
<path id="p3" stroke="#E0C852" d="[…]"/>
<path id="p4" stroke="#8DB38B" d="[…]"/>
<path id="p5" stroke="#43616F" d="[…]"/>
<path id="p6" stroke="#A13D63" d="[…]"/>
</g>
|
虽然CSS现在支持路径点之间的动画,但每个状态的点数需要匹配。GSAP没有这种限制,可以在具有不同点数状态之间进行动画,使其成为此类动画的理想选择。
定义新的直线路径集:
1
2
3
4
5
6
7
8
|
const Waves = {
p1: "[…]",
p2: "[…]",
p3: "[…]",
p4: "[…]",
p5: "[…]",
p6: "[…]"
};
|
创建GSAP时间轴,在六秒内前后重复:
1
2
3
4
5
6
7
8
9
|
const waveTimeline = gsap.timeline({
repeat: -1,
yoyo: true,
defaults: { duration: 6, ease: "sine.inOut" }
});
Object.entries(Waves).forEach(([id, d]) => {
waveTimeline.to(`#${id}`, { morphSVG: d }, 0);
});
|
分层构建复杂性
另一个环境动画原则是使用分层构建复杂性。三行音符以不同速度移动:
1
2
3
|
<path id="notes-row-1"/>
<path id="notes-row-2"/>
<path id="notes-row-3"/>
|
每行动画的持续时间也使用GSAP定义,从100到400秒,为整体动画提供视差效果:
1
2
3
4
5
|
const noteRows = [
{ id: "#notes-row-1", duration: 300, y: 100 }, // 最慢
{ id: "#notes-row-2", duration: 200, y: 250 }, // 中等
{ id: "#notes-row-3", duration: 100, y: 400 } // 最快
];
|
钢琴键与阴影动画
下一层包含钢琴键投射的阴影,围绕其中心缓慢旋转:
1
2
3
4
5
6
7
8
9
|
gsap.to("shadow", {
y: -10,
rotation: -2,
transformOrigin: "50% 50%",
duration: 3,
ease: "sine.inOut",
yoyo: true,
repeat: -1
});
|
钢琴键本身同时旋转,但与阴影方向相反:
1
2
3
4
5
6
7
8
9
|
gsap.to("#g3-keys", {
y: 10,
rotation: 2,
transformOrigin: "50% 50%",
duration: 3,
ease: "sine.inOut",
yoyo: true,
repeat: -1
});
|
Mike Worth项目
Mike Worth是艾美奖获奖电影、视频游戏和电视作曲家,我为他设计了网站并创造了猩猩冒险家Orango Jones角色。
交互式环境动画
对于Mike的"关于"页面,我想将环境动画与交互结合起来。SVG被故意结构化,从后到前包括洞穴、光柱、Orango和导航:
1
2
3
4
5
6
|
<svg data-lights="lights-off">
<g id="cave">[…]</g>
<path id="light-shaft" d="[…]"></path>
<g id="orango">[…]</g>
<g id="nav">[…]</g>
</svg>
|
添加了一个锚点围绕隐藏圆圈,定位在Orango的放大镜上,作为大点击目标,通过更改SVG上的data-lights值来切换光柱:
1
2
3
|
<a href="javascript:void(0);" id="light-switch" title="Lights on/off">
<circle cx="700" cy="1000" r="100" opacity="0" />
</a>
|
CSS选择器与动画
在CSS中添加了两个后代选择器,根据data-lights值调整光柱的不透明度:
1
2
3
4
5
6
7
8
9
|
[data-lights="lights-off"] .light-shaft {
opacity: .05;
transition: opacity .25s linear;
}
[data-lights="lights-on"] .light-shaft {
opacity: .25;
transition: opacity .25s linear;
}
|
缓慢微妙的旋转为光柱增添了自然运动:
1
2
3
4
5
|
@keyframes shaft-rotate {
0% { rotate: 2deg; }
50% { rotate: -2deg; }
100% { rotate: 2deg; }
}
|
仅当灯光切换处于活动状态时可见:
1
2
3
4
|
[data-lights="lights-on"] .light-shaft {
animation: shaft-rotate 20s infinite;
transform-origin: 100% 0;
}
|
性能与可访问性考虑
开发任何环境动画时,考虑性能至关重要,因为即使CSS动画是轻量级的,模糊滤镜和投影等功能仍可能给低功耗设备带来压力。
考虑可访问性也很关键,因此要尊重用户的prefers-reduced-motion偏好:
1
2
3
4
5
6
7
8
|
@media screen and (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
transition-duration: 1ms !important;
}
}
|
当动画功能纯粹是装饰性时,考虑添加aria-hidden=“true"以防止它混乱可访问性树:
1
2
3
|
<a href="javascript:void(0);" id="light-switch" aria-hidden="true">
[…]
</a>
|
EPD项目
EPD是一家房地产投资公司,我为他们设计了新网站的创意概念。
随机点动画
在每个环境动画中,轮子旋转,大圆圈以随机间隔改变颜色。
开始优化此插画以进行动画处理,我导出了包含除轮子外所有元素的基本路径:
1
2
3
4
5
6
|
<g id="banner-base>
<path d="[…]"/>
<path d="[…]"/>
<path d="[…]"/>
[…]
</g>
|
接下来,导出了包含要更改颜色的圆圈元素的层:
1
2
3
4
5
6
|
<g id="banner-dots">
<circle class="data-theme-fill" […]/>
<circle class="data-theme-fill" […]/>
<circle class="data-theme-fill" […]/>
[…]
</g>
|
再次使用GSAP选择一组圆圈,像灯光在整个天际线上闪烁:
1
2
3
4
5
|
function animateRandomDots() {
const circles = gsap.utils.toArray("#banner-dots circle")
const numberToAnimate = gsap.utils.random(3, 6, 1)
const selected = gsap.utils.shuffle(circles).slice(0, numberToAnimate)
}
|
然后,在两秒间隔处,这些圆圈的填充颜色从蓝绿色强调色更改为与插画其余部分相同的灰白色:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
gsap.to(selected, {
fill: "color(display-p3 .439 .761 .733)",
duration: 0.3,
stagger: 0.05,
onComplete: () => {
gsap.to(selected, {
fill: "color(display-p3 .949 .949 .949)",
duration: 0.5,
delay: 2
})
}
})
gsap.delayedCall(gsap.utils.random(1, 3), animateRandomDots) }
animateRandomDots()
|
轮子旋转动画
最后,我旋转了轮子。这里不需要使用GSAP,因为仅使用CSS旋转就可以实现:
1
2
3
4
|
<g id="banner-wheel">
<path stroke="#F2F2F2" stroke-linecap="round" stroke-width="4" d="[…]"/>
<path fill="#D8F76E" d="[…]"/>
</g>
|
1
2
3
4
5
6
7
8
9
|
#banner-wheel {
transform-box: fill-box;
transform-origin: 50% 50%;
animation: rotateWheel 30s linear infinite;
}
@keyframes rotateWheel {
to { transform: rotate(360deg); }
}
|
技术选择考量
CSS动画轻量级,适用于简单、重复的效果,如淡入淡出和旋转。它们易于实现且不需要库。另一方面,GSAP提供了更多控制,可以处理路径变形和序列时间轴。选择使用哪一个取决于我需要GSAP的精度还是CSS的简单性。
总结
从Reuven的音乐纹理到Mike的叙事驱动Orango Jones以及EPD的发光天际线,这些项目展示了环境动画如何适应上下文。有时它纯粹是氛围性的,如漂移的音符或旋转的轮子;其他时候,它与交互无缝融合,奖励好奇心而不妨碍。
无论它是呼应作曲家的即兴创作、作为好玩的叙事设备,还是为保守行业增添微妙区别,同样的原则都成立:保持动作缓慢、无缝且有目的性,使其增强而不是分散设计的注意力。