首页修改记录 07:最近摄影光圈最终版本的分割和动画
最终版本把最近摄影拆成外部照片叶片、中心模拟光圈和鼠标预览三层。照片靠 clip-path 分割,间隔靠底色露出,动画共享同一个 progress。
最近摄影光圈最终能稳定下来,是因为它不再靠视觉补丁,而是把结构拆成三层:
- 外部照片叶片。
- 中心模拟光圈。
- 鼠标悬停预览。
这三层各自负责自己的事情,不再互相画线、互相遮挡。
外部照片如何分割
外圈照片最多取 6 张。每张照片对应一个扇区,扇区不是普通饼图,而是带一点旋转错位的光圈式扇区。
核心数据来自 apertureWheelGeometry(progress):
const geometry = apertureWheelGeometry(progress);
const sector = geometry.sectors[index];
const imageStyle = {
clipPath: sector.clipPath,
WebkitClipPath: sector.clipPath,
opacity: geometry.progress,
filter: `blur(${8 * (1 - geometry.easedProgress)}px)`,
};clipPath 是一个 polygon(...)。它由内圆半径、外圆半径、起止角度和侧边内缩共同决定。
和早期版本不同的是,照片之间没有 SVG 分隔线。每个照片扇区的左右边界都会向内部移动 PHOTO_SECTOR_GAP_WIDTH / 2,相邻两片各让出半份,底色自然变成间隔。
这样 hover 时浮起来的是完整照片叶片。没有另一层线条挡在上面,也没有固定外圆边界暴露出来。
中心光圈如何生成
中心光圈也不是图片,而是 SVG 几何。
apertureGlyphGeometry(progress) 生成 6 个 blade path。每个叶片从外圆弧连到中心六边形边缘,中心洞由叶片围出来。
<circle r={glyph.bodyR} fill={apertureBasePaint} />
{glyph.blades.map((blade) => (
<path key={blade.index} d={blade.path} fill={bladePaint} />
))}
<path d={glyph.holePath} fill={cutoutPaint} />
<circle r={glyph.outerRingR} fill="none" stroke={ringPaint} />这里仍然有一个 holePath,但它的作用是把中心洞明确画成底色,不是额外放一个突兀的白色块或黑色块。它使用和面板一致的 theme-aware 颜色,所以明暗主题都不会跳。
外圆环只保留中心光圈自己的环。照片圆盘外侧不再额外描边,因为照片叶片 hover 后会离开原位,固定外框只会暴露结构。
动画如何接在一起
这次动画统一由一个 motionProgress 推动:
const motionProgress = useMotionValue(0);
const apertureProgress = progress;
const photoProgress = clampProgress((progress - 0.22) / 0.78);中心光圈从一开始就参与变化;外部照片稍晚启动。这样视觉上像是镜头先打开,再把照片向外展开。
动画时间被拉到可以被看见,但仍然不应该拖慢内容:
animate(motionProgress, 1, {
duration: 2.45,
delay: 0.35,
ease: [0.22, 1, 0.36, 1],
});prefers-reduced-motion 下直接把 progress 设为 1。也就是说,动画是增强体验,不是访问内容的条件。
hover 只移动叶片本身
早期 hover 是内部图片在框里放大。后来改成整个照片叶片浮起:
<li className="motion-safe:hover:-translate-y-1 motion-safe:hover:scale-[1.018]">
<Link>
<Image className="object-cover" />
</Link>
</li>这一步很重要。因为照片已经被 clip-path 切成叶片,hover 的对象也必须是这个被切好的叶片,而不是里面的图片。否则视觉上会变成“图片在框里晃”,不像叶片被抬起。
现在没有独立分隔线,所以叶片浮起后不会被线条穿过。底色间隔仍然留在原位,像叶片被拿起后下面露出的空隙。
鼠标预览为何独立
照片叶片本身为了保持圆形构图,图片会 object-cover。这意味着有些照片主体可能被裁掉。于是 hover 增加了一个跟随鼠标的预览:
previewPositionFromPoint(x, y, width, height)它根据鼠标在圆盘里的位置决定预览在左侧还是右侧,避免固定在右上角挡住不同照片。预览图用 object-contain,尽量显示照片全貌。
这样主视觉负责“光圈照片轮盘”,预览负责“看清这张照片”。两者不用互相妥协。
最终版本为什么更稳
最后的稳定性来自几个删除:
- 删除固定照片外圆。
- 删除照片分隔线 SVG。
- 删除内部图片 hover scale。
- 删除中心黑色或白色硬填充。
留下的是可计算的几何关系:
- 照片扇区由 clip-path 生成。
- 间隔由侧边内缩露出底色。
- 中心叶片由 SVG path 生成。
- 动画由同一个 progress 推动。
这就是这次最近摄影板块反复修改后真正收敛的地方:不要用覆盖层模拟结构,让结构本身产生视觉。

