mcpField
M
mask-reveal-transition

mask-reveal-transition

github.com/rheinmirDesign & UI1076 installsUpdated 2w ago
Repository

About mask-reveal-transition

A vanilla HTML, CSS, and JavaScript skill for revealing one image through an expanding organic mask as the page scrolls, or switching images in a pinned split-screen layout. It covers both scroll-progress and active-panel variants, with reduced-motion and browser checks.

Key features

  1. 01Organic radial-mask image reveal
  2. 02Scroll-progress-driven mask radius
  3. 03Sticky split-screen image changes
  4. 04Reduced-motion handling
  5. 05Safari mask-prefix support

Use cases

  1. 01Add an ink-like reveal to a portfolio
  2. 02Change a pinned image as case-study text scrolls

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/rheinmir/setup --skill mask-reveal-transition

After installing, type/mask-reveal-transitionin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

assets/
demo.html
mask-reveal-transition.css
mask-reveal-transition.js

Details

From the project README

Skill: mask-reveal-transition

Component vanilla HTML/CSS/JS — ảnh lộ ra qua một mask hình dạng hữu cơ (vòng loang mực, tâm cố định, bán kính tăng dần) thay vì crossfade/opacity thường. Hai biến thể dùng chung một cơ chế nền: đo tiến trình cuộn TRONG section bằng getBoundingClientRect(), map về 0..1, rồi ghi bán kính loang lên CSS custom property.

WHAT

Purpose và context
  • Purpose: lộ/chuyển ảnh qua mask hình dạng hữu cơ (vòng loang mực) theo tiến trình cuộn, hoặc pin split-screen sticky đổi ảnh qua mask theo panel đang đọc — ưu tiên code gốc nguyên văn ở Rheinmir/uiux-asset, assets/ là bản vanilla nhẹ dự phòng.
  • Trigger (when to use):
    • Chuyển ảnh nghệ thuật/portfolio kiểu "vết mực loang" đồng bộ theo cuộn (không phải crossfade phẳng).
    • Layout case-study/portfolio 2 cột: ảnh cố định bên trái, đoạn văn cuộn bên phải, ảnh đổi theo đoạn đang đọc.
  • Non-goals: KHÔNG dùng cho reveal đơn giản (mờ + trượt nhẹ khi vào viewport) — đó là skills/scroll-effects pattern #1, không có mask hình dạng. KHÔNG dùng cho chuyển ảnh toàn màn hình kiểu zoom-blur nặng GPU — đó là skills/blur (Three.js/shader). Skill này chỉ CSS mask-image, không canvas/WebGL.
Mental model

section → đo progress cuộn (getBoundingClientRect → 0..1) hoặc panel active (IntersectionObserver) → --mrt-r → mask-image radial-gradient trên ảnh "trên" → lỗ trong suốt loang rộng → lộ ảnh "dưới". Hai biến thể: [data-mrt-blob] (ghi --mrt-r mỗi frame, không transition) và [data-mrt-split] (đổi rời rạc theo panel, có transition qua @property).

