PAYATHON 2026

如何仅用 CSS 实现滚动切换图片,同时保持页面其余部分静止?

技术老齐

仅用 CSS 就能实现:用 position: sticky 让整个展示区保持原位,再用 CSS 滚动驱动动画移动图片列表。

在这个过程中,页面始终在滚动。展示区吸附在视口中,看起来只有图片在移动;到达吸附边界后,展示区随页面离开视口,后续内容接着出现。这种做法要求浏览器支持 animation-timeline、animation-range 等属性,旧浏览器需要回退方案。MDN:animation-timeline

原来的代码为什么会显示全部图片或留下空白?

position: sticky 会保留元素在正常文档流中的位置。分别给每张图片设置 sticky,无法自动形成只显示一张图片的轮播窗口,也无法让页面中的其他内容保持原位。MDN:position

原来的 grid 会把图片依次排成多行,每行占据 300px 高度:

  • 没有裁剪窗口,后面的图片也会显示出来。
  • 图片吸附或重叠后,原来的布局空间仍然保留,因此下方可能看起来有留白。
  • 如果直接缩短列表或父容器,又可能使 sticky 没有足够的移动空间。

<ul> 的直接列表项应使用 <li>,同时清除默认的 margin、padding 和列表标记。

调整结构:外层提供滚动距离,内层固定展示

  1. 外层 .productions 提供图片切换所需的滚动距离。
  2. .productions__stage 包含图片和需要保持静止的内容,整体设置 sticky。
  3. .productions__window 将可见区域限制为一张图片的高度。
  4. 图片列表按普通纵向布局排列,通过 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 补充实现。

这里的图片会连续向上滑动,切换时会同时露出相邻两张图片的一部分。如果要求每次只显示一张完整图片,并在图片之间瞬间切换,还需要另外设计离散的关键帧。

备注:内容仅供参考。