HTML5画布似乎不支持“扫描渐变”,即颜色停止围绕中心旋转而不是从中心发出的渐变。
有什么方法可以在画布上模拟扫描渐变?我想我可以用很多小的线性渐变来做类似的事情,但是那时候我基本上是在自己渲染渐变。
答案 0 :(得分:1)
实际上,没有内置这种东西。
不确定这些“ 很多小的线性渐变”是什么想法,但实际上您只需要一个,即圆的周长,就只能得到正确的颜色使用。
尽管您需要很多东西,但是线条,因为我们将使用linearGradient中的纯色围绕中心点绘制线条。
因此要渲染此图像,您只需移动到中心点,然后使用线性渐变中的纯色绘制一条线,然后旋转并重复。
要获取linearGradient的所有颜色,只需绘制它并将其ImageData映射为CSS颜色即可。
尽管困难的部分在于,要拥有一个行为像CanvasGradient的对象,我们需要将其设置为fillStyle或strokeStyle。
这可以通过返回CanvasPattern来实现。另一个困难是,梯度实际上无限大。非重复模式不是。
我没有找到解决此问题的好方法,但是作为一种解决方法,我们可以使用目标画布的大小作为限制。
这是一个粗略的实现:
<configuration debug="true" scan="true" scanPeriod="5 seconds">
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
...
</filter>
...
<subject>Testing %logger{20} - %m</subject>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%-5level %logger-%msg%n</pattern>
</layout>
</appender>
<appender name="EMAIL_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="EMAIL"/>
</appender>
<root level="ERROR">
<appender-ref ref="EMAIL_ASYNC" />
</root>
</configuration>
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}
但是要注意,所有这些在计算上都非常繁琐,因此请确保偶尔使用它并缓存生成的渐变/图案。