Input và output contract
FieldRequired?Ý nghĩa
Inảnh (2 ảnh cho blob; mỗi panel một ảnh cho split)cónội dung thay vào bản gốc/asset
Inràng buộc dự án có được kéo lib/file ngoài khôngkhông (mặc định: được)không được → dùng assets/ vanilla
Outtrang/component HTML có hiệu ứng mask revealcóchạy qua HTTP local, có -webkit-mask-image, có nhánh prefers-reduced-motion
Outbằng chứng playwrightcóảnh trước/giữa/sau (biến thể 1), assert đổi ảnh + pin (biến thể 2)
Rules và capabilities
  • RULE-01 (MUST): Hỗ trợ trình duyệt mask-image: Safari (kể cả Safari hiện đại) chỉ nhận -webkit-mask-image/-webkit-mask-repeat — PHẢI viết cả mask-image chuẩn LẪN -webkit-mask-image (đã có sẵn trong CSS asset, đừng xoá prefix khi adapt/minify). Thiếu prefix = ảnh trên không bao giờ có lỗ hổng trên Safari, coi như hiệu ứng không chạy.
  • RULE-02 (MUST): position: sticky không hoạt động nếu BẤT KỲ phần tử cha nào (kể cả xa, lên tới <body>) có overflow: hidden/auto/scroll hoặc clip — đây là lỗi hay gặp nhất khi nhúng .mrt-split-sticky vào layout có sẵn (nhiều trang đặt overflow: hidden trên wrapper để chặn scroll ngang, vô tình giết sticky của mọi phần tử con). Kiểm tra toàn bộ chuỗi cha trước khi báo "sticky không chạy" là bug.
  • RULE-03 (MUST): Biến thể 2 dùng @property --mrt-r để trình duyệt animate được custom property nằm trong calc()/gradient — thiếu khai báo này, đổi --mrt-r bằng JS sẽ nhảy tức thì thay vì mượt qua transition.
  • RULE-04 (MUST): prefers-reduced-motion: reduce → cả 2 biến thể set --mrt-r = bán kính tối đa ngay lập tức (lộ ảnh hoàn toàn), tắt animate/transition — đã code sẵn trong JS/CSS, đừng xoá nhánh này khi adapt.
  • RULE-05 (MUST): Một scroll listener rAF-throttle DUY NHẤT cho mọi [data-mrt-blob] trên trang; một IntersectionObserver DUY NHẤT mỗi [data-mrt-split] cho các panel của nó — không gắn nhiều listener scroll rời hoặc nhiều observer trùng vai trò.
  • RULE-06 (MUST): Verify bằng playwright-verify: chụp trước/giữa/sau khi cuộn qua biến thể 1 để thấy vòng loang lớn dần, và assert biến thể 2 đổi đúng ảnh khi mỗi panel vào giữa khung nhìn + cột ảnh thực sự pin (không cuộn theo trang) trong lúc đọc.
  • Capabilities: đọc/copy file từ repo asset ngoài hoặc assets/ của skill; ghi file HTML/CSS/JS dự án; chạy trình duyệt headless để verify.
Failure boundaries
  • Hiệu ứng không chạy trên Safari → thiếu -webkit-mask-image → sửa prefix, không kết luận là bug khác.
  • Sticky không pin → rà toàn bộ chuỗi cha tìm overflow/clip trước khi báo bug.
  • Mở bằng file:// → không hợp lệ; chạy lại qua HTTP.
  • Yêu cầu thực ra là reveal opacity/translate hoặc chuyển ảnh shader toàn màn hình → không thuộc skill, chuyển skills/scroll-effects hoặc skills/blur.

HOW

Main workflow
StepTypeInputsActionOutputs/exitFailure/next
W01judgmentyêu cầuChọn biến thể (blob hay split) và nguồn: bản gốc (mặc định) hay assets/ vanillalựa chọnkhông được kéo lib ngoài → B01
W02effectpen gốcMở link chạy xem đúng hiệu ứng → copy nguyên file .html (+ _assets/) → giữ comment ghi công → chỉ sửa nội dung/ảnh/màufile trong dự án—
W03deterministictrangChạy local qua HTTP, verify bằng playwright-verifyảnh + assertlỗi Safari/sticky → xem Failure boundaries, sửa, lặp

Chi tiết từng bước (nguồn chân lý cho W01–W03):

Bản gốc — dùng TRƯỚC (copy nguyên file, đừng viết lại)

Code gốc NGUYÊN VĂN của tác giả (CodePen public = MIT) nằm ở repo ngoài Rheinmir/uiux-asset — gallery chạy thật: https://rheinmir.github.io/uiux-asset/scroll-effects/ . Quy trình: mở link chạy để xem đúng hiệu ứng → copy nguyên file .html (+ thư mục _assets/ nếu file trỏ tới) vào dự án → giữ comment ghi công dòng đầu → chỉ sửa nội dung/ảnh/màu. Chạy local qua HTTP, không file://. git clone --depth 1 https://github.com/Rheinmir/uiux-asset nếu cần cả bộ offline.

Pen gốc — tác giảLink
Ink Transition Scroll Effect — iamryanyuchạy · code
Pinned Split-Screen Mask Reveal — gridmorphicchạy · code
Parallax Jungle Leaves Reveal — Mamboleoochạy · code

assets/ trong skill này chỉ là bản rút gọn vanilla tự viết — dùng khi dự án KHÔNG được kéo lib ngoài; mặc định dùng bản gốc ở trên.

Assets

assets/demo.html + assets/mask-reveal-transition.css + assets/mask-reveal-transition.js — đọc rồi ADAPT (đổi ảnh, --mrt-edge độ mềm viền, nội dung panel).

