【问题标题】:HTML canvas: Why does a large shadow blur not show up for small objects?HTML 画布:为什么小物体不会出现大阴影模糊?
【发布时间】:2016-12-29 01:03:20
【问题描述】:

这是一个演示:

var ctx = document.getElementById("test").getContext("2d");

ctx.shadowColor = "black";
ctx.fillStyle = "white";

ctx.shadowBlur = 10;
ctx.fillRect(10, 10, 10, 10);

ctx.shadowBlur = 50;
ctx.fillRect(70, 10, 10, 10);
ctx.fillRect(70, 70, 70, 70);
<canvas id="test" width="200" height="200"></canvas>

如果我设置shadowBlur=10,然后绘制一个 10x10 的小正方形,我会得到一个漂亮、强烈的阴影。如果我设置 shadowBlur=50 并绘制一个 70x70 的大正方形,则相同。但是,如果我设置shadowBlur=50,然后绘制一个 10x10 的小方块,我会得到一个非常微弱、几乎看不到的阴影。

相反,我会期待一个小的中心正方形和一个大的黑色阴影。

显然我误解了阴影模糊的工作原理,所以 - 它是如何工作的,以及如何在一个小物体周围获得一个大的暗阴影?

【问题讨论】:

    标签: html canvas html5-canvas shadow blur


    【解决方案1】:

    shadowBlur 使用高斯模糊在内部产生阴影。对象被绘制到一个单独的位图作为阴影颜色的模板,然后使用半径进行模糊。此步骤后它不使用原始形状。结果被合成回来(作为旁注:以前在如何合成阴影方面存在分歧,因此 Firefox 和 Chrome/Opera 以不同的方式呈现它们 - 我认为他们现在已经在两个阵营中登陆 source-over)。

    如果对象非常小而模糊半径非常大,则平均会被对象周围的剩余空间变薄,留下更微弱的阴影。

    使用内置方法获得更明显阴影的唯一方法是使用更小的半径。您还可以使用径向渐变“作弊”,或绘制更大的对象,并将阴影应用于屏幕外画布但相对于阴影本身偏移,因此对象不会与其重叠,然后仅绘制阴影(使用剪裁参数使用drawImage()) 在绘制主对象之前以所需大小返回主画布。

    在较新版本的浏览器中,您还可以使用带有 CSS 过滤器的上下文中的新 filter 属性手动生成高斯模糊阴影。它确实需要一些额外的合成步骤,并且在大多数情况下很可能需要屏幕外画布,但是您可以使用此方法在多个步骤中过度绘制阴影,半径从小到大可变,以牺牲一些性能为代价产生更明显的阴影。

    使用filter手动生成阴影的示例:

    这允许使用内置阴影等更复杂的形状,但可以更好地控制最终结果。在这种情况下,“衰减”可以通过在循环内使用具有初始归一化半径值的缓动函数来控制。

    // note: requires filter support on context
    var ctx = c.getContext("2d");
    
    var iterations = 16, radius = 50,
        step = radius / iterations;
    for(var i = 1; i < iterations; i++) {
      ctx.filter = "blur(" + (step * i) + "px)";
      ctx.fillRect(100, 50, 10, 10);
    }
    
    ctx.filter = "none";
    ctx.fillStyle = "#fff";
    ctx.fillRect(100, 50, 10, 10);
    &lt;canvas id=c&gt;&lt;/canvas&gt;

    gradient + filter 的示例:

    这是一个更跨浏览器友好的解决方案,好像不支持过滤器,至少渐变接近可接受的阴影。唯一的缺点是它在复杂形状方面受到更多限制。

    此外,为渐变使用可变中心点可以模拟衰减、灯光大小、灯光类型等。

    基于@Kaiido 在评论中的示例/mod -

    // note: requires filter support on context
    var ctx = c.getContext("2d");
    
    var grad = ctx.createRadialGradient(105,55,50,105,55,0);
    grad.addColorStop(0,"transparent");
    grad.addColorStop(0.33,"rgba(0,0,0,0.5)");	// extra point to control "fall-off"
    grad.addColorStop(1,"black");
    
    ctx.fillStyle = grad;
    ctx.filter = "blur(10px)";
    ctx.fillRect(0, 0, 300, 150);
    
    ctx.filter = "none";
    ctx.fillStyle = "#fff";
    ctx.fillRect(100, 50, 10, 10);
    &lt;canvas id=c&gt;&lt;/canvas&gt;

    【讨论】:

    • 最好看的可能是渐变+滤镜:blur()jsfiddle.net/seL753k4
    • @Kaiido 同意(很好)。渐变还允许使用“中间”点进行更好的控制,因此您可以模拟不同的灯光类型(衰减、距离、大小)。我也会添加这个作为例子。
    • 是的,渐变的唯一问题是复杂路径的控制变得更加困难......但我想在这些情况下对原始 shadowBlur 进行一些合成可能会有所帮助。
    猜你喜欢
    • 2015-09-17
    • 2020-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多