mcpField
I
infinite-webgl-grid

infinite-webgl-grid

github.com/rheinmir设计与 UI1075 次安装2 周前更新
代码仓库

关于 infinite-webgl-grid

infinite-webgl-grid 是用于网页作品集和图片墙的 Three.js 技能,可通过拖动或滚轮浏览向各方向延伸的图片网格。它使用坐标环绕复用固定数量的网格和纹理,而非持续创建或销毁网格。

主要功能

  1. 01双向拖动和滚轮浏览
  2. 02坐标环绕实现无限网格
  3. 03固定网格复用纹理
  4. 04可配置网格与图块参数
  5. 05支持离线占位图演示

适用场景

  1. 01为作品集制作可拖动图片墙
  2. 02构建可向各方向浏览的海报墙
  3. 03制作无尽延伸的图片画廊

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rheinmir/setup --skill infinite-webgl-grid

安装后在 Claude Code 中输入/infinite-webgl-grid确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
demo.css
demo.html
demo.js

详细介绍

技能:infinite-webgl-grid

纯 Three.js 组件——固定的 N×N 图片平铺网格,通过拖动(pointer)或滚动(wheel)进行浏览,仿佛网格向各个方向无限延伸。使用坐标环绕(modulo offset)技术,不会持续生成/销毁 mesh。

内容

用途与背景
  • **用途:**使用坐标环绕构建可向各个方向无限浏览(拖动/滚动)的 WebGL N×N 图片网格,优先原样复制 Rheinmir/uiux-asset 中的原始版本。
  • 触发条件(适用场景):
    • 作品集/画廊/海报墙需要营造向各个方向“无限浏览”的感觉(而不是常规的单向滚动)。
    • 用户提到“无限图片网格”、“infinite scroll grid webgl”、“可拖动图片墙”、“无限滚动海报墙”。
  • 不适用场景:
    • 需要粒子随滚动进度汇聚成一张特定图片时不要使用(这应使用 threejs-particle-morph——它有“已到达目标”的状态,而这里没有目标,只能持续浏览)。
    • 不要用于同一画框中两张图片之间的切换效果(这应使用 blur)。
心智模型

虚拟 offset(拖动/滚动/inertia)→ 每个 mesh 固定不动:wrap(localX - offset.x) → 显示位置 → 虚拟 tile 索引变化 → 从固定纹理池中更换 material.map。相机保持静止,运行时不添加/移除 mesh(详情:参考 — 机制)。

输入与输出约定
字段必填?含义
输入图片/内容 + 颜色否缺少时 → demo 会自动绘制 placeholder canvas,可离线运行
输入项目约束:是否允许引入外部 lib/file是允许 → 原始版本;不允许 → assets/ vanilla
输入gridSize/tileSize/gap/poolSize否InfiniteGrid 的初始化参数
输出.html 文件(+ _assets/)或已适配到项目中的 demo.html/.css/.js是如果是原始版本,首行保留署名注释
输出Playwright 验证证据是向 4 个方向拖动前/中/后的截图 + 无错误的 console
规则与能力
  • RULE-01(MUST):限制同时存在的 tile/texture 数量:移动设备 GPU 对同时工作的 texture unit 数量有限制。实际显示的网格(不包括 wrap 缓冲区)应保持在约 ≤ 5×5 个 tile 同时显示;demo 默认 gridSize=7(显示 5 个 + 每侧 1 个缓冲 tile,以便 tile 平滑跳转,不出现 pop-in)。如果需要的不同图片数量超过可见 tile 数量 → 使用 texture atlas(将多张图片合并到 1 个 texture,通过更改 UV offset 替代为每张图片单独更改 material.map),不要无限增加 poolSize——如果代码保留引用,每个独立 texture 即使不同时显示,也会占用 1 个 texture unit。
  • RULE-02(MUST):必须使用 Passive: false:wheel 和 pointermove 监听器必须使用 { passive: false } 注册,并在适当的位置调用 preventDefault()(仅在正在拖动或位于 canvas 内时)——缺少此标记时,现代浏览器默认采用 passive,preventDefault() 会被静默忽略(没有错误,也没有警告),页面背景仍会与网格同时滚动/缩放,导致移动设备上出现卡顿/冲突。容器上的 CSS touch-action: none 也必须设置,但不能替代 preventDefault。
  • RULE-03(SHOULD):demo 使用 Ortho camera(网格平面,无透视变形)——如果需要深度效果(远处 tile 逐渐模糊/缩小),可改用 PerspectiveCamera;此时 wrap 公式保持不变,只需更改 world-unit 到 pixel 的映射方式。
  • RULE-04(MUST):使用 playwright-verify 进行验证——大力拖动时截取前/中/后的画面(测试全部 4 个方向),并读取 console(WebGL 上下文错误通常不会在 canvas 上显示,也不会明确抛出 JS error)。
  • 能力:从外部资源仓库(或技能的 assets/)读取/复制文件;将文件写入项目;通过本地 HTTP 提供服务;控制无头浏览器进行截图并读取 console。
失败边界
  • 项目不允许引入外部 lib → partial 是有效结果:使用精简版 assets/,而不是原始版本。
  • 通过 file:// 打开 → texture/asset 出错 → 在通过 HTTP 运行之前视为 blocked。
  • console 出现 WebGL 错误,或页面背景与网格同时滚动 → 验证 failed;修复(passive:false + touch-action: none)后重新验证。
  • 实际需求是粒子汇聚/两张图片切换 → clarify,转用 threejs-particle-morph / blur。

HOW

