HTML的createLinearGradient不正常工作。

huangapple go评论73阅读模式
英文:

html createLinearGradient not working properly

问题

我试图创建一个沿曲线tangentially走势的渐变。由于默认情况下无法实现这一点,我已经将曲线分成一系列四边形,并对每个四边形应用了渐变。然而,似乎渐变方向不正确。我已经将四边形扩大,以便更容易看到这里。黑线代表每个形状的ctx.createLinearGradient()x0, y0x1, y1输入。

因此,我计算了点1, 2, 3, 4xy坐标(从形状的左上角顺时针方向开始),然后计算了从形状顶部中间到底部中间的渐变方向线。

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.lineTo(x4, y4);

const grd_x1 = (x1 + x2) / 2;
const grd_y1 = (y1 + y2) / 2;
const grd_x2 = (x3 + x4) / 2;
const grd_y2 = (y3 + y4) / 2;

const gradient = ctx.createLinearGradient(
    grd_x1,
    grd_y1,
    grd_x2,
    grd_y2
);
gradient.addColorStop(0, "#3acbfcc3");
gradient.addColorStop(1, "#ffffff00");
ctx.fillStyle = gradient;
ctx.fill();

因此,我期望渐变会沿着黑线的方向,就像这样。然而,在形状中,很明显渐变方向不是这样的,就像这样。我期望顶部的两个角有相同的颜色(蓝色),底部的两个角有相同的颜色(白色)。我是否误解了函数的输入?这篇文档顶部的图示让我觉得应该可以工作。

英文:

I am trying to create a curve with a gradient that follows it tangentially. Since its not possible to do that by default, i've taken to splitting the curve into a series of quadrilaterals and applying a gradient to each one. However, it seems that the gradient is not going in the correct direction. I've made the quadrilaterals larger to make it easier to see here. The black lines represent the x0, y0 and x1, y1 inputs to ctx.createLinearGradient() for each shape.

So i calculate the x and y for points 1, 2, 3, 4 (which go in clockwise direction around the shape starting from the top left), and then calculate the gradient direction line from the middle of the top of the shape to the middle of the bottom.

<!-- language: lang-js -->

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.lineTo(x4, y4);

const grd_x1 = (x1 + x2) / 2;
const grd_y1 = (y1 + y2) / 2;
const grd_x2 = (x3 + x4) / 2;
const grd_y2 = (y3 + y4) / 2;

