肯·伯恩斯平移
静态图片在画框中缓慢放大并漂移,这是让照片像影片一样流动的纪录片手法。
用 CSS 为 React 首屏图片制作肯·伯恩斯效果。把 <img>(object-fit: cover)放进一个 overflow hidden、固定宽高比的画框中。用一个往返播放的 keyframe 为图片做动画:从 scale(1) translate(0, 0) 到 scale(1.18) translate(-4%, -3%),时长 14 秒,ease-in-out,infinite alternate,这样循环重新开始时不会跳动。把 transform-origin 设在主体附近(例如 60% 40%)。底部叠加一层从透明到 60% 黑色的渐变,并在角落显示说明文字“黄昏时的兰卡威”。添加 will-change: transform。画框离开屏幕时用 animation-play-state: paused 暂停动画(IntersectionObserver),在 prefers-reduced-motion 下显示静态图片。
源代码
'use client'
import { useEffect, useRef } from 'react'
import s from './demo.module.css'
export default function KenBurns({ labels }: { labels: Record<string, string> }) {
const ref = useRef<HTMLElement>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const io = new IntersectionObserver(([e]) => el.toggleAttribute('data-paused', !e.isIntersecting))
io.observe(el)
return () => io.disconnect()
}, [])
return (
<figure ref={ref} className={s.frame}>
<div className={s.scene} role="img" aria-label={labels.caption} />
<figcaption className={s.caption}>{labels.caption}</figcaption>
</figure>
)
}
.frame {
--sky-top: oklch(42% 0.09 300);
--sky-low: oklch(70% 0.14 45);
--sun: oklch(88% 0.11 75);
--sea: oklch(36% 0.07 250);
--island: oklch(22% 0.03 280);
--shade: oklch(0% 0 0 / 0.6);
position: relative;
width: min(300px, 100%);
aspect-ratio: 16 / 10;
max-width: 100%;
margin: 0;
overflow: hidden;
border-radius: 14px;
}
.scene {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 22% 14% at 30% 62%, var(--island) 97%, transparent 100%),
radial-gradient(ellipse 34% 18% at 78% 64%, var(--island) 97%, transparent 100%),
linear-gradient(180deg, transparent 62%, var(--sea) 62%),
radial-gradient(circle at 58% 56%, var(--sun) 0 10%, transparent 10.5%),
linear-gradient(180deg, var(--sky-top), var(--sky-low) 62%);
transform-origin: 60% 40%;
will-change: transform;
animation: drift 14s ease-in-out infinite alternate;
}
.frame[data-paused] .scene { animation-play-state: paused; }
.frame::after {
content: '';
position: absolute;
inset: 50% 0 0;
background: linear-gradient(transparent, var(--shade));
}
.caption {
position: absolute;
left: 12px;
bottom: 10px;
z-index: 1;
font: 500 var(--text-sm) / 1.2 var(--font-body);
color: var(--stage-ink);
}
@keyframes drift {
from { transform: scale(1) translate(0, 0); }
to { transform: scale(1.18) translate(-4%, -3%); }
}
@media (prefers-reduced-motion: reduce) {
.scene { animation: none; }
}