主要工作流
步骤类型输入操作输出/退出失败/下一步
W01judgment需求确认匹配该技能(无限滚动网格,没有终点),并选择原版或 assets/已选择的来源不允许引入外部库 → B01
W02effect原版 pen打开运行链接查看效果 → 将完整的 .html 文件(以及 _assets/)复制到项目中,保留署名注释项目中的文件—
W03judgment已复制的文件只修改内容/图片/颜色已适配的文件—
W04effect文件通过 HTTP 在本地运行,不要使用 file://正在运行的页面资源出错 → 检查路径
W05deterministic页面使用 playwright-verify 验证:向 4 个方向大力拖动,并在开始/中途/结束时截图 + 检查 console截图 + 无错误的 console出错 → 修复后重复 W04

各步骤详情(W01–W05 的权威来源):

原版 — 优先使用(复制完整文件,不要重写)

作者的原始代码(公开的 CodePen = MIT)位于外部仓库 Rheinmir/uiux-asset — 可运行的图库:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。 流程:打开运行链接查看准确效果 → 将完整的 .html 文件(如果文件引用了 _assets/ 文件夹,也一并复制)复制到项目中 → 保留首行署名注释 → 只修改内容/图片/颜色。通过 HTTP 在本地运行,不要使用 file://。 如果需要完整的离线版本,运行 git clone --depth 1 https://github.com/Rheinmir/uiux-asset。

原版 Pen — 作者链接
Infinite Scrollable and Draggable WebGL Grid — ReGGae运行 · 代码
Infinite 3D Poster Scroll Wall — photodow运行 · 代码
Infinite Scrolling with Image Cards — jkantner运行 · 代码

此技能中的 assets/ 只是自行编写的精简 vanilla 版本 — 仅在项目不允许引入外部库时使用;默认使用上面的原版。

分支
ID类型条件行为跳过/失败汇合
B01conditional_required项目不允许引入外部库使用 assets/demo.html + demo.css + demo.js,适配参数,并将占位符替换为真实图片(见 Assets 部分)—W04
B02user_optional需要深度效果将 Ortho 改为 PerspectiveCamera,保留 wrap 公式,只更改 world-unit → pixel 映射—W05
B03conditional_required需要的不同图片数量多于显示的 tile 数量使用 texture atlas(更改 UV offset),而不是增大 poolSize—W05
验证与停止条件

完成条件:Playwright 已向 4 个方向拖动,并在开始/中途/结束时完成截图;网格没有 pop-in,页面背景不会随之滚动,console 没有错误(WebGL 错误通常不会明显显示 — 必须检查 console,不能只看截图)。

示例
  • 正向示例:“为作品集制作无限滚动的 poster wall” → 从 Rheinmir/uiux-asset 复制 infinite-3d-poster-scroll-wall.html,替换图片,通过 HTTP 在本地运行,使用 Playwright 向 4 个方向拖动 → tile 平滑跳转,console 无错误。
  • **边界/失败:**使用 assets/demo.js,但注册 wheel 时没有设置 { passive: false } → preventDefault() 被静默忽略,页面背景和网格一起滚动 → 验证失败;添加标志和 touch-action: none 后重新运行。
参考 — 资源

assets/demo.html + assets/demo.css + assets/demo.js — 阅读后进行适配(初始化 InfiniteGrid 时更改 gridSize/tileSize/gap/poolSize,将 _drawPlaceholder() 替换为真实图片,通过 THREE.TextureLoader 加载 URL,而不是使用 canvas data URL)。Demo 会自行绘制占位符(canvas 数字 + hash 颜色),因此无需图片/网络即可立即运行。

参考 — 机制
  • 网格固定,不会新增:初始化时一次性构建 gridSize × gridSize 个网格(PlaneGeometry 共用 1 个实例,每个网格使用单独的材质),运行时绝不添加或移除网格——这与天真地“拖到尚无网格的区域时创建新网格”的做法截然不同。
  • 虚拟偏移,不会让摄像机无限远移:拖动或滚动只会累加到 offset.x/offset.y(浮点数,理论上可以无限增长,实际上永远不会达到极限)。摄像机保持静止。
  • 使用 modulo 环绕:每个网格的局部坐标 localX/localY 固定不变(在原始 N×N 网格中的位置)。每帧的实际显示位置 = wrap(localX - offset.x),其中 wrap(raw) = ((raw + half) % full + full) % full - half(full = gridSize*pitch,half = full/2)——将无限坐标折叠到中心周围的显示范围内,这是此技术使用的标准取模公式。
  • 瓦片跳跃时复用纹理:仅当网格的“虚拟瓦片索引”发生变化时,才会更改其 material.map(根据 wrapped_position + offset 计算,并按 pitch 四舍五入)——也就是瓦片从视口一侧离开、并从另一侧重新出现时。纹理取自固定纹理池(通过 THREE.TextureLoader 一次性加载 poolSize 个纹理),跳跃时绝不创建新纹理。
  • 不依赖网络的占位图:纹理池由 <canvas> 绘制数字和颜色生成(使用 golden-angle hue spread,便于肉眼区分),通过 canvas.toDataURL() 获取数据 URL,然后仍然通过 THREE.TextureLoader.load(dataUrl) 加载——演示可离线运行,同时仍使用真实图片加载的 API 调用路径(替换为真实图片时只需更改 URL,无需更改代码流程)。
  • 惯性:指针拖动会记录 velocity(最后一次 pointermove 的移动量);松开后,每帧执行 offset += velocity; velocity *= damping (0.92),直到速度低于阈值——逐渐插值回 0,并非真正的物理模拟。
  • 清理:destroy() 移除 pointerdown/pointermove/pointerup/pointercancel/wheel/resize 监听器,释放每个材质、释放共用几何体(仅 1 次)、释放纹理池中的每个纹理(仅 1 次——不要按网格释放,因为多个网格会共用纹理)、释放渲染器,并从 DOM 中移除 canvas。

来源

评论

支持 Markdown