幕布转场

两块彩色面板扫过来盖住页面,内容在后面切换,然后面板扫开露出新页面。

  • GSAP
  • 布局
  • 点击
  • 收录于
01

首页

AI 提示词

用 GSAP 时间线制作幕布式页面转场。在页面上叠两块全尺寸面板,一块主色、一块更深的颜色,初始都为 scaleX(0),transform-origin 为 left。导航时建立时间线:两块面板在 0.45s 内动到 scaleX(1),ease "power3.inOut",错开 0.08s,让深色面板跟在主色面板后面。在中点、页面被完全遮住时切换内容(调用路由或修改 state)。然后把 transform-origin 改为 right,用同样的缓动把两块面板动回 scaleX(0),主色面板最后离开。时间线进行中忽略点击。在 prefers-reduced-motion 下用 150ms 的交叉淡化代替幕布。组件卸载时 revert GSAP context。页面为“首页”和“作品”。

源代码

Demo.tsx
'use client'

import { useEffect, useRef, useState } from 'react'
import gsap from 'gsap'
import s from './demo.module.css'

export default function CurtainTransition({ labels }: { labels: Record<string, string> }) {
  const root = useRef<HTMLDivElement>(null)
  const ctx = useRef<gsap.Context | null>(null)
  const busy = useRef(false)
  const [page, setPage] = useState<'a' | 'b'>('a')
  const next = page === 'a' ? 'b' : 'a'

  useEffect(() => {
    ctx.current = gsap.context(() => {}, root)
    return () => ctx.current?.revert()
  }, [])

  const go = () => {
    if (busy.current) return
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return setPage(next)
    busy.current = true
    ctx.current?.add(() => {
      const panels = [`.${s.accent}`, `.${s.dark}`]
      gsap
        .timeline({ onComplete: () => void (busy.current = false) })
        .set(panels, { transformOrigin: 'left center' })
        .to(panels, { scaleX: 1, duration: 0.45, ease: 'power3.inOut', stagger: 0.08 })
        .call(() => setPage(next))
        .set(panels, { transformOrigin: 'right center' })
        .to([...panels].reverse(), { scaleX: 0, duration: 0.45, ease: 'power3.inOut', stagger: 0.08 }, '+=0.05')
    })
  }

  return (
    <div ref={root} className={s.view}>
      <div className={s.page} data-page={page}>
        <span className={s.kicker}>{page === 'a' ? '01' : '02'}</span>
        <h3 className={s.heading}>{labels[page]}</h3>
        <button type="button" className={s.go} onClick={go}>
          {labels.go.replace('{page}', labels[next])} →
        </button>
      </div>
      <span className={`${s.panel} ${s.accent}`} aria-hidden="true" />
      <span className={`${s.panel} ${s.dark}`} aria-hidden="true" />
    </div>
  )
}
demo.module.css
.view {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.page {
  display: grid;
  align-content: center;
  justify-items: start;
  gap: 8px;
  height: 100%;
  padding: 0 32px;
}

.page[data-page='b'] { background: var(--stage-surface); }

.kicker { font: 500 var(--text-sm) / 1 var(--font-mono); color: var(--stage-accent); }

.heading {
  margin: 0 0 8px;
  font: 700 2.5rem / 1 var(--font-display);
  color: var(--stage-ink);
}

.go {
  padding: 0;
  border: 0;
  background: none;
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1.4 var(--font-body);
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
}

.go:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }

.panel {
  position: absolute;
  inset: 0;
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

.accent { background: var(--stage-accent); }
.dark { background: var(--stage-ink); }

投稿创意

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

你想投稿的是?

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