const gradient = ctx.createLinearGradient(
    grd_x1,
    grd_y1,
    grd_x2,
    grd_y2
);
gradient.addColorStop(0, &quot;#3acbfcc3&quot;);
gradient.addColorStop(1, &quot;#ffffff00&quot;);
ctx.fillStyle = gradient;
ctx.fill();

<!-- end snippet -->

Hence i would expect the gradient would follow the direction of the black lines like this. However, in the shapes it is clear that the gradient is not going in that direction like this. I expect that the top two corner have the same colour (blue) and the bottom two corners have the same colour (white). Am i interpreting the inputs to the function wrong? The diagram at the top of this doc makes me think it should work.

答案1

得分: 1

以下是您要翻译的部分:

然而,乍一看可能会有些矛盾,但在您的情况下,渐变不应按您直觉认为的方式渲染方向

正如您已经链接到的文章中所示:

HTML的createLinearGradient不正常工作。

渐变是沿着指定的矢量平行渲染的。这意味着颜色沿着与该矢量垂直的任何线都是恒定的


然而,您的矢量与波的前段不正交,因此您得到的渐变是:

  • 在前段部分改变其颜色,而期望颜色保持恒定。
  • 与彼此不对齐,而它们应该在视觉上是连续的。

<img src="https://imgur.com/TAhiTbF.png" width="300" height="170">
<img src="https://imgur.com/KK1kg4x.png" width="300" height="170">


为了正确对齐渐变,您必须使它们与它们相应的前段段正交,并同时确保颜色沿着通用期望的渐变矢量均匀传播。

如何满足这两个要求,可以使用从前段段和附加到其上的期望渐变矢量创建的平行四边形来可视化(这是您已经构建四边形的方式)。现在,您只需通过绘制垂直线连接前段边的线相对边的线

这个垂直线就是您正在寻找的解决方案。

<img src="https://imgur.com/869G9a4.png" width="475" height="253">

起始点可以位于前段段的任何位置,因为根据定义(和设计),它将正交于渐变。结束点可以通过解线性代数方程来找到。


无论如何,我想练习一下,所以这里是演示!

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function renderWaveSegment ({ ctx, x1, y1, x2, y2, xGradient, yGradient, color1, color2 }) {

// Linear algebra to calc proper ending point of the gradient
let v1x = y2 - y1;
let v1y = x1 - x2;
let v3x = x2 - x1;
let v3y = y2 - y1;
let t = (v3x * yGradient - v3y * xGradient) / (v1y * v3x - v1x * v3y);
let xg = x1 + v1x * t;
let yg = y1 + v1y * t;

let gradient;
try {
// Just in case if there're NaNs or Infinities and createLinearGradient decides to throw,
// don't let it ruin the party!
gradient = ctx.createLinearGradient(x1, y1, xg, yg);
}
catch (e) { return; }
gradient.addColorStop(0, color1);
gradient.addColorStop(1, color2);

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x2 + xGradient, y2 + yGradient);
ctx.lineTo(x1 + xGradient, y1 + yGradient);
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
}

function renderWave ({ ctx, xStart, yStart, xEnd, yEnd, step, phase, period, amplitude, xGradient, yGradient, color1, color2 }) {

let length = Math.hypot(xEnd - xStart, yEnd - yStart);
let xUnit = (xEnd - xStart) / length;
let yUnit = (yEnd - yStart) / length;
let xNormal = -yUnit * amplitude;
let yNormal = xUnit * amplitude;
period /= Math.PI * 2;

function waveFrontAt (pos) {
let sin = Math.sin(phase + pos / period);
return [
xStart + pos * xUnit + sin * xNormal,
yStart + pos * yUnit + sin * yNormal
];
}

// Reduce aliasing artifacts between segments by slightly overlapping them
let overlap = 0.0525;

for (var pos = 0; pos < length - step; pos += step) {
let [x1, y1] = waveFrontAt(pos - overlap);
let [x2, y2] = waveFrontAt(pos + step + overlap);
renderWaveSegment({ ctx, x1, y1, x2, y2, xGradient, yGradient, color1, color2 });
}

let [x1, y1] = waveFrontAt(pos - overlap);
let [x2, y2] = waveFrontAt(length + overlap);
renderWaveSegment({ ctx, x1, y1, x2, y2, xGradient, yGradient, color1, color2 });
}

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

function render () {

let time = Date.now() / 1000;
ctx.clearRect(0, 0, canvas.width, canvas.height);
renderWave({
ctx,

// Starting point of the wave
xStart: -50,
yStart: canvas.height * (Math.sin(time * 1.77) * 0.3 + 0.4),

// Ending point of the wave
xEnd: canvas.width + 50,
yEnd: canvas.height * (Math.sin(time * 1.22) * 0.3 + 0.4),

// Width of segment in pixels (along the baseline)
step: 5,

// Sine function (phase in radians, period and amplitude in pixels)
phase: time * 10,
period: Math.sin(time * 0.77) * 30 + 70,
amplitude: 10,

// Desired gradient vector and colors
xGradient: Math.sin(time * 2.11) * 40,
yGradient: Math.sin

英文:

However paradoxical it may seem at first glance, but in your case gradients shouldn't be rendered the way how you intuitively think you want them to be oriented.

As can be seen from the article you already linked to:

HTML的createLinearGradient不正常工作。

Gradients are rendered parallel to the specified vector. Which means that the color is constant along any perpendicular to that vector.


Yet your vectors are not orthogonal to the frontal segments of the wave, so you end up with gradients which are:

  • Changing their colors along frontal segments, where color is expected to be constant.
  • Misaligned with each other, while they should be visually continuous.

<img src="https://imgur.com/TAhiTbF.png" width="300" height="170">
<img src="https://imgur.com/KK1kg4x.png" width="300" height="170">


In order to align gradients properly, you have to make them orthogonal to their corresponding frontal segments, and in the same time ensure that colors propagate uniformly along common desired gradient vectors.

How both requirements are met, can be visualized using a parallelogram created from the frontal segment and desired gradient vector attached to it (which is how you are already constructing your quadrilaterals). Now you only have to connect the line of the frontal side with the line of the opposite side, by drawing a perpendicular.

This perpendicular is the solution you are looking for.

<img src="https://imgur.com/869G9a4.png" width="475" height="253">

The starting point can lie anywhere on the frontal segment, since by definition (and by design) it will be orthogonal to the gradient. The ending point can be found by solving linear algebra equations.


I wanted to practice anyway, so here's the demo!

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function renderWaveSegment ({ ctx, x1, y1, x2, y2, xGradient, yGradient, color1, color2 }) {

    // Linear algebra to calc proper ending point of the gradient
    let v1x = y2 - y1;
    let v1y = x1 - x2;
    let v3x = x2 - x1;
    let v3y = y2 - y1;
    let t = (v3x * yGradient - v3y * xGradient) / (v1y * v3x - v1x * v3y);
    let xg = x1 + v1x * t;
    let yg = y1 + v1y * t;

    let gradient;
    try {
        // Just in case if there&#39;re NaNs or Infinities and createLinearGradient decides to throw,
        // don&#39;t let it ruin the party!
        gradient = ctx.createLinearGradient(x1, y1, xg, yg);
    }
    catch (e) { return; }
    gradient.addColorStop(0, color1);
    gradient.addColorStop(1, color2);

    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.lineTo(x2 + xGradient, y2 + yGradient);
    ctx.lineTo(x1 + xGradient, y1 + yGradient);
    ctx.closePath();
    ctx.fillStyle = gradient;
    ctx.fill();
}

function renderWave ({ ctx, xStart, yStart, xEnd, yEnd, step, phase, period, amplitude, xGradient, yGradient, color1, color2 }) {

    let length = Math.hypot(xEnd - xStart, yEnd - yStart);
    let xUnit = (xEnd - xStart) / length;
    let yUnit = (yEnd - yStart) / length;
    let xNormal = -yUnit * amplitude;
    let yNormal =  xUnit * amplitude;
    period /= Math.PI * 2;

    function waveFrontAt (pos) {
        let sin = Math.sin(phase + pos / period);
        return [
            xStart + pos * xUnit + sin * xNormal,
            yStart + pos * yUnit + sin * yNormal
        ];
    }

    // Reduce aliasing artifacts between segments by slightly overlapping them
    let overlap = 0.0525;

    for (var pos = 0; pos &lt; length - step; pos += step) {
        let [x1, y1] = waveFrontAt(pos - overlap);
        let [x2, y2] = waveFrontAt(pos + step + overlap);
        renderWaveSegment({ ctx, x1, y1, x2, y2, xGradient, yGradient, color1, color2 });
    }

    let [x1, y1] = waveFrontAt(pos - overlap);
    let [x2, y2] = waveFrontAt(length + overlap);
    renderWaveSegment({ ctx, x1, y1, x2, y2, xGradient, yGradient, color1, color2 });
}

let canvas = document.getElementById(&#39;canvas&#39;);
let ctx = canvas.getContext(&#39;2d&#39;);

function render () {

    let time = Date.now() / 1000;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    renderWave({
        ctx,

        // Starting point of the wave
        xStart: -50,
        yStart: canvas.height * (Math.sin(time * 1.77) * 0.3 + 0.4),

        // Ending point of the wave
        xEnd: canvas.width + 50,
        yEnd: canvas.height * (Math.sin(time * 1.22) * 0.3 + 0.4),

        // Width of segment in pixels (along the baseline)
        step: 5,

        // Sine function (phase in radians, period and amplitude in pixels)
        phase: time * 10,
        period: Math.sin(time * 0.77) * 30 + 70,
        amplitude: 10,

        // Desired gradient vector and colors
        xGradient: Math.sin(time * 2.11) * 40,
        yGradient: Math.sin(time * 3.33) * 35 + 85,
        color1: &#39;#3acbfcc3&#39;,
        color2: &#39;#ffffff00&#39;
    });
    window.requestAnimationFrame(render);
}

render();

<!-- language: lang-html -->

&lt;canvas id=&quot;canvas&quot; width=&quot;500&quot; height=&quot;175&quot;&gt;&lt;/canvas&gt;

<!-- end snippet -->

Also note that iteratively filling paths with CanvasRenderingContext2D always leaves gaps between adjacent shapes, which otherwise are expected to be rendered seamlessly. This is an unfortunate "feature" of antialiased rendering when performed in several steps on top of each other rather than drawing everything at once (but canvas is leaving us with no choice anyway). I tried to minimize these artifacts by slightly overlapping quadrilaterals, so I hope they are not so noticeable and annoying now.

Of course, there's WebGL at everyone's service, which allows to make quality and performant shaders, provided that one is ready to take the time to learn it.

答案2

得分: -3

这个不适用于渐变...

ctx.fill();

所以用这个替换...

ctx.fillRect(0, 0, C.width, C.height);

或者指定其他区域... 但必须是一个区域。

注意: 用你的画布标识符替换"C"。


这个测试是为了somethinghere...

JS:

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

let gradient = ctx.createLinearGradient(0, 0, 200, 0);

gradient.addColorStop(0, "green");
gradient.addColorStop(1, "pink");

ctx.fillStyle = gradient;

ctx.fill();  // &lt;-- 这是你的想法

//ctx.fillRect(10, 10, 200, 100); // &lt;-- 这是我的想法

HTML:

<canvas id="canvas"></canvas>

哪一个有效?

英文:

This doesn't work with gradients...

ctx.fill();

So replace it with this...

ctx.fillRect(0,0,C.width,C.height);

Or specify some other region... but it's gotta be a region.

NB: Change "C" with your canvas identifier.


This test is for somethinghere...

JS:

const canvas = document.getElementById(&quot;canvas&quot;);
const ctx = canvas.getContext(&quot;2d&quot;);

let gradient = ctx.createLinearGradient(0, 0, 200, 0);

gradient.addColorStop(0, &quot;green&quot;);
gradient.addColorStop(1, &quot;pink&quot;);

ctx.fillStyle = gradient;

ctx.fill();  // &lt;-- here&#39;s your idea

//ctx.fillRect(10, 10, 200, 100); // &lt;-- here&#39;s mine

HTML:

&lt;canvas id=&quot;canvas&quot;&gt;&lt;/canvas&gt;

Which one works?

huangapple
  • 本文由 发表于 2023年6月13日 03:36:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/76459798.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定