javascript - 是否有 CANVAS 阴影属性,例如 CSS box-shadow 的 "spread"属性?

标签 javascript html css canvas

对于 CSS box-shadow,“spread”属性用于设置阴影的大小。但CANVAS的shadow API似乎缺乏对应的属性。

我知道答案可能是否定的。

但是有没有办法在CANVAS上实现阴影“扩散”的效果呢?

如果答案仍然是否定的,那么在下一个即将推出的 HTML 标准中它会是"is"吗?

最佳答案

But is there some way to achieve the shadow "spread" effect on CANVAS?

是的:)

如何?

模仿扩散半径的一种方法是创建一个更大版本的盒子,我们将其放置在 Canvas 外部,并使用偏移量 X 和偏移量 Y 使其阴影位于 Canvas 内我们想要的地方。之后就只剩下绘制原来的盒子了。


说明:

要创建一个更大的盒子,我们将使用 scale() ,我们需要首先计算比例因子。 enter image description here

我们通过计算所需阴影的大小与框的大小之间的比率来获得比例因子(SF)SF = (boxW + 2 * spreadRadius)/boxW

然后我们将使用 save() 保存 Canvas 的整个状态。方法之前调用scale(),因此我们将能够在操作后恢复到原始状态(使用 restore() 方法)

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;

const x = 50;
let y = 20;
const spreadRadius = 10;
const boxW = 50;
const boxH = 50;
const color = 'rgba(255, 0, 0, 0.4)';
const blurRadius = 10;
const offsetX = 0;
const offsetY = 0;

ctx.fillRect(x, y, boxW, boxH);
ctx.font = "16px Arial";
ctx.fillText('Origin Box', x + 100, y + 30);

ctx.save();
y += 85;
let scaleFactor = ((2 * spreadRadius) + boxW) / boxW;
ctx.scale(scaleFactor, scaleFactor);
ctx.fillRect((x - spreadRadius) / scaleFactor, (y - spreadRadius ) / scaleFactor, boxW, boxH);
ctx.restore();
ctx.fillText('Scaled Box', x + 100, y + 30);

ctx.save();
y += 100;
scaleFactor = ((2 * spreadRadius) + boxW) / boxW;
ctx.scale(scaleFactor, scaleFactor);
ctx.shadowColor = color;
ctx.shadowBlur = blurRadius;
ctx.shadowOffsetX = offsetX;
ctx.shadowOffsetY = offsetY;
ctx.fillRect((x - spreadRadius) / scaleFactor, (y - spreadRadius) / scaleFactor, boxW, boxH);
ctx.restore();
ctx.fillText('Scaled Box with shadow', x + 100, y + 30);

ctx.save();
y += 100;
scaleFactor = ((2 * spreadRadius) + boxW) / boxW;
ctx.scale(scaleFactor, scaleFactor);
ctx.shadowColor = color;
ctx.shadowBlur = blurRadius;
ctx.shadowOffsetX = offsetX + w;
ctx.shadowOffsetY = offsetY + h;
ctx.fillRect((x - spreadRadius - w) / scaleFactor, (y - spreadRadius - h) / scaleFactor, boxW, boxH);
ctx.restore();
ctx.fillText('Same but we draw the box outside the canvas', x + 100, y + 30);
ctx.fillText('and keep its shadow by playing on the offsets', x + 100, y + 47);

ctx.save();
y += 100;
scaleFactor = ((2 * spreadRadius) + boxW) / boxW;
ctx.scale(scaleFactor, scaleFactor);
ctx.shadowColor = color;
ctx.shadowBlur = blurRadius;
ctx.shadowOffsetX = offsetX + w;
ctx.shadowOffsetY = offsetY + h;
ctx.fillRect((x - spreadRadius - w) / scaleFactor, (y - spreadRadius - h) / scaleFactor, boxW, boxH);
ctx.restore();
ctx.clearRect(x, y, boxW, boxH);
ctx.fillRect(x, y, boxW, boxH);
ctx.fillText('Origin Box + shadow with the spread radius', x + 100, y + 30);
#canvas{
  border: 1px solid black;
}
<canvas id="canvas" width="500" height="500"></canvas>

诀窍是在 Canvas 外绘制缩放框。我们通过将 Canvas 的宽度和高度减去缩放框的 x 和 y 位置来实现此目的。通过这样做,我们确定它是在 Canvas 之外绘制的:

ctx.fillRect(x - w, y - h, boxW, boxH); // We substract w to the x position and h to the y position

然后,我们将其添加到阴影偏移量以在 Canvas 内绘制阴影:

ctx.shadowOffsetX = offsetX + w; // We add w which is the canvas width
ctx.shadowOffsetY = offsetY + h; // We add h which is the canvas height

只需绘制原始框即可得到我们想要的结果。


函数

我创建了一个 boxShadow() 函数来收集具体示例中的所有方法。该函数的工作原理如下:

  1. 创建一个shadowValues对象:

    const newShadowObject = {box: {posX: x, posY: y, width: boxW, height: boxH}, offsetX, offsetY, blurRadius, spreadRadius, color}
    
  2. 以对象作为参数调用 boxShadow() 函数:

    boxShadow(newShadowObject);
    

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;

