CSS 线性渐变和画布线性渐变与不透明度设置不同

M.E*_*vin 5 css opacity linear-gradients rgba html5-canvas

我想在画布上实现由 CSS 定义的相同线性渐变外观。使用了一种在不使用透明度设置之前效果很好的方法。当使用相同的线性渐变颜色设置定义了 rgba 颜色值时,结果看起来不一样,请参阅以下链接:

JSFiddle: 例子

var canvas = document.getElementById("myCanvas");
var ctx = document.getElementById("myCanvas").getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cssAng = Math.PI;
var dir = getDir(cssAng, w, h);
var gr = ctx.createLinearGradient(dir.x0,dir.y0,dir.x1,dir.y1);
gr.addColorStop(0, "rgb(255, 255, 255, 0)");
gr.addColorStop(0.87, "rgb(0, 0, 0, 1)");
ctx.fillStyle = gr;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);


function getDir(radian, width, height) {
        radian += Math.PI;
        const HALF_WIDTH = width * 0.5;
    const HALF_HEIGHT = height * 0.5;
    const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
    const HALF_LINE_LENGTH = lineLength / 2;

    const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
    const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
    const x1 = width - x0;
    const y1 = height - y0;

return {x0, x1, y0, y1};
}
Run Code Online (Sandbox Code Playgroud)
<!DOCTYPE html>
<html>
<body>
<div style='background-color:gray;display:inline-block;max-height:300px'>
  <div id="myDiv" style="display:inline-block;width:300px;height:300px;border:1px solid #d3d3d3;background:linear-gradient(180deg,rgba(255,255,255, 0) 0%, rgba(0,0,0,1) 87%"> </div>
</div>
<canvas id="myCanvas" width="300" height="300" style="background-color: gray;border:1px solid #d3d3d3;"> </canvas>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

知道为什么会这样吗?有没有可以处理这个问题的包?

Jul*_*ire 6

CSS linear-gradientcanvas linear gradient的规格实际上是有区别的。除了需要根据 alpha 值计算颜色的方式外,它们看起来几乎完全相同。对于 CSS 线性渐变,你有这个:

3.4.2. 给渐变线着色 在每个色标位置,渐变线就是色标的颜色。在第一个色标之前,渐变线是第一个色标的颜色,在最后一个色标之后,渐变线是最后一个色标的颜色。在两个色标之间,渐变线的颜色在两个色标的颜色之间进行插值,插值发生在预乘 RGBA 空间中

请参阅:https : //drafts.c​​sswg.org/css-images-3/#coloring-gradient-line

而画布一:

创建渐变后(见下文),沿其放置停靠点以定义颜色如何沿渐变分布。每个停靠点的渐变颜色是为该停靠点指定的颜色。在每个这样的停止点之间,颜色和 alpha 分量必须在 RGBA 空间上线性插值,而无需预先乘以 alpha 值来找到要在该偏移处使用的颜色。在第一站之前,颜色必须是第一站的颜色。

请参阅:https : //html.spec.whatwg.org/multipage/canvas.html#interpolation

所以 CSS 版本通过预乘它们的 alpha 值来计算色标。我已经改变了你的例子,让它更明显一点。在下面的示例中,CSS 版本从rgba(255, 0, 0, 0)rgba(0, 0, 0, 0)变为rgba(0, 0, 0, 1)。因此,在 50% 时,使用预乘 alpha 计算的颜色为rgba(0, 0, 0, 0.5)

在画布版本中,插值是在没有预乘的情况下计算的。所以在 50% 你有rgba(127,5, 0, 0, 0.5)。对于梯度线的每个点都是如此。

请参阅预乘意味着什么:https : //drafts.c​​sswg.org/css-images-3/#premultiplied

和例子:

var canvas = document.getElementById("myCanvas");
var ctx = document.getElementById("myCanvas").getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cssAng = Math.PI;
var dir = getDir(cssAng, w, h);
var gr = ctx.createLinearGradient(dir.x0,dir.y0,dir.x1,dir.y1);
gr.addColorStop(0, "rgb(255, 0, 0, 0)");
gr.addColorStop(1, "rgb(0, 0, 0, 1)");
ctx.fillStyle = gr;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);


function getDir(radian, width, height) {
        radian += Math.PI;
        const HALF_WIDTH = width * 0.5;
    const HALF_HEIGHT = height * 0.5;
    const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
    const HALF_LINE_LENGTH = lineLength / 2;

    const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
    const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
    const x1 = width - x0;
    const y1 = height - y0;

return {x0, x1, y0, y1};
}
Run Code Online (Sandbox Code Playgroud)
<!DOCTYPE html>
<html>
<body>
<div style='display:inline-block;max-height:300px'>
  <div id="myDiv" style="display:inline-block;width:200px;height:300px;border:1px ;background:linear-gradient(180deg,rgba(255,0,0, 0) 0%, rgba(0,0,0,1) 100%"> </div>
   
</div>
<canvas id="myCanvas" width="200" height="300" > </canvas>
<div style="position: absolute;width:8px;height:8px;background:rgba(0,0,0,0.5); top: 144px; left: 0px; ">

</div>
<div style="position: absolute;width:10px;height:10px;background:rgba(127.5,0,0,0.5); top: 144px; left: 412px; ">

</div>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

我认为没有办法使 2 等效,除非计算渐变线的每个点。


小智 -1

不能说这是否是解决您问题的正确方法,但这是一种解决方法。在中间添加一个图层并调整黑色、灰色和白色部分的比例。它们完全不同,我想添加更多的颜色停止点以更好地控制颜色的放置。

var canvas = document.getElementById("myCanvas");
var ctx = document.getElementById("myCanvas").getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cssAng = Math.PI;
var dir = getDir(cssAng, w, h);
var gr = ctx.createLinearGradient(dir.x0,dir.y0,dir.x1,dir.y1);
gr.addColorStop(0, "rgb(255,255,255)");
gr.addColorStop(0, "rgb(125,125,125)");
gr.addColorStop(.9, "rgb(0,0,0)" );
ctx.fillStyle = gr;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);


function getDir(radian, width, height) {
        radian += Math.PI;
        const HALF_WIDTH = width * 0.5;
    const HALF_HEIGHT = height * 0.5;
    const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
    const HALF_LINE_LENGTH = lineLength / 2;

    const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
    const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
    const x1 = width - x0;
    const y1 = height - y0;

return {x0, x1, y0, y1};
}
Run Code Online (Sandbox Code Playgroud)
<!DOCTYPE html>
<html>
<body>
<div style='background-color:gray;display:inline-block;max-height:300px'>
  <div id="myDiv" style="display:inline-block;width:300px;height:300px;border:1px solid #d3d3d3;background:linear-gradient(180deg,rgba(255,255,255, 0) 0%, rgba(0,0,0,1) 87%"> </div>
</div>
<canvas id="myCanvas" width="300" height="300" style="background-size: cover;background-color: gray;border:1px solid #d3d3d3;"> </canvas>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)