沿HTML5画布路径的连续渐变

Sii*_*las 3 javascript html5 canvas

我试图使用HTML5 canvas 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();
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它会产生一个低效的效果,因为路径未合并,并且"线连接"清晰可见.

是否有可能通过canvas API实现我正在寻找的效果?

mar*_*rkE 6

这是对您原始想法的略微修改,使得连接很好地融合.

在此输入图像描述

原始:从线段的开始到结束绘制渐变线.

这会导致线连接重叠并产生明显且不希望的转换.

在此输入图像描述

修改:绘制不延伸到起点/终点的渐变线.

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

在此输入图像描述

这是示例代码和演示:

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();
}
Run Code Online (Sandbox Code Playgroud)
#canvas{border:1px solid red; margin:0 auto; }
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas" width=350 height=200></canvas>
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以使用一种简单的方法沿一条线插入两种颜色。如果您需要平滑/共享渐变,其中两条线以更陡峭的角度连接,您将需要计算并基本上从(几乎)从头开始实现线绘制算法。这超出了 SO 的范围,所以这里有一个更简单的方法。

话虽如此 - 链接中的示例实际上不是一条线,而是几张不同颜色的正方形图。它也会遇到的问题也被其微妙的变化“隐藏”了。

例子

快照

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

  1. 线插值函数,将每个线段从鼠标前一个位置绘制到当前位置

  2. 颜色插值函数采用颜色数组并根据长度、位置和段大小在两种当前颜色之间进行插值。

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

线插功能

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;
  }
Run Code Online (Sandbox Code Playgroud)

颜色插值功能

  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) + ")";
  }
Run Code Online (Sandbox Code Playgroud)

演示

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

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;
  }
Run Code Online (Sandbox Code Playgroud)
  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) + ")";
  }
Run Code Online (Sandbox Code Playgroud)
// 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)
};
Run Code Online (Sandbox Code Playgroud)

提示:

  • 梯度值可以预先填充/缓存
  • 梯度位置的步长可以绑定到长度,以不受绘制速度的影响而均匀分布
  • 您可以轻松地用其他路径/图形/形状替换画笔,甚至组合与当前颜色合成的基于图像的画笔

  • @markE "很好。当程序控制下调用 plotLine 时,您可能需要调整 segment=500 仅几个点。例如: plotLine(ctx,100,100,150,150); 和 plotLine(ctx,150,150,200,100); 将绘制一条全红线由于涉及的几点。;-)“好吧,既然 98-99% 的代码都已经完成,我将把它作为 OP 的练习;-P (2认同)