threejs-particle-morph
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSkill: threejs-particle-morph
Skill: threejs-particle-morph
Component Three.js thuần — hàng nghìn particle rải ngẫu nhiên trong không gian 3D, hội tụ dần thành ảnh sample từ pixel khi user cuộn trang, tan rã lại khi cuộn ngược.
纯 Three.js component——数千个 particle 随机散布在 3D 空间中;随着用户滚动页面,particle 会逐渐汇聚成由像素采样得到的图像,反向滚动时则重新散开。
WHAT
功能说明
Purpose và context
目的与上下文
- Purpose: dựng một particle cloud Three.js (+
THREE.Points) hội tụ từ vị trí ngẫu nhiên thành ảnh/logo theo tiến độ cuộn trang và tan rã lại khi cuộn ngược — ưu tiên copy nguyên bản gốc, bản vanilla trongBufferGeometrylà dự phòng.assets/ - Trigger (when to use):
- Hero section/portfolio cần hiệu ứng "ảnh được ghép lại từ các hạt" khi cuộn tới.
- User nói "particle image", "hạt hội tụ thành ảnh", "point cloud morph", "three.js particle scroll".
- Non-goals: KHÔNG dùng cho hiệu ứng chuyển ảnh liền mạch kiểu zoom-blur (đó là — shader trên 1 plane, không có particle rời rạc) và KHÔNG dùng cho hiệu ứng cuộn nhẹ không cần WebGL (đó là
blur). Không viết lại code gốc của tác giả.scroll-effects
- 目的: 构建一个 Three.js particle cloud(+
THREE.Points),根据页面滚动进度从随机位置汇聚成图像/logo,反向滚动时重新散开——优先完整复制原始版本;BufferGeometry中的 vanilla 版本作为备用方案。assets/ - 触发条件(何时使用):
- Hero section/portfolio 需要在滚动到此处时呈现“图像由 particle 拼合而成”的效果。
- 用户说出“particle image(粒子图像)”“hạt hội tụ thành ảnh(粒子汇聚成图像)”“three.js particle scroll”“point cloud morph”“particle cloud biến hình theo scroll(随 scroll 变形的 particle cloud)”。
- 非目标: 不用于类似 zoom-blur 的平滑图像切换效果(那是 ——在 1 个 plane 上运行的 shader,不包含离散 particle),也不用于不需要 WebGL 的轻量滚动效果(那是
blur)。不要重写原作者的代码。scroll-effects
Mental model
心智模型
ảnh nguồn → canvas ẩn (sampleResolution) → getImageData + stride + alphaThreshold → targets (+ màu) · starts random → scroll → targetProgress → progress (damping) → position = lerp(starts, targets, progress) → PointsMaterial render源图像 → 隐藏 canvas(sampleResolution)→ getImageData + stride + alphaThreshold → targets(+颜色)· starts 随机 → scroll → targetProgress → progress(damping)→ position = lerp(starts, targets, progress) → 使用 PointsMaterial 渲染Input và output contract
输入与输出约定
| Field | Required? | Ý nghĩa | |
|---|---|---|---|
| In | ảnh/logo nguồn ( | không | thiếu → demo tự vẽ chữ mẫu lên canvas ẩn để sample |
| In | | không | tham số khởi tạo |
| In | ràng buộc dự án (được kéo lib ngoài không?) | có | quyết định bản gốc (mặc định) hay |
| Out | component trong dự án | có | file gốc copy nguyên (giữ comment ghi công) hoặc |
| Out | bằng chứng verify | có | ảnh chụp progress 0/0.5/1 + console sạch |
| 字段 | 必填? | 含义 | |
|---|---|---|---|
| 输入 | 源图像( | 否 | 缺失时 → demo 会在隐藏 canvas 上绘制示例文字用于采样 |
| 输入 | | 否 | |
| 输入 | 项目约束(是否允许拉取外部库?) | 是 | 决定使用原始版本(默认)还是 |
| 输出 | 项目中的 component | 是 | 完整复制的原始文件(保留署名 comment),或已适配的 |
| 输出 | 验证证据 | 是 | progress 为 0/0.5/1 时的截图 + 无报错的 console |
Rules và capabilities
规则与能力
- RULE-01 (MUST): Hiệu năng: số particle càng nhiều càng nặng CPU (vòng lặp ghi lại mỗi frame) lẫn GPU. Ngưỡng an toàn gợi ý cho máy tầm trung: ≤ 5.000–10.000 particle. Đo bằng DevTools Performance/FPS meter TRƯỚC KHI tăng
position, đừng đoán — nhất là khi thêm particle cho ảnh lớn/nhiều chi tiết (tăngparticleCountcũng tăng chi phí sample lúcsampleResolution, dù chỉ chạy một lần).init() - RULE-02 (MUST): Ba container () phải cài qua npm (
three) và import qua bundler cho production — bản demo dùngnpm install threetrỏ CDN (importmap) CHỈ để chạy độc lập không cần bundler, không dùng bản CDN cũ ghim version nhưunpkg.com/three@0.169.0từng làm. Khi wire vào dự án thật: xoáblur,<script type="importmap">, đổinpm install threesang import qua bundler.import * as THREE from 'three' - RULE-03 (SHOULD): /brightness filter trong
alphaThresholdloại pixel nền trong suốt/tối — ảnh nền trong suốt (PNG logo) cho kết quả gọn hơn ảnh nền đặc (JPEG); ảnh nền đặc sẽ sample luôn cả nền trừ khi crop trước._buildSamples() - RULE-04 (MUST): Verify bằng — chụp ở progress 0/0.5/1 (cuộn tới 3 mốc), đọc console (WebGL context lỗi thường im lặng, không throw JS error rõ ràng).
playwright-verify - RULE-05 (MUST): Bản gốc dùng TRƯỚC — copy nguyên file, đừng viết lại; 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:// - Capabilities: đọc/copy asset từ repo ngoài công khai; ghi file front-end vào dự án; chạy trang qua HTTP + trình duyệt headless để chụp/đọc console.
- RULE-01(必须):性能:particle 数量越多,CPU(每帧重写 属性的循环)和 GPU 负担越重。对于中等性能设备,建议的安全上限为 ≤ 5,000–10,000 个 particle。在增加
position之前,应先使用 DevTools Performance/FPS meter 实测,不要凭感觉判断——尤其是在为更大或细节更多的图像增加 particle 时(提高particleCount也会增加sampleResolution时的采样成本,尽管只执行一次)。init() - RULE-02(必须):名为 的 container 必须通过 npm 安装(
three),并在 production 中通过 bundler import。demo 版本使用指向 CDN(npm install three)的unpkg.com/three@0.169.0,仅用于无需 bundler 即可独立运行;不要像importmap过去那样使用锁定旧版本的 CDN。接入真实项目时:删除blur,执行<script type="importmap">,并将npm install three改为通过 bundler import。import * as THREE from 'three' - RULE-03(应该):中的
_buildSamples()/亮度滤镜会移除透明或深色背景像素——透明背景图像(PNG logo)的结果比实色背景图像(JPEG)更干净;实色背景图像会连背景一起采样,除非事先裁剪。alphaThreshold - RULE-04(必须):使用 进行验证——滚动至 progress 0/0.5/1 三个节点并截图,读取 console(WebGL context 错误通常不会明确抛出,也不会显示清晰的 JS error)。
playwright-verify - RULE-05(必须):优先使用原始版本——完整复制文件,不要重写;保留文件首行的署名 comment;只修改内容、图像或颜色;通过 HTTP 在本地运行,不要使用 。
file:// - 能力:从公开外部 repo 读取/复制 asset;将前端文件写入项目;通过 HTTP + headless 浏览器运行页面,以截图并读取 console。
Failure boundaries
失败边界
- Không mở được link gốc / không kéo được lib ngoài → dùng vanilla (nhánh B01).
assets/ - FPS tụt khi tăng → blocked việc tăng thêm: đo lại, giữ ≤ 5.000–10.000 particle.
particleCount - Console báo lỗi WebGL context (thường im lặng) → failed verify, chưa bàn giao.
- Ảnh nền đặc sample cả nền → crop trước hoặc dùng PNG nền trong suốt.
- 无法打开原始链接/无法拉取外部库 → 使用 中的 vanilla 版本(分支 B01)。
assets/ - 提高 后 FPS 下降 → 阻止继续增加:重新测量,并将数量保持在 ≤ 5,000–10,000 个 particle。
particleCount - Console 报告 WebGL context 错误(通常不会明确抛出)→ 验证 失败,暂不交付。
- 实色背景的图像会把背景一并采样 → 先裁剪,或改用透明背景的 PNG。
HOW
实施流程
Main workflow
主工作流
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | ràng buộc dự án | Chọn nguồn: bản gốc | nguồn đã chọn | không kéo lib ngoài → B01 |
| W02 | effect | link chạy + code | Mở link chạy xem đúng hiệu ứng → copy nguyên file | file trong dự án, còn comment ghi công | — |
| W03 | effect | file | Chỉ sửa nội dung/ảnh/màu (hoặc | component đã adapt | — |
| W04 | deterministic | trang qua HTTP | Verify bằng | 3 ảnh + console sạch | lỗi WebGL/FPS → sửa, lặp W03 |
| W05 | effect | dự án thật | Wire production: xoá importmap, | build chạy | — |
Chi tiết từng bước (nguồn chân lý cho W01–W05):
| 步骤 | 类型 | 输入 | 操作 | 输出/退出 | 失败/下一步 |
|---|---|---|---|---|---|
| W01 | 判断 | 项目约束 | 选择来源:原始版本 | 已选定来源 | 无法拉取外部库 → B01 |
| W02 | 效果 | 运行链接 + 代码 | 打开实际运行的链接以确认效果 → 将原始 | 项目中的文件,并保留署名 comment | — |
| W03 | 效果 | 文件 | 仅修改内容、图像或颜色(或在 vanilla 版本中修改 | 已适配的 component | — |
| W04 | 确定性验证 | 通过 HTTP 访问的页面 | 使用 | 3 张截图 + 无报错的 console | WebGL/FPS 错误 → 修复并重复 W03 |
| W05 | 效果 | 真实项目 | 接入 production:删除 importmap,执行 | 可正常运行的 build | — |
各步骤详情(W01–W05 的唯一事实来源):
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 — 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 (+ thư mục 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 .
nếu cần cả bộ offline.
Rheinmir/uiux-asset.html_assets/file://git clone --depth 1 https://github.com/Rheinmir/uiux-asset| Pen gốc — tác giả | Link |
|---|---|
| Scroll-Driven Particle WebGL Image Matrix — ycw | chạy · code |
| GSAP ScrollSmoother and Three.js — cmalven | chạy · code |
| GSAP ScrollTrigger Disintegration Effect — dev_loop | chạy · code |
assets/作者原封不动的原始代码(CodePen public = MIT)位于外部 repo ——实际运行 gallery:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。
Rheinmir/uiux-asset流程:先打开实际运行的链接以确认正确效果 → 将原始 文件(如果文件有引用,还包括 目录)完整复制到项目 → 保留文件首行的署名 comment → 仅修改内容、图像或颜色。通过 HTTP 在本地运行,不要使用 。
.html_assets/file://如果需要完整离线版本,可执行 。
git clone --depth 1 https://github.com/Rheinmir/uiux-asset| 原始 Pen——作者 | 链接 |
|---|---|
| Scroll-Driven Particle WebGL Image Matrix — ycw | 运行 · 代码 |
| GSAP ScrollSmoother and Three.js — cmalven | 运行 · 代码 |
| GSAP ScrollTrigger Disintegration Effect — dev_loop | 运行 · 代码 |
本 skill 中的 只是自行编写的精简 vanilla 版本——仅在项目不允许拉取外部库时使用;默认使用上述原始版本。
assets/Assets
资源
assets/demo.htmlassets/demo.cssassets/demo.jsimageUrlparticleCountparticleSizeParticleMorphimageUrlassets/demo.htmlassets/demo.cssassets/demo.jsParticleMorphimageUrlparticleCountparticleSizeimageUrlCơ chế
实现机制
- Sample ảnh: vẽ ảnh nguồn (hoặc canvas vẽ tay) lên ẩn kích thước
<canvas>, đọcsampleResolution. Duyệt pixel theo bước nhảy (getImageData()tính từstridemong muốn so với tổng pixel) — KHÔNG lấy hết mọi pixel, quá nặng — giữ pixel đủ sáng/không trong suốt (particleCount) làm toạ độ đích + màu RGB.alphaThreshold - Particle: mỗi toạ độ sample = 1 particle trong (
THREE.PointsattributeBufferGeometry). Vị trí khởi tạo (position) random trong một khối không gian lớn ("nổ tung"), vị trí đích (starts) là toạ độ pixel đã sample, lưu song song trong haitargetsriêng (không phải hai attribute GPU).Float32Array - Scroll → progress: một scroll listener duy nhất, rAF-throttle (không GSAP/ScrollTrigger — bản demo cố tình viết thuần để không trùng phụ thuộc với ), tính
gsap-scrolltrigger-pintừtargetProgress.scrollY / (scrollHeight - innerHeight) - Lerp mỗi frame: được làm mượt dần tới
progress(damping đơn giản, không phải animate trực tiếptargetProgress), rồi mỗi phần tửscrollY, setposition[i] = lerp(starts[i], targets[i], progress).geometry.attributes.position.needsUpdate = true - Màu + kích thước: — giữ màu ảnh gốc qua attribute
PointsMaterial({ size, sizeAttenuation: true, vertexColors: true })tĩnh (set một lần lúc build, không đổi theo progress).color - Cleanup: viết dạng class có — huỷ
destroy(), gỡrequestAnimationFrame/scrolllistener, gọiresize,geometry.dispose(),material.dispose().renderer.dispose()
- 图像采样:将源图像(或手绘 canvas)绘制到尺寸为 的隐藏
sampleResolution上,然后读取<canvas>。按步长(getImageData())遍历像素(该步长根据期望的stride与总像素数计算)——不要取用全部像素,否则负担过重——保留足够明亮/不透明(particleCount)的像素,将其作为目标坐标和 RGB 颜色。alphaThreshold - Particle:每个采样坐标对应 中的 1 个 particle(
THREE.Points的BufferGeometryattribute)。初始位置(position)随机分布于一个很大的 3D 空间(“爆散”),目标位置(starts)为采样得到的像素坐标;两者分别并行存储在两个targets中(不是两个 GPU attribute)。Float32Array - Scroll → progress:仅使用一个 scroll listener,并通过 rAF 节流(不使用 GSAP/ScrollTrigger——demo 特意采用纯原生实现,以免与 重复依赖),根据
gsap-scrolltrigger-pin计算scrollY / (scrollHeight - innerHeight)。targetProgress - 每帧 Lerp:通过简单 damping 平滑逼近
progress(而不是直接对targetProgress做 animation),随后每个元素都执行scrollY,并设置position[i] = lerp(starts[i], targets[i], progress)。geometry.attributes.position.needsUpdate = true - 颜色与尺寸:——通过静态
PointsMaterial({ size, sizeAttenuation: true, vertexColors: true })attribute 保留原图颜色(build 时设置一次,不随 progress 改变)。color - 清理:以包含 的 class 形式实现——取消
destroy(),移除requestAnimationFrame/scrolllistener,并调用resize、geometry.dispose()、material.dispose()。renderer.dispose()
Branches
分支
| ID | Kind | Guard | Hành vi | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | dự án KHÔNG được kéo lib ngoài | dùng | — | W04 |
| B02 | capability_optional | cần cả bộ offline | | có mạng tới gallery → skip | W02 |
| ID | 类型 | 条件 | 行为 | 跳过/失败 | 汇合点 |
|---|---|---|---|---|---|
| B01 | conditional_required | 项目不允许拉取外部库 | 使用 | — | W04 |
| B02 | capability_optional | 需要完整离线版本 | | 可访问 gallery → 跳过 | W02 |
Validation và stopping
验证与停止条件
Kiểm bằng chạy thật: chụp 3 mốc cuộn + đọc console (WebGL lỗi thường im lặng); hiệu năng đo bằng DevTools Performance/FPS meter, không đoán. Review mắt: ảnh hội tụ đúng hình ở progress 1, tan rã ở 0. Dừng khi 3 ảnh đúng + console sạch.
playwright-verify通过实际运行进行验证:使用 截取 3 个滚动节点并读取 console(WebGL 错误通常不会明确抛出);使用 DevTools Performance/FPS meter 测量性能,不要凭感觉判断。通过视觉检查:progress 为 1 时 particle 正确汇聚成图像,为 0 时正确散开。当 3 张截图正确且 console 无报错时停止。
playwright-verifyExamples
示例
- Positive: hero portfolio cần logo PNG hội tụ khi cuộn → copy nguyên từ gallery, giữ comment ghi công, đổi ảnh logo → chụp 0/0.5/1 thấy hạt rải → nửa hình → logo đủ, console sạch.
scroll-driven-particle-webgl-image-matrix.html - Boundary/failure: dự án cấm lib CDN ngoài → dùng ,
assets/demo.js30.000 làm FPS tụt → hạ về ≤ 10.000 theo đo, rồi verify lại.particleCount - Boundary: user muốn chuyển ảnh A→B kiểu zoom-blur trên một plane → không dùng skill này, chuyển .
blur
- 正向示例: hero portfolio 需要在滚动时让 logo PNG 汇聚成形 → 从 gallery 完整复制 ,保留署名 comment,替换 logo 图像 → 截取 0/0.5/1 三个状态,可见 particle 散开 → 图像呈现一半 → 完整 logo,且 console 无报错。
scroll-driven-particle-webgl-image-matrix.html - 边界/失败: 项目禁止使用外部 CDN → 使用 ;
assets/demo.js设为 30,000 后 FPS 下降 → 根据实测降至 ≤ 10,000,然后重新验证。particleCount - 边界: 用户希望实现 A→B 的 zoom-blur 图像切换,且效果位于单个 plane 上 → 不使用本 skill,改用 。
blur