Процедурное заполнение формы более чем одним цветом

Я делаю приложение с node, js и т. Д. Я хотел бы заполнить пользовательские формы, которые я могу создать с помощью точек данных или другого формата, разными слоями цветов. Например, у меня есть треугольник. Я хочу заполнить нижнюю 1/3 красным, среднюю 1/3 синим, а верхнюю 1/3 зеленым. Как мне это сделать?

Я смотрю на Paper.js и базовый холст, но кажется, что они имеют только одноцветные заливки.

Спасибо за любой совет!


person xHocquet    schedule 13.03.2015    source источник


Ответы (2)


Я знаю, что ответ был принят, но я хотел представить очень простой подход для будущих читателей. Который, в качестве бонуса, автоматически вычисляет высоту каждой части и работает быстро, используя линейный градиент -

Результат будет

snap

Код и демонстрация

var ctx = document.querySelector("canvas").getContext("2d"),
    grad = ctx.createLinearGradient(0, 0, 0, 150);

grad.addColorStop(0, "red");     // start of red
grad.addColorStop(1/3, "red");   // end of red at 1/3

grad.addColorStop(1/3, "gold");  // start of gold at 1/3
grad.addColorStop(2/3, "gold");  // end of gold at 2/3

grad.addColorStop(2/3, "blue");  // start of blue at 2/3
grad.addColorStop(1, "blue");    // end of blue at 3/3

// Fill a triangle:
ctx.moveTo(75, 0); ctx.lineTo(150, 150); ctx.lineTo(0, 150);
ctx.fillStyle = grad;
ctx.fill();
<canvas/>

Анимированная версия с использованием техники композитинга

var ctx = document.querySelector("canvas").getContext("2d"),
    grad = ctx.createLinearGradient(0, 0, 0, 150),
    step = grad.addColorStop.bind(grad), // function reference to simplify
    dlt = -3, y = 150;

step(0, "red");     // start of red
step(1/3, "red");   // end of red at 1/3
step(1/3, "gold");  // start of gold at 1/3
step(2/3, "gold");  // end of gold at 2/3
step(2/3, "blue");  // start of blue at 2/3
step(1, "blue");    // end of blue at 3/3

// store a triangle path - we'll reuse this for the demo loop
ctx.moveTo(75, 0); ctx.lineTo(150, 150); ctx.lineTo(0, 150);

(function loop() {
  ctx.globalCompositeOperation = "copy";  // will clear canvas with next draw

  // Fill the previously defined triangle path with any color:
  ctx.fillStyle = "#000";  // fill some solid color for performance
  ctx.fill();
  
  // draw a rectangle to clip the top using the following comp mode:
  ctx.globalCompositeOperation = "destination-in";
  ctx.fillRect(0, y, 150, 150 - y);

  // now that we have the shape we want, just replace it with the gradient:
  // to do that we use a new comp. mode
  ctx.globalCompositeOperation = "source-in";
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, 150, 150);
  
  y += dlt; if (y <= 0 || y >= 150) dlt = -dlt;  
  requestAnimationFrame(loop);
})();
<canvas/>

Кешированное изображение градиента для анимации (рекомендуется)

var ctx = document.querySelector("canvas").getContext("2d"),
    tcanvas = document.createElement("canvas"),    // to cache triangle
    tctx = tcanvas.getContext("2d"),
    grad = tctx.createLinearGradient(0, 0, 0, 150),
    step = grad.addColorStop.bind(grad), // function reference to simplify
    dlt = -3, y = 150;

step(0, "red");     // start of red
step(1/3, "red");   // end of red at 1/3
step(1/3, "gold");  // start of gold at 1/3
step(2/3, "gold");  // end of gold at 2/3
step(2/3, "blue");  // start of blue at 2/3
step(1, "blue");    // end of blue at 3/3

// draw triangle to off-screen canvas once.
tctx.moveTo(75, 0); tctx.lineTo(150, 150); tctx.lineTo(0, 150);
tctx.fillStyle = grad; tctx.fill();

(function loop() {
  ctx.clearRect(0, 0, 150, 150);

  // draw clipped version of the cached triangle image
  if (150-y) ctx.drawImage(tcanvas, 0, y, 150, 150 - y, 0, y, 150, 150 - y);

  y += dlt; if (y <= 0 || y >= 150) dlt = -dlt;  
  requestAnimationFrame(loop);
})();
<canvas/>

