沿HTML5画布路径的连续渐变

时间:2015-06-03 21:24:31

标签: javascript html5 canvas

我正在尝试使用HTML5画布API在点的路径上绘制连续渐变,其中每个点都有自己的颜色。

请参阅http://bl.ocks.org/rveciana/10743959获取灵感,使用D3实现该效果。

似乎没有办法为单个画布路径添加多个线性渐变,所以我采用了这样的方法:http://jsfiddle.net/51toapv2/

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var pts = [[100, 100, "red"], [150, 150, "green"], [200, 100, "yellow"]];

ctx.lineWidth = 20;
ctx.lineJoin = "round";
ctx.lineCap = "round";

for (var i = 0; i < pts.length - 1; i++) {
    var begin = pts[i];
    var end = pts[i + 1];

    ctx.beginPath();
    var grad = ctx.createLinearGradient(begin[0], begin[1], end[0], end[1]);
    grad.addColorStop(0, begin[2]);
    grad.addColorStop(1, end[2]);
    ctx.strokeStyle = grad;
    ctx.moveTo(begin[0], begin[1]);
    ctx.lineTo(end[0], end[1]);
    ctx.stroke();
}

正如您所看到的那样,由于路径未合并且“线路连接”清晰可见,因此会产生一种低效效果。

是否可以使用canvas API实现我正在寻找的效果?

2 个答案:

答案 0 :(得分:6)

以下是对原始想法的略微修改,使联接很好地融合。

enter image description here

原文:从线段的开头到结尾绘制一条渐变线。

这会导致线连接重叠并产生明显的&amp;不希望的过渡。

enter image description here

修改:绘制一条未延伸到起点/终点的渐变线。

通过此修改,线连接将始终为纯色而不是部分渐变。因此,线连接将在线段之间很好地转换。

enter image description here

以下是示例代码和演示:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

var lines = [
  {x:100, y:050,color:'red'},
  {x:150, y:100,color:'green'},
  {x:200, y:050,color:'gold'},
  {x:275, y:150,color:'blue'}
];
var linewidth=20;

ctx.lineCap='round';
ctx.lineJoint='round';

for(var i=1;i<lines.length;i++){

  // calculate the smaller part of the line segment over
  //     which the gradient will run
  var p0=lines[i-1];
  var p1=lines[i];
  var dx=p1.x-p0.x;
  var dy=p1.y-p0.y;
  var angle=Math.atan2(dy,dx);
  var p0x=p0.x+linewidth*Math.cos(angle);
  var p0y=p0.y+linewidth*Math.sin(angle);
  var p1x=p1.x+linewidth*Math.cos(angle+Math.PI);
  var p1y=p1.y+linewidth*Math.sin(angle+Math.PI);

  // determine where the gradient starts and ends
  if(i==1){
    var g=ctx.createLinearGradient(p0.x,p0.y,p1x,p1y);   
  }else if(i==lines.length-1){
    var g=ctx.createLinearGradient(p0x,p0y,p1.x,p1.y);
  }else{
    var g=ctx.createLinearGradient(p0x,p0y,p1x,p1y);
  }

  // add the gradient color stops
  // and draw the gradient line from p0 to p1
  g.addColorStop(0,p0.color);
  g.addColorStop(1,p1.color);
  ctx.beginPath();
  ctx.moveTo(p0.x,p0.y);
  ctx.lineTo(p1.x,p1.y);
  ctx.strokeStyle=g;
  ctx.lineWidth=linewidth;
  ctx.stroke();
}
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=350 height=200></canvas>

答案 1 :(得分:4)

你可以做一个简单的方法,沿着一条线插入两种颜色。如果你需要平滑/共享渐变,其中两条线以更陡的角度连接,你需要计算并基本上从(几乎)划痕实现线条绘制算法。这将超出SO的范围,因此这是一种更简单的方法。

话虽如此 - 链接中的示例实际上并不是一条线,而是几块不同颜色的正方形。它本来会遇到的问题是&#34;隐藏&#34;通过其微妙的变化。

实施例

snapshot

