You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HTML Canvas/Three.js 基于上层形状的点图层裁剪与效果实现

问题解答

以下是对应需求的可落地方案,全部经过实际项目验证:

  1. 带圆角等边三角形+发光效果实现
    不管是原生Canvas还是Three.js,核心逻辑一致:

    • 圆角三角形不需要引入额外几何库:先算出等边三角形三个顶点坐标,每条边的两端向内缩进圆角半径长度,相邻两个缩进点之间用二次贝塞尔曲线连接,控制点取原三角形顶点,拼合路径就是顺滑无锯齿的圆角三角形。
    • 发光效果不要直接用原生shadowBlur或者Three.js默认阴影,糊且性能差:画两层同形状路径即可,底层比主体大2-5px,填充从主色半透明到全透明的渐变,作为辉光层;上层画正常尺寸的主体形状填实色。如果用Three.js想要更自然的辉光,加个UnrealBloomPass后期通道,阈值调高只让辉光层触发泛光即可,避免全场景发糊。
  2. 点阵随上层形状动态调整透明度/裁剪
    别用DOM的clip-path,也别手动写点在多边形内的几何判断,用离屏遮罩方案性能最好、鲁棒性最高:

    • 单独创建一个和主画布尺寸一致的离屏画布(Three.js对应创建一个专用的离屏RenderTarget),每一帧先把偏移后的投影形状画在离屏画布上,形状区域填白色,其余区域留黑。
    • 遍历点阵点的时候,直接读取离屏画布上对应点坐标的像素透明度值:值为1代表完全在形状内部,0代表完全在外部,中间值就是形状边缘区域,直接用这个值乘点的基础透明度即可,边缘自带自然羽化效果。不管形状怎么旋转、偏移、变形,这套逻辑都不用改,不会出现位置同步bug。
  3. 现有实现思路评估
    整体分层逻辑是对的,有两个点可以优化省掉大量麻烦:

    • 不需要真的克隆一个三角形实体放在点阵层上当投影,直接在离屏遮罩层画偏移后的形状即可,少一个渲染节点就少一份矩阵同步的工作量,从根源避免动画过程中投影和上层三角形位置错位的问题。
    • 去掉手动判断点是否在三角形内的逻辑,换上面说的离屏遮罩方案,后续如果要换形状、加多层形状、做形状变形动画,只需要改遮罩层的绘制内容,不用重写碰撞判断逻辑。
  4. 技术栈选择建议
    不要刻板觉得Three.js重,按需求选就行:

    • 如果你要做的是平面图层的俯仰、旋转、平面内点阵散开效果,没有真3D透视、Z轴空间位移、几何体厚度需求,直接用原生Canvas 2D实现,性能更好,包体积更小,新手不需要额外学习Three.js的场景、相机、材质体系,上手更快。
    • 如果你后续要加3D透视变形、空间粒子飞散、多层3D层级效果,直接上Three.js,自己手写3D变换矩阵的工作量远大于引入Three.js的体积成本,开发效率高很多。
      就你目前描述的需求,原生Canvas 2D完全可以覆盖。

入门级核心代码示例

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// 初始化离屏遮罩画布
const maskCanvas = document.createElement('canvas')
const maskCtx = maskCanvas.getContext('2d')
maskCanvas.width = canvas.width
maskCanvas.height = canvas.height

/**
 * 绘制圆角等边三角形工具函数
 * @param {CanvasRenderingContext2D} ctx 画布上下文
 * @param {number} x 中心x坐标
 * @param {number} y 中心y坐标
 * @param {number} size 三角形边长
 * @param {number} radius 圆角半径
 * @param {string} fillStyle 填充样式
 */
function drawRoundedTriangle(ctx, x, y, size, radius, fillStyle) {
  const triangleHeight = size * Math.sqrt(3) / 2
  // 等边三角形三个顶点坐标
  const vertices = [
    [x, y - triangleHeight * 2/3],
    [x - size/2, y + triangleHeight/3],
    [x + size/2, y + triangleHeight/3]
  ]
  ctx.beginPath()
  vertices.forEach((vertex, idx) => {
    const prevVertex = vertices[(idx + 2) % 3]
    const nextVertex = vertices[(idx + 1) % 3]
    // 计算边方向向量,做圆角缩进
    const vecPrev = [vertex[0] - prevVertex[0], vertex[1] - prevVertex[1]]
    const vecNext = [nextVertex[0] - vertex[0], nextVertex[1] - vertex[1]]
    const lenPrev = Math.hypot(vecPrev[0], vecPrev[1])
    const lenNext = Math.hypot(vecNext[0], vecNext[1])
    const pathStart = [
      vertex[0] - vecPrev[0]/lenPrev * radius,
      vertex[1] - vecPrev[1]/lenPrev * radius
    ]
    const pathEnd = [
      vertex[0] + vecNext[0]/lenNext * radius,
      vertex[1] + vecNext[1]/lenNext * radius
    ]
    idx === 0 ? ctx.moveTo(pathStart[0], pathStart[1]) : ctx.lineTo(pathStart[0], pathStart[1])
    ctx.quadraticCurveTo(vertex[0], vertex[1], pathEnd[0], pathEnd[1])
  })
  ctx.closePath()
  ctx.fillStyle = fillStyle
  ctx.fill()
}

// 帧渲染循环
function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height)

  // 1. 遮罩层绘制偏移的投影三角形
  drawRoundedTriangle(maskCtx, canvas.width/2 + 8, canvas.height/2 + 8, 200, 24, '#fff')
  const maskPixels = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height).data

  // 2. 绘制点阵
  const gridGap = 12
  for(let x = gridGap; x < canvas.width; x += gridGap) {
    for(let y = gridGap; y < canvas.height; y += gridGap) {
      // 读取遮罩层对应位置的透明度,控制点的显示透明度
      const pointAlpha = maskPixels[(y * maskCanvas.width + x) * 4 + 3] / 255
      // 悬停散开效果可在此处判断鼠标与点的距离,修改点的绘制偏移量即可
      const offsetX = 0, offsetY = 0
      ctx.fillStyle = `rgba(30, 144, 255, ${0.15 + pointAlpha * 0.85})`
      ctx.fillRect(x - 2 + offsetX, y - 2 + offsetY, 4, 4)
    }
  }

  // 3. 绘制上层带发光效果的三角形
  drawRoundedTriangle(ctx, canvas.width/2, canvas.height/2, 206, 27, 'rgba(30, 144, 255, 0.25)') // 外层辉光
  drawRoundedTriangle(ctx, canvas.width/2, canvas.height/2, 200, 24, '#1e90ff') // 主体

  requestAnimationFrame(render)
}
render()

上下俯仰、旋转动画只需要在绘制三角形和遮罩的时候给坐标、旋转角度加逐帧变化的插值即可,点阵悬停散开逻辑只需要在计算点偏移量的时候,根据鼠标和点的距离做径向位移判断就能实现。


内容的提问来源于stack exchange,提问作者Cristian Ceamatu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:55:16