如何仅用 CSS 实现滚动切换图片,同时保持页面其余部分静止?
仅用 CSS 就能实现:用 position: sticky 让整个展示区保持原位,再用 CSS 滚动驱动动画移动图片列表。
在这个过程中,页面始终在滚动。展示区吸附在视口中,看起来只有图片在移动;到达吸附边界后,展示区随页面离开视口,后续内容接着出现。这种做法要求浏览器支持 animation-timeline、animation-range 等属性,旧浏览器需要回退方案。MDN:animation-timeline
原来的代码为什么会显示全部图片或留下空白?
position: sticky 会保留元素在正常文档流中的位置。分别给每张图片设置 sticky,无法自动形成只显示一张图片的轮播窗口,也无法让页面中的其他内容保持原位。MDN:position
原来的 grid 会把图片依次排成多行,每行占据 300px 高度:
- 没有裁剪窗口,后面的图片也会显示出来。
- 图片吸附或重叠后,原来的布局空间仍然保留,因此下方可能看起来有留白。
- 如果直接缩短列表或父容器,又可能使
sticky没有足够的移动空间。
<ul> 的直接列表项应使用 <li>,同时清除默认的 margin、padding 和列表标记。
调整结构:外层提供滚动距离,内层固定展示
- 外层
.productions提供图片切换所需的滚动距离。 .productions__stage包含图片和需要保持静止的内容,整体设置sticky。.productions__window将可见区域限制为一张图片的高度。- 图片列表按普通纵向布局排列,通过
transform随滚动向上移动。
下面的示例保留了 50px 高的顶部导航和 300px 的图片高度。使用时,整体替换原来的结构与样式,并移除每张图片上的 position: sticky。
HTML
<header class="site-header">页面导航</header>
<section class="productions" aria-label="演出图片">
<div class="productions__stage">
<div class="productions__window">
<ul class="productions__list">
<li class="productions__item">
<img
src="https://picsum.photos/id/1015/1200/800"
alt="山谷与河流"
width="1200"
height="800"
/>
</li>
<li class="productions__item">
<img
src="https://picsum.photos/id/1016/1200/800"
alt="山地景色"
width="1200"
height="800"
/>
</li>
</ul>
</div>
<p class="productions__copy">
图片切换时,这部分内容保持原位。
</p>
</div>
</section>
<main class="page-content">
<h1>后续内容</h1>
<p>最后一张图片展示完整后,继续滚动即可进入这里。</p>
</main>
CSS
:root {
--header-height: 50px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.site-header {
position: sticky;
top: 0;
z-index: 10;
height: var(--header-height);
padding-inline: 24px;
line-height: var(--header-height);
background: #fff;
}
.productions {
--image-count: 2;
--image-height: 300px;
--top: var(--header-height);
--stage-height: calc(100vh - var(--top));
--travel: calc(
(var(--image-count) - 1) * var(--image-height)
);
}
.productions__list {
margin: 0;
padding: 0;
list-style: none;
}
.productions__item {
height: var(--image-height);
}
.productions__item img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.productions__copy {
margin: 0;
padding: 24px;
background: #eee;
}
.page-content {
min-height: 100vh;
padding: 24px;
}
/*
* 默认显示普通图片列表。
* 支持相关属性且用户允许动效时,再启用滚动切换。
*/
@supports (animation-timeline: --productions)
and (animation-range: contain) {
@media (prefers-reduced-motion: no-preference) {
.productions {
height: calc(var(--stage-height) + var(--travel));
view-timeline-name: --productions;
view-timeline-axis: block;
view-timeline-inset: var(--top) 0;
}
.productions__stage {
position: sticky;
top: var(--top);
height: var(--stage-height);
display: grid;
grid-template-rows: var(--image-height) 1fr;
background: #fff;
}
.productions__window {
height: var(--image-height);
overflow: hidden;
}
.productions__list {
animation-name: move-images;
animation-duration: auto;
animation-timing-function: linear;
animation-fill-mode: both;
animation-timeline: --productions;
animation-range: contain 0% contain 100%;
}
}
}
@keyframes move-images {
from {
transform: translateY(0);
}
to {
transform: translateY(calc(-1 * var(--travel)));
}
}
图片结束后,为什么能自然恢复滚动?
假设有 N 张图片,每张高度为 H。要从第一张完整显示移动到最后一张完整显示,列表的上移距离和外层高度应这样计算:
图片移动距离 = (N - 1) × H
外层总高度 = 固定展示区高度 + 图片移动距离
两张 300px 高的图片,列表只需上移 300px。若上移 600px,把整个列表都移出窗口,最后就会露出空白。
示例将这段移动绑定到外层元素的 contain 时间线范围。外层比有效视口更高,因此这个范围从外层顶部与有效视口顶部对齐开始,到两者底部对齐结束,正好覆盖展示区的吸附阶段。view-timeline-inset: var(--top) 0 用来避开顶部导航占据的空间。CSS 滚动驱动动画规范草案:时间线范围、MDN:view-timeline-inset
在这段滚动距离内:
- 展示区保持原位。
- 图片列表从
0移动到-300px。 - 第二张图片完整进入窗口。
继续滚动,外层便没有足够的剩余空间让展示区维持吸附状态,展示区随页面向上离开,后续内容紧接着出现。整个过程无须监听滚动事件或切换定位方式。
外层增加的高度用于驱动图片切换。切换期间,吸附的展示区持续覆盖这段空间,结束后也就不会额外留下一段空白。
使用时需要注意什么?
图片数量必须与变量一致
增加图片时,需要同步更新 --image-count:
.productions {
--image-count: 3;
}
这份实现按等高图片计算移动距离。图片之间不要额外添加 gap 或上下外边距;如果添加了,就需要将这些间距也计入计算。
需要静止的内容要放进展示区
示例中的 .productions__copy 会与图片窗口一起保持原位。其他需要保持静止的文字、按钮或背景,也应放在 .productions__stage 内。
容器外的普通内容仍会随页面滚动,CSS 不会自动让所有兄弟元素保持静止。
不要在外层随意添加 overflow: hidden
将裁剪设置在 .productions__window 上。如果给 sticky 的祖先元素设置 overflow: hidden、auto 或 scroll,可能会改变它依附的滚动容器,使吸附效果不符合预期。MDN:sticky 定位
保留兼容性和小屏回退
当浏览器不支持相关属性,或用户启用了减少动态效果时,示例会显示普通图片列表。此时所有图片都可见,属于预设的回退行为。
固定展示区必须能容纳图片和其他内容。屏幕较矮时,应通过媒体查询降低图片高度,或关闭吸附效果,避免文字溢出。如果还要在不支持滚动驱动动画的浏览器中保持完全相同的交互,可以用 JavaScript 补充实现。
这里的图片会连续向上滑动,切换时会同时露出相邻两张图片的一部分。如果要求每次只显示一张完整图片,并在图片之间瞬间切换,还需要另外设计离散的关键帧。
备注:内容仅供参考。