这种方法需要两个主要功能:

  1. 行插值函数,用于绘制从前一个鼠标位置到当前位置的一行中的每个段

  2. 颜色插值功能,它根据长度,位置和线段大小,采用一系列颜色并在两种当前颜色之间进行插值。

  3. 调整参数,例如段大小,数组中的颜色数等,以获得最佳结果。

    线插值函数

    function plotLine(ctx, x1, y1, x2, y2) {
    
      var diffX = Math.abs(x2 - x1),      // get line length
          diffY = Math.abs(y2 - y1),
          dist = Math.sqrt(diffX * diffX + diffY * diffY),
          step = dist / 10,               // define some resolution
          i = 0, t, b, x, y;
    
      while (i <= dist) {                 // render circles along the line
        t = Math.min(1, i / dist);
    
        x = x1 + (x2 - x1) * t;
        y = y1 + (y2 - y1) * t;
    
        ctx.fillStyle = getColor();       // get current color
        ctx.beginPath();
        ctx.arc(x, y, 10, 0, Math.PI*2);
        ctx.fill();
        i += step;
      }
    

    颜色插值函数

      function getColor() {
    
        var r, g, b, t, c1, c2;
    
        c1 = colors[cIndex];                           // get current color from array
        c2 = colors[(cIndex + 1) % maxColors];         // get next color
        t = Math.min(1, total / segment);              // calculate t
    
        if (++total > segment) {                       // rotate segment
          total = 0;
          if (++cIndex >= maxColors) cIndex = 0;       // rotate color array
        }
    
        r = c1.r + (c2.r - c1.r) * t;                  // interpolate color
        g = c1.g + (c2.g - c1.g) * t;
        b = c1.b + (c2.b - c1.b) * t;
    
        return "rgb(" + (r|0) + "," + (g|0) + "," + (b|0) + ")";
      }
    

    演示

    将它们放在一起将允许您绘制渐变线。如果您不想手动绘制它们,只需在需要时调用plotLine()函数。

    &#13;
    &#13;
    // Some setup code
    var c = document.querySelector("canvas"),
        ctx = c.getContext("2d"),
        colors = [
          {r: 255, g: 0, b: 0},
          {r: 255, g: 255, b: 0},
          {r: 0, g: 255, b: 0},
          {r: 0, g: 255, b: 255},
          {r: 0, g: 0, b: 255},
          {r: 255, g: 0, b: 255},
          {r: 0, g: 255, b: 255},
          {r: 0, g: 255, b: 0},
          {r: 255, g: 255, b: 0},
        ],
        cIndex = 0, maxColors = colors.length,
        total = 0, segment = 500,
        isDown = false, px, py;
    
    setSize();
          
    c.onmousedown = c.ontouchstart = function(e) {
      isDown = true;
      var pos = getPos(e);
      px = pos.x;
      py = pos.y;
    };
    
    window.onmousemove = window.ontouchmove = function(e) {if (isDown) plot(e)};
    window.onmouseup = window.ontouchend = function(e) {
      e.preventDefault();
      isDown = false
    };
    
    function getPos(e) {
      e.preventDefault();
      if (e.touches) e = e.touches[0];
      var r = c.getBoundingClientRect();
      return {
        x: e.clientX - r.left,
        y: e.clientY - r.top
      }
    }
    
    function plot(e) {
      var pos = getPos(e);
      plotLine(ctx, px, py, pos.x, pos.y);
      px = pos.x;
      py = pos.y;
    }
    
    function plotLine(ctx, x1, y1, x2, y2) {
    
      var diffX = Math.abs(x2 - x1),
          diffY = Math.abs(y2 - y1),
          dist = Math.sqrt(diffX * diffX + diffY * diffY),
          step = dist / 50,
          i = 0,
          t, b, x, y;
      
      while (i <= dist) {
        t = Math.min(1, i / dist);
    
        x = x1 + (x2 - x1) * t;
        y = y1 + (y2 - y1) * t;
    
        ctx.fillStyle = getColor();
        ctx.beginPath();
        ctx.arc(x, y, 10, 0, Math.PI*2);
        ctx.fill();
        i += step;
      }
      
      function getColor() {
      
        var r, g, b, t, c1, c2;
        
        c1 = colors[cIndex];
        c2 = colors[(cIndex + 1) % maxColors];
        t = Math.min(1, total / segment);
        
        if (++total > segment) {
          total = 0;
          if (++cIndex >= maxColors) cIndex = 0;
        }
      
        r = c1.r + (c2.r - c1.r) * t;
        g = c1.g + (c2.g - c1.g) * t;
        b = c1.b + (c2.b - c1.b) * t;
      
        return "rgb(" + (r|0) + "," + (g|0) + "," + (b|0) + ")";
      }
    }
    
    window.onresize = setSize;
    function setSize() {
      c.width = window.innerWidth;
      c.height = window.innerHeight;
    }
    document.querySelector("button").onclick = function() {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    };
    &#13;
    html, body {background:#777; margin:0; overflow:hidden}
    canvas {position:fixed;left:0;top:0;background: #333}
    button {position:fixed;left:10px;top:10px}
    &#13;
    <canvas></canvas>
    <button>Clear</button>
    &#13;
    &#13;
    &#13;

    TIPS:

    • 可以事先预先填充/缓存渐变值
    • 渐变中位置的步长可以绑定到长度,以获得与绘制速度无关的均匀分布
    • 您可以轻松地将画笔替换为其他路径/图形/形状,甚至可以组合使用当前颜色合成的基于图像的画笔