关于 mask-reveal-transition
用于以原生 HTML、CSS 和 JavaScript 实现有机形状遮罩揭示图片,或在固定分栏布局中随内容切换图片。涵盖滚动进度和活动面板两种方式,并要求处理减少动态效果及浏览器兼容。
主要功能
- 01有机径向遮罩揭示图片
- 02遮罩半径随滚动进度变化
- 03固定分栏布局切换图片
- 04支持减少动态效果设置
- 05兼容 Safari 遮罩前缀
适用场景
- 01为作品集添加墨迹扩散式揭示
- 02随案例文字滚动切换固定图片
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rheinmir/ setup --skill mask-reveal-transition安装后在 Claude Code 中输入/mask-reveal-transition确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/demo.htmlmask-reveal-transition.cssmask-reveal-transition.js详细介绍
技能:mask-reveal-transition
原生 HTML/CSS/JS 组件——通过有机形状的遮罩(墨迹晕染圆环,中心固定,半径逐渐增大)显示图片,而非普通的交叉淡入淡出/透明度过渡。两个变体共用同一套底层机制:通过 getBoundingClientRect() 测量 section 内的滚动进度,将其映射到 0..1,然后把晕染半径写入 CSS 自定义属性。
内容
用途与背景
- 用途: 根据滚动进度,通过有机形状的遮罩(墨迹晕染圆环)显示/切换图片;或使用固定分屏布局,左侧 sticky 显示图片,图片根据右侧当前阅读的面板通过遮罩切换——优先使用
Rheinmir/uiux-asset中的原始代码;assets/是轻量的原生备用版本。 - 触发条件(何时使用):
- 艺术作品集风格的图片切换,像“墨迹晕染”一样与滚动同步(不是平滑的交叉淡入淡出)。
- 案例研究/作品集双栏布局:左侧固定图片,右侧滚动文本,图片根据当前阅读的段落切换。
- 非目标: 不要用于简单的显示效果(进入视口时淡入并轻微滑动)——那是
skills/scroll-effects的模式 #1,不包含形状遮罩。不要用于 GPU 开销较大的全屏缩放模糊图片切换——那是skills/blur(Three.js/shader)。此技能只使用 CSSmask-image,不使用 canvas/WebGL。
心智模型
section → 测量滚动进度(getBoundingClientRect → 0..1)或当前活动面板(IntersectionObserver)→ --mrt-r → 对“上层”图片应用 mask-image radial-gradient → 透明孔洞向外晕染 → 显示“下层”图片。两个变体:[data-mrt-blob](每帧写入 --mrt-r,不使用 transition)和 [data-mrt-split](根据面板离散切换,通过 @property 实现 transition)。
输入和输出约定
| 字段 | 必填? | 含义 | |
|---|---|---|---|
| 输入 | 图片(blob 使用 2 张;split 每个面板使用 1 张) | 是 | 替换原始版本/资源中的内容 |
| 输入 | 项目是否允许引入外部库/文件 | 否(默认:允许) | 不允许时 → 使用原生 assets/ |
| 输出 | 带遮罩显示效果的 HTML 页面/组件 | 是 | 通过本地 HTTP 运行,包含 -webkit-mask-image,并包含 prefers-reduced-motion 分支 |
| 输出 | Playwright 验证证据 | 是 | 前/中/后截图(变体 1);断言图片已切换且保持固定(变体 2) |
规则与能力
- RULE-01(MUST):支持浏览器的
mask-image:Safari(包括较新的 Safari)只接受-webkit-mask-image/-webkit-mask-repeat——必须同时编写标准mask-image和-webkit-mask-image(CSS 资源中已包含,不要在适配/压缩时删除前缀)。缺少前缀 = Safari 中的上层图片永远不会出现孔洞,可视为效果未运行。 - RULE-02(MUST):如果任意祖先元素(包括远端祖先,直至
<body>)设置了overflow: hidden/auto/scroll或clip,position: sticky就不会生效——这是将.mrt-split-sticky嵌入现有布局时最常见的错误(许多页面会在 wrapper 上设置overflow: hidden来阻止横向滚动,却意外导致所有子元素的 sticky 失效)。在报告“sticky 不工作”是 bug 之前,检查整个祖先链。 - RULE-03(MUST):变体 2 使用
@property --mrt-r,让浏览器能够动画处理位于calc()/gradient 中的自定义属性——缺少此声明时,通过 JS 更改--mrt-r会立即跳变,而不是平滑过渡。 - RULE-04(MUST):
prefers-reduced-motion: reduce→ 两个变体都立即将--mrt-r设置为最大半径(完全显示图片),并关闭动画/过渡——JS/CSS 中已预先实现此分支,适配时不要删除。 - RULE-05(MUST):页面上所有
[data-mrt-blob]只使用一个经 rAF 节流的 scroll 监听器;每个[data-mrt-split]只使用一个IntersectionObserver观察其面板——不要绑定多个独立的scroll监听器或用途重复的 observer。 - RULE-06(MUST):使用
playwright-verify验证:滚动经过变体 1 时,分别在开始/中间/结束处截图,以确认晕染圆环逐渐扩大;并断言变体 2 在每个面板进入视口中部时切换到正确图片,且阅读过程中图片栏确实保持固定(不随页面滚动)。 - 能力:从外部资源仓库或技能的
assets/中读取/复制文件;编写项目 HTML/CSS/JS 文件;运行无头浏览器进行验证。
失败边界
- 效果在 Safari 上不运行 → 缺少
-webkit-mask-image→ 修复前缀,不要认定为其他 bug。 - Sticky 不固定 → 报告 bug 前,检查整条父级链中的
overflow/clip。 - 通过
file://打开 → 无效;改用 HTTP 重新运行。 - 实际需求是 opacity/translate reveal 或全屏 shader 图像切换 → 不属于此 skill,转到
skills/scroll-effects或skills/blur。
HOW
主要工作流
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | 需求 | 选择变体(blob 或 split)和来源:原版(默认)或 assets/ vanilla | 选择 | 不允许拉取外部 lib → B01 |
| W02 | effect | 原版 pen | 打开运行链接确认效果正确 → 复制完整 .html 文件(+ _assets/)→ 保留署名注释 → 只修改内容/图片/颜色 | 项目中的文件 | — |
| W03 | deterministic | 页面 | 通过 HTTP 在本地运行,使用 playwright-verify 验证 | 截图 + 断言 | Safari/sticky 出错 → 查看 Failure boundaries,修复后重试 |
各步骤详情(W01–W03 的事实来源):
原版 — 优先使用(复制完整文件,不要重写)
作者的原始代码(公开 CodePen = MIT)位于外部 repo Rheinmir/uiux-asset — 可运行 gallery:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。
流程:打开运行链接确认效果正确 → 将完整 .html 文件(+ 文件引用的 _assets/ 目录)复制到项目中 → 保留首行署名注释 → 只修改内容/图片/颜色。通过 HTTP 在本地运行,不要使用 file://。
如需离线获取完整套件,可运行 git clone --depth 1 https://github.com/Rheinmir/uiux-asset。
| 原版 Pen — 作者 | 链接 |
|---|---|
| Ink Transition Scroll Effect — iamryanyu | 运行 · 代码 |
| Pinned Split-Screen Mask Reveal — gridmorphic | 运行 · 代码 |
| Parallax Jungle Leaves Reveal — Mamboleoo | 运行 · 代码 |
此 skill 中的 assets/ 只是自行编写的精简 vanilla 版本 — 仅在项目不允许拉取外部 lib 时使用;默认使用上面的原版。
Assets
assets/demo.html + assets/mask-reveal-transition.css + assets/mask-reveal-transition.js — 阅读后进行 ADAPT(更换图片、调整 --mrt-edge 边缘柔和度、修改面板内容)。
分支
| ID | Kind | Guard | 行为 | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | 项目不允许拉取外部 lib/file | 使用 assets/demo.html + .css + .js,阅读后进行 ADAPT(图片、--mrt-edge、面板内容) | — | W03 |
| B02 | user_optional | 需要离线获取完整套件 | git clone --depth 1 https://github.com/Rheinmir/uiux-asset | — | W02 |
验证与停止
确定性验证(playwright):变体 1 在滚动前/中/后截图,确认墨迹圆环逐渐变大;变体 2 断言每个面板进入视口中央时图片正确切换,且图片列确实固定。需要人工审核:确认“墨迹扩散”的方向正确(逐渐显露,而不是逐渐遮盖)。上述断言全部通过后停止。
示例
- 正例: 双列作品集案例研究,“左侧图片固定,右侧文字滚动” → 从
Rheinmir/uiux-asset复制完整的pinned-split-screen-mask-reveal.html,更换面板图片 → 使用 playwright 断言每个[data-mrt-panel]进入视口中央时图片会切换,且图片列不会随页面滚动。 - 边界/失败: 将
.mrt-split-sticky嵌入带有overflow: hidden的布局 wrapper → 图片随页面滚动,无法固定 → 检查父级链,移除 wrapper 上的overflow,不要报告为组件 bug。Minify 移除了-webkit-mask-image→ Safari 中没有 mask 镂空效果 → 恢复该前缀。
参考 — 通用底层机制
对于每个 section([data-mrt-blob] 或 [data-mrt-split]):同一框架内绝对定位叠放 2 张图片——下层图片(z-index 较低,始终预先显示)和上层图片(z-index 较高,带有蒙版)。上层图片具有
mask-image: radial-gradient(circle at 50% 50%, transparent 0, transparent var(--mrt-r), black calc(var(--mrt-r) + var(--mrt-edge)), black 100%);
— 半径 --mrt-r 内的区域透明(上层图片在此处消失 → 显露下层图片),--mrt-r + --mrt-edge 之外的区域仍为不透明(上层图片完全遮挡)。--mrt-r 逐渐增大 = 透明圆逐渐扩展 = 下层图片从中心“晕开”,呈现墨迹扩散的感觉——而不是反过来(实心圆点逐渐变大),因为那样只会逐渐遮挡,而不是逐渐显露。
变体 1 — Blob/墨迹蒙版显露([data-mrt-blob])
<section data-mrt-blob>
<div class="mrt-blob-frame">
<img class="mrt-blob-under" src="B.jpg" alt="…">
<img class="mrt-blob-over" src="A.jpg" alt="…">
</div>
</section>
每一帧都根据滚动进度直接设置 --mrt-r(不使用 CSS transition):滚动监听器通过 rAF 限流,测量 stage.getBoundingClientRect(),progress = (vh - rect.top) / (vh + rect.height) 并限制在 0..1,再乘以最大半径(框架对角线 / 2 + margin)→ over.style.setProperty('--mrt-r', ...)。此变体不使用 transition——transition 会导致扩散圆相对于真实滚动位置滞后(原因与 svg-stroke-reveal 的 "scroll" 模式不使用 transition 相同)。
变体 2 — 固定分屏蒙版显露([data-mrt-split])
<div class="mrt-split-section" data-mrt-split>
<div class="mrt-split-sticky">
<img class="mrt-split-under" alt="…">
<img class="mrt-split-over" alt="">
</div>
<div class="mrt-split-scroll">
<article data-mrt-panel data-mrt-src="1.jpg">…</article>
<article data-mrt-panel data-mrt-src="2.jpg">…</article>
</div>
</div>
图片列纯粹使用 CSS position: sticky(不使用 GSAP/ScrollTrigger——采用浏览器原生支持的最简单固定方式)。JS 仅负责根据当前 active 面板切换图片/蒙版:在每个 [data-mrt-panel] 上附加一个 IntersectionObserver(threshold 0.5),当面板进入视口中心时 → 调用 revealTo(src):将当前图片移到“上层”(完全遮挡,立即设置 --mrt-r = 0,强制 reflow),将新图片放到“下层”,然后通过 requestAnimationFrame 将 --mrt-r 设回最大半径,让 CSS transition(通过 @property --mrt-r { syntax: "<length>"; ... } 注册)平滑运行。这里 --mrt-r 会随面板事件离散变化,而不是随着滚动的每个 px 变化——因此(与变体 1 不同)在 --mrt-r 上使用 CSS transition 才是合适的。
来源
Batch 180926 — 原始版本原样保存在 Rheinmir/uiux-asset/scroll-effects/(参见上方“原始版本”一节);assets/ 是手写的精简 vanilla 版本。