Branches
IDKindGuardHành viSkip / failureRejoin
B01conditional_requireddự án KHÔNG được kéo lib/file ngoàidùng assets/demo.html + .css + .js, đọc rồi ADAPT (ảnh, --mrt-edge, nội dung panel)—W03
B02user_optionalcần cả bộ offlinegit clone --depth 1 https://github.com/Rheinmir/uiux-asset—W02
Validation và stopping

Tất định (playwright): biến thể 1 chụp trước/giữa/sau khi cuộn thấy vòng loang lớn dần; biến thể 2 assert đổi đúng ảnh khi mỗi panel vào giữa khung nhìn + cột ảnh thực sự pin. Cần review: cảm giác "mực loang" đúng chiều (lộ dần, không che dần). Dừng khi các assert trên xanh.

Examples
  • Positive: portfolio case-study 2 cột "ảnh cố định trái, chữ cuộn phải" → copy nguyên pinned-split-screen-mask-reveal.html từ Rheinmir/uiux-asset, đổi ảnh panel → playwright assert mỗi [data-mrt-panel] vào giữa khung thì ảnh đổi và cột ảnh không cuộn theo trang.
  • Boundary/failure: nhúng .mrt-split-sticky vào layout có wrapper overflow: hidden → ảnh cuộn theo trang, không pin → rà chuỗi cha, bỏ overflow ở wrapper, không báo là bug của component. Minify xoá -webkit-mask-image → Safari không có lỗ hổng mask → trả lại prefix.
Reference — Cơ chế nền chung

Với mỗi section ([data-mrt-blob] hoặc [data-mrt-split]): 2 ảnh xếp lớp tuyệt đối trong cùng khung — ảnh dưới (z-index thấp, luôn hiện sẵn) và ảnh trên (z-index cao, mang mask). Ảnh trên có

mask-image: radial-gradient(circle at 50% 50%, transparent 0, transparent var(--mrt-r), black calc(var(--mrt-r) + var(--mrt-edge)), black 100%);

— vùng trong bán kính --mrt-r trong suốt (ảnh trên biến mất tại đó → lộ ảnh dưới), vùng ngoài --mrt-r + --mrt-edge vẫn đặc (ảnh trên che kín). --mrt-r tăng dần = vòng trong suốt loang rộng ra = ảnh dưới "loang" ra từ tâm, đúng cảm giác vết mực — không phải chiều ngược lại (chấm đặc lớn dần), vì như vậy sẽ che dần thay vì lộ dần.

Biến thể 1 — Blob/ink mask reveal ([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 ghi trực tiếp mỗi frame (không CSS transition) theo scroll progress: scroll listener rAF-throttle đo stage.getBoundingClientRect(), progress = (vh - rect.top) / (vh + rect.height) kẹp 0..1, nhân với bán kính tối đa (đường chéo khung / 2 + margin) → over.style.setProperty('--mrt-r', ...). KHÔNG dùng transition ở biến thể này — transition sẽ làm vòng loang trễ nhịp so với vị trí cuộn thật (cùng lý do svg-stroke-reveal mode "scroll" không dùng transition).

Biến thể 2 — Pinned split-screen mask reveal ([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>

Cột ảnh dùng thuần CSS position: sticky (KHÔNG GSAP/ScrollTrigger — pin theo cách rẻ nhất trình duyệt hỗ trợ sẵn). JS chỉ có nhiệm vụ đổi ảnh/mask theo panel đang active: một IntersectionObserver (threshold 0.5) gắn trên từng [data-mrt-panel], khi panel vào tâm khung nhìn → gọi revealTo(src): đẩy ảnh hiện tại lên lớp "trên" (che kín, --mrt-r = 0 tức thì, ép reflow), đặt ảnh mới xuống lớp "dưới", rồi requestAnimationFrame set --mrt-r về bán kính tối đa để CSS transition (đăng ký qua @property --mrt-r { syntax: "<length>"; ... }) tự chạy mượt. Ở đây --mrt-r đổi RỜI RẠC theo sự kiện panel, không theo từng px cuộn — nên (khác biến thể 1) CSS transition trên --mrt-r là đúng chỗ.

Origin

Batch 180926 — bản gốc NGUYÊN VĂN nằm ở Rheinmir/uiux-asset/scroll-effects/ (xem mục "Bản gốc" ở trên); assets/ là bản rút gọn vanilla tự viết.

Comments

Markdown supported