滚动放大至全幅
带框的图片滚到屏幕中间时向两侧展开铺满宽度,把一张缩略图变成一整幅场景。
向下滚动 ↓云层之上
用 CSS 滚动驱动动画制作滚入视野时展开到全宽的图片。给图片外层设置 animation: open linear both,animation-timeline: view(),animation-range: entry 10% cover 50%。keyframes 中从 clip-path: inset(0 22% round 20px) 变为 clip-path: inset(0 0 round 0),同时在同一时间线上把内部图片从 scale 1.2 缩到 1,让画框打开时像镜头往后拉。全部包在 @supports (animation-timeline: view()) 中,不支持的浏览器直接显示完整图片。在 prefers-reduced-motion 下去掉动画。说明文字为“云层之上”。
源代码
'use client'
import s from './demo.module.css'
export default function ScrollZoom({ labels }: { labels: Record<string, string> }) {
return (
<div className={s.frame} tabIndex={0}>
<span className={s.hint}>{labels.hint} ↓</span>
<figure className={s.figure}>
<div className={s.scene} role="img" aria-label={labels.caption} />
<figcaption className={s.caption}>{labels.caption}</figcaption>
</figure>
</div>
)
}
.frame {
position: absolute;
inset: 0;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
}
.frame:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -4px; }
.hint {
display: block;
font: 500 var(--text-xs) / 1 var(--font-mono);
letter-spacing: 0.06em;
text-transform: uppercase;
text-align: center;
color: var(--stage-muted);
}
.frame { padding-top: 24px; }
.figure {
--sky-top: oklch(40% 0.08 260);
--sky-low: oklch(78% 0.1 60);
--cloud: oklch(92% 0.02 80);
position: relative;
height: 200px;
margin: 160px 0 200px;
overflow: hidden;
}
.scene {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 30% 14% at 20% 78%, var(--cloud) 96%, transparent),
radial-gradient(ellipse 40% 16% at 70% 84%, var(--cloud) 96%, transparent),
radial-gradient(ellipse 50% 20% at 45% 96%, var(--cloud) 96%, transparent),
radial-gradient(circle at 62% 46%, oklch(95% 0.06 85) 0 8%, transparent 8.5%),
linear-gradient(var(--sky-top), var(--sky-low));
}
.caption {
position: absolute;
left: 50%;
bottom: 12px;
translate: -50% 0;
font: 600 var(--text-base) / 1 var(--font-display);
color: var(--stage-bg);
white-space: nowrap;
}
@supports (animation-timeline: view()) {
.figure {
animation: open linear both;
animation-timeline: view();
animation-range: entry 10% cover 50%;
}
.scene {
animation: settle linear both;
animation-timeline: view();
animation-range: entry 10% cover 50%;
}
}
@keyframes open {
from { clip-path: inset(0 22% round 20px); }
to { clip-path: inset(0 0 round 0); }
}
@keyframes settle {
from { transform: scale(1.2); }
to { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.figure, .scene { animation: none; }
}