视差分层

滚动时山丘、太阳和标题以不同速度移动,让一幅平面画面产生纵深。

  • JS
  • 背景
  • 滚动
  • 收录于

走进群山

向下滚动 ↓

AI 提示词

用 React 和 CSS 制作分层视差场景。在一个 sticky 场景中叠放四个绝对定位的图层:带太阳的天空、远山、近山和标题。滚动时计算区块内 0 到 1 的进度,写入场景的 CSS 自定义属性 --p(不使用 React state)。每个图层以各自的系数移动:transform: translateY(calc(var(--p) * <n>px)),越远的图层移动越少(太阳 60px、远山 30px、近山 -20px),标题向上漂移并淡出(translateY(calc(var(--p) * -80px)),opacity calc(1 - var(--p) * 1.4))。山丘用 clip-path 多边形或 radial-gradient 绘制。图层设置 will-change: transform。在 prefers-reduced-motion 下所有图层保持静止。标题为“走进群山”。

源代码

Demo.tsx
'use client'

import s from './demo.module.css'

export default function ParallaxLayers({ labels }: { labels: Record<string, string> }) {
  const onScroll = (e: React.UIEvent<HTMLDivElement>) => {
    const el = e.currentTarget
    el.style.setProperty('--p', String(el.scrollTop / (el.scrollHeight - el.clientHeight || 1)))
  }

  return (
    <div className={s.frame} tabIndex={0} onScroll={onScroll}>
      <div className={s.section}>
        <div className={s.scene}>
          <span className={s.sun} />
          <span className={s.far} />
          <p className={s.title}>{labels.title}</p>
          <span className={s.near} />
          <span className={s.label}>{labels.hint} ↓</span>
        </div>
      </div>
    </div>
  )
}
demo.module.css
.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 { --p: 0; }

.section { height: 300%; }

.scene {
  position: sticky;
  top: 0;
  height: 240px;
  overflow: hidden;
  background: linear-gradient(oklch(34% 0.06 280), oklch(62% 0.11 45));
}

.sun, .far, .near, .title { position: absolute; will-change: transform; }

.sun {
  left: 60%;
  top: 30%;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: oklch(90% 0.09 80);
  transform: translateY(calc(var(--p) * 60px));
}

.far {
  inset: 45% -10% -10%;
  background: oklch(40% 0.07 30);
  clip-path: polygon(0 40%, 18% 10%, 34% 35%, 52% 0, 70% 30%, 86% 8%, 100% 30%, 100% 100%, 0 100%);
  transform: translateY(calc(var(--p) * 30px));
}

.near {
  inset: 68% -10% -20%;
  background: var(--stage-bg);
  clip-path: polygon(0 30%, 22% 0, 40% 25%, 62% 5%, 80% 30%, 100% 10%, 100% 100%, 0 100%);
  transform: translateY(calc(var(--p) * -20px));
}

.title {
  left: 0;
  right: 0;
  top: 18%;
  margin: 0;
  text-align: center;
  font: 700 var(--text-2xl) / 1 var(--font-display);
  color: var(--stage-ink);
  transform: translateY(calc(var(--p) * -80px));
  opacity: calc(1 - var(--p) * 1.4);
}

.label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 12px;
  font: 500 var(--text-xs) / 1 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  color: var(--stage-muted);
}

@media (prefers-reduced-motion: reduce) {
  .sun, .far, .near, .title { transform: none; opacity: 1; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。