Вы можете изменить направление с помощью линии градиента, которая определяет угол градиента.

// vertical 
ctx.createLinearGradient(0, 0, 0, 150); // x1, y1, x2, y2

// hortizontal
ctx.createLinearGradient(0, 0, 150, 0); // x1, y1, x2, y2

// 45° degrees
ctx.createLinearGradient(0, 0, 150, 150); // x1, y1, x2, y2

и т.п.

person Community    schedule 13.03.2015
comment
Этот подход намного лучше, короче и не опасен для призрачных остатков других нарисованных пикселей. - person dwana; 13.03.2015
comment
Мне это тоже нравится, я попробую оба. Сможете ли вы использовать для этого animate (), чтобы он заполнялся снизу? - person xHocquet; 13.03.2015
comment
@xHocquet конечно, используя пару шагов компоновки, вы можете обрезать форму треугольника, а затем заполнить ее градиентом. Я добавил демонстрацию для удобства :) (вы можете кэшировать треугольник с градиентом на отдельном холсте и просто нарисовать его обрезанную версию обратно на основной холст). - person ; 13.03.2015
comment
@xHocquet хорошо, я добавил демонстрационный код для подхода к кэшированию изображений, так как я бы рекомендовал это по соображениям производительности (я не устанавливаю размеры холста, поскольку по умолчанию это 300x150, идеально подходит для демонстрации, но в реальной жизни вам нужен код чтобы установить их, а также использовать несамозакрывающийся тег холста - мне просто лень писать эти n раз.)). - person ; 13.03.2015

Вы можете использовать собственный холст HTML, превратив ваши формы (например, треугольник) в области отсечения.

Это означает, что любые последующие заливки не будут выводиться за пределы вашего треугольника.

Все, что вам нужно сделать, это:

  • Нарисуй треугольник

  • Сделайте область отсечения

  • Нарисуйте зеленый прямоугольник поверх верхней трети треугольника. Не волнуйтесь ... прямоугольник будет обрезан, чтобы отображаться только там, где он находится внутри треугольника.

  • Нарисуйте синий прямоугольник поверх средней трети вашего треугольника.

  • Нарисуйте красный прямоугольник поверх нижней трети треугольника.

Вот пример кода и демонстрация:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

var points=[];
points.push({x:100,y:50});
points.push({x:150,y:150});
points.push({x:50,y:150});
points.push({x:100,y:50});

drawPoints(points);

function drawPoints(pts){
  var minY= 100000;
  var maxY=-100000;
  ctx.save();
  ctx.beginPath();
  for(var i=0;i<pts.length;i++){
    var p=pts[i];
    if(i==0){
      ctx.moveTo(p.x,p.y);
    }else{
      ctx.lineTo(p.x,p.y);
    }
    if(p.y<minY){minY=p.y;}
    if(p.y>maxY){maxY=p.y;}
  }
  ctx.stroke();
  ctx.clip();

  var height=maxY-minY;
  ctx.fillStyle='green';
  ctx.fillRect(0,minY,cw,minY,height/3);
  ctx.fillStyle='blue';
  ctx.fillRect(0,minY+height/3,cw,height/3);
  ctx.fillStyle='red';
  ctx.fillRect(0,minY+height*2/3,cw,height/3);
  ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

person markE    schedule 13.03.2015
comment
Эй! Этот ответ идеален. Большое спасибо, не знал о масках. Не могли бы вы подсказать мне, как вычислять проценты на основе массива или объекта чисел. Вычислить общую сумму, получить соотношения, выполнить итерацию по соотношениям и сгенерировать прямоугольник * с соотношением 100% высоты? - person xHocquet; 13.03.2015
comment
Пожалуйста! Вот как рассчитать высоту: (1) Учитывая var data=[3,4,6]; вычислить общее количество данных: var total=0; for(var i=0;i<data.length;i++){total+=data[i];} (2) Рассчитать var height (как в моем ответе), (3) Рассчитать высоту каждого прямоугольника в процентах каждой опорной точки на общую высоту: var heights=[]; for(var i=0;i<data.length;i++){heights[i]=data[i]/total*height;} Удачи в вашем проекте! - person markE; 13.03.2015