function boxShadow(shadowValues){

  const {box: {posX: x, posY: y, width: boxW, height: boxH}, offsetX, offsetY, blurRadius, spreadRadius, color} = shadowValues;
  
    // We draw the shadow
    ctx.save();
    const scaleFactor = ((2 * spreadRadius) + boxW) / boxW;
    ctx.scale(scaleFactor, scaleFactor);
    ctx.shadowColor = color;
    ctx.shadowBlur = blurRadius;
    ctx.shadowOffsetX = offsetX + w;
    ctx.shadowOffsetY = offsetY + h;  
    ctx.fillRect((x - spreadRadius - w) / scaleFactor, (y - spreadRadius - h) / scaleFactor, boxW, boxH);
    ctx.restore();
    // We draw the original box
    ctx.clearRect(x, y, boxW, boxH);
    ctx.rect(x, y, boxW, boxH);
    ctx.stroke();
    
}

const case1 = {box: {posX: 30, posY: 30, width: 50, height: 50}, offsetX: 0, offsetY: 0, blurRadius: 0, spreadRadius: 0, color: '#ff0000'};
boxShadow(case1);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 0", 30, 120);
ctx.fillText("offsetY: 0", 30, 140);
ctx.fillText("blurRadius: 0", 30, 160);
ctx.fillText("spreadRadius: 0", 30, 180);

const case2 = {box: {posX: 170, posY: 30, width: 50, height: 50}, offsetX: 0, offsetY: 0, blurRadius: 0, spreadRadius: 10, color: '#ff0000'};
boxShadow(case2);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 0", 170, 120);
ctx.fillText("offsetY: 0", 170, 140);
ctx.fillText("blurRadius: 0", 170, 160);
ctx.fillText("spreadRadius: 10", 170, 180);

const case3 = {box: {posX: 320, posY: 30, width: 50, height: 50}, offsetX: 5, offsetY: 5, blurRadius: 0, spreadRadius: 10, color: '#ff0000'};
boxShadow(case3);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 5", 320, 120);
ctx.fillText("offsetY: 5", 320, 140);
ctx.fillText("blurRadius: 0", 320, 160);
ctx.fillText("spreadRadius: 10", 320, 180);

const case4 = {box: {posX: 470, posY: 30, width: 50, height: 50}, offsetX: 5, offsetY: 5, blurRadius: 20, spreadRadius: 10, color: '#ff0000'};
boxShadow(case4);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 5", 470, 120);
ctx.fillText("offsetY: 5", 470, 140);
ctx.fillText("blurRadius: 20", 470, 160);
ctx.fillText("spreadRadius: 10", 470, 180);

const case5 = {box: {posX: 55, posY: 225, width: 50, height: 100}, offsetX: 0, offsetY: 0, blurRadius: 20, spreadRadius: 0, color: '#ff0000'};
boxShadow(case5);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 0", 30, 380);
ctx.fillText("offsetY: 0", 30, 400);
ctx.fillText("blurRadius: 20", 30, 420);
ctx.fillText("spreadRadius: 0", 30, 440);

const case6 = {box: {posX: 200, posY: 250, width: 100, height: 50}, offsetX: 10, offsetY: 5, blurRadius: 0, spreadRadius: 0, color: '#ff0000'};
boxShadow(case6);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 10", 200, 380);
ctx.fillText("offsetY: 5", 200, 400);
ctx.fillText("blurRadius: 0", 200, 420);
ctx.fillText("spreadRadius: 0", 200, 440);

const case7 = {box: {posX: 400, posY: 210, width: 50, height: 50}, offsetX: 70, offsetY: 70, blurRadius: 10, spreadRadius: 10, color: '#ff0000'};
boxShadow(case7);
ctx.font = "16px Arial";
ctx.fillText("offsetX: 70", 400, 380);
ctx.fillText("offsetY: 70", 400, 400);
ctx.fillText("blurRadius: 0", 400, 420);
ctx.fillText("spreadRadius: 10", 400, 440);
#canvas{
  border: 1px solid black;
  background-color: #EFEFFA;
}
<canvas id="canvas" width="620" height="500"></canvas>

关于javascript - 是否有 CANVAS 阴影属性,例如 CSS box-shadow 的 "spread"属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60656468/

相关文章:

javascript - 如何在弹出窗口中添加链接

javascript - jquery清空父div不删除事件为什么

javascript - 使用 &lt;iframe&gt; 和 <embed> 显示 SVG 和脚本的区别

jquery - 为什么我的 JQuery Masonry 网格与我的标题重叠?

Safari 上的 HTML5 视频问题

javascript - Android 不安全 :tel: with phonegap application

jquery - 单击链接时显示 div 类

javascript - 从嵌套对象中删除父键

JQuery 动态更改 CSS 在 IE7 中不起作用

css - 为什么我的媒体查询不适用于 Flexbox?