代码之家  ›  专栏  ›  技术社区  ›  Elliot Nelson

HTML5画布扫描渐变

  •  0
  • Elliot Nelson  · 技术社区  · 7 年前

    有没有办法在画布上模拟扫描渐变?我想我可以做一些类似的事情,用很多线性渐变,但在那一点上,我基本上是自己渲染渐变。

    Example of sweep gradient

    1 回复  |  直到 7 年前
        1
  •  1
  •   Kaiido    7 年前

    事实上,这样的事情是没有内在的。

    很多小的线性梯度 “,但实际上你只需要一个,圆周长的大小,只需要得到正确的颜色就可以了。

    你需要的是很多线,因为我们将使用我们在linearGradient中使用的纯色围绕中心点绘制这些线。

    要获取lineargradent的所有颜色,只需绘制它并将其ImageData映射到CSS颜色。

    不过,最困难的是,要让一个对象的行为像画布渐变,我们需要能够设置为fillStyle或strokeStyle。 这可以通过返回一个画布模式来实现。另一个困难是梯度实际上是无限大的。非重复模式不是。
    我没有找到一个很好的解决方案来解决这个问题,但是作为一个解决方法,我们可以使用目标画布的大小作为限制。

    下面是一个粗略的实现:

    class SweepGrad {
      constructor(ctx, x, y) {
        this.x = x;
        this.y = y;
        this.target = ctx;
        this.colorStops = [];
      }
      addColorStop(offset, color) {
        this.colorStops.push({offset, color});
      }
      render() {
        // get the current size of the target context
        const w = this.target.canvas.width;
        const h = this.target.canvas.width;
        const x = this.x;
        const y = this.y;
        // get the max length our lines can be
        const maxDist = Math.ceil(Math.max(
          Math.hypot(x, y),
          Math.hypot(x - w, y),
          Math.hypot(x - w, y - h),
          Math.hypot(x, y - h)
        ));
        // the circumference of our maxDist circle
        // this will determine the number of lines we will draw
        // (we double it to avoid some antialiasing artifacts at the edges)
        const circ = maxDist*Math.PI*2 *2;
      
        // create a copy of the target canvas
        const canvas = this.target.canvas.cloneNode();
        const ctx = canvas.getContext('2d');
    
        // generate the linear gradient used to get all our colors
        const linearGrad = ctx.createLinearGradient(0, 0, circ, 0);
        this.colorStops.forEach(stop => 
         linearGrad.addColorStop(stop.offset, stop.color)
        );
        const colors = getLinearGradientColors(linearGrad, circ);
        // draw our gradient
        ctx.setTransform(1,0,0,1,x,y);
    
        for(let i = 0; i<colors.length; i++) {
          ctx.beginPath();
          ctx.moveTo(0,0);
          ctx.lineTo(maxDist, 0);
          ctx.strokeStyle = colors[i];
          ctx.stroke();
          ctx.rotate((Math.PI*2)/colors.length);
        }
        // return a Pattern so we can use it as fillStyle or strokeStyle
        return ctx.createPattern(canvas, 'no-repeat');
      }
    
    }
    // returns an array of CSS colors from a linear gradient
    function getLinearGradientColors(grad, length) {
      const canvas = Object.assign(document.createElement('canvas'), {width: length, height: 10});
      const ctx = canvas.getContext('2d');
      ctx.fillStyle = grad;
      ctx.fillRect(0,0,length, 10);
      return ctx.getImageData(0,0,length,1).data
        .reduce((out, channel, i) => {
          const px_index = Math.floor(i/4);
          const px_slot = out[px_index] || (out[px_index] = []);
          px_slot.push(channel);
          if(px_slot.length === 4) {
             px_slot[3] /= 255;
             out[px_index] = `rgba(${px_slot.join()})`;
          }
          return out;
        }, []);
    }
    
    // How to use
    const ctx = canvas.getContext('2d');
    
    const redblue = new SweepGrad(ctx, 70, 70);
    redblue.addColorStop(0, 'red');
    redblue.addColorStop(1, 'blue');
    // remeber to call 'render()' to get the Pattern back
    // maybe a Proxy could handle that for us?
    ctx.fillStyle = redblue.render();
    ctx.beginPath();
    ctx.arc(70,70,50,Math.PI*2,0);
    ctx.fill();
    
    const yellowgreenred = new SweepGrad(ctx, 290, 80);
    yellowgreenred.addColorStop(0, 'yellow');
    yellowgreenred.addColorStop(0.5, 'green');
    yellowgreenred.addColorStop(1, 'red');
    ctx.fillStyle = yellowgreenred.render();
    ctx.fillRect(220,10,140,140);
    
    // just like with gradients, 
    // we need to translate the context so it follows our drawing
    ctx.setTransform(1,0,0,1,-220,-10);
    ctx.lineWidth = 10;
    ctx.strokeStyle = ctx.fillStyle;
    ctx.stroke(); // stroke the circle
    canvas{border:1px solid}
    <canvas id="canvas" width="380" height="160"></canvas>

    ,所有这些计算量都非常大,因此请确保偶尔使用它并缓存生成的渐变/模式。

    推荐文章