svg - 如何在 Safari 上使用性能可接受且不会崩溃的简单 SVG 过滤器?
问题描述
免责声明:这篇文章是我在尝试寻找解决方案时的实验的一半问题和一半报告。
任务:单色矩形上的简单 SVG 过滤器
在 SVG 中使用过滤器更改或修改单色形状的颜色非常简单。这是如何完成的:
<svg viewBox="0 0 460 130">
<defs>
<filter id="filter1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
</defs>
<g transform="translate(20, 0)">
<text x="0" y="35">Original</text>
<rect x="0" y="50" width="200" height="80" fill="red" />
</g>
<g transform="translate(240, 0)">
<text x="0" y="35">Filtered</text>
<rect x="0" y="50" width="200" height="80" fill="red" filter="url(#filter1)" />
</g>
</svg>
问题:在 Safari 上运行缓慢,在 Safari Mobile 上崩溃
这样做的小问题是它在 Safari 上真的很慢。在下面的片段中,每半秒将一个新的过滤矩形添加到 SVG 中。这最终会在 10-80 个周期后在 Safari 移动设备上崩溃,具体取决于您的设备。此外,它的速度慢得令人无法忍受。相比之下,在运行 Chrome 的入门级 Android 手机上,它几乎一直以 50 fps 的速度运行。
var rectCount = 1;
var svgRectElem = document.getElementById('svg-rect');
var rectCountElem = document.getElementById('rect-count');
var fpsElem = document.getElementById('fps');
(function insertRect() {
window.requestAnimationFrame(function() {
var start = new Date().getTime();
var clonedSvgRectElem = svgRectElem.cloneNode();
// insert cloned rect SVG element
svgRectElem.parentNode.appendChild(clonedSvgRectElem);
rectCountElem.textContent = ++rectCount;
window.requestAnimationFrame(function() {
var fps = Math.round(100000 / (new Date().getTime() - start)) / 100;
fpsElem.textContent = fps;
window.setTimeout(insertRect, 500);
}, 100);
});
})();
<p>SVG Rect Count: <span id="rect-count">1</span></p>
<p>fps: <span id="fps"></span></p>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0,0,1024,1024">
<defs>
<filter id="filter-1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
</defs>
<rect id="svg-rect" fill="red" filter="url(#filter-1)" x="0" y="0" width="1024" height="1024" />
</svg>
可能的解决方案:到目前为止我想出了什么
我尝试了几种方法,但正如您将看到的那样。正如您将看到的,其中一些适用于 Safari,每个都有一个问题。
1. 等待 WebKit 团队修复
tl;博士也许有效,也许不是
那将是最方便的选择,但如果您认为 SVG 过滤器是在 15 年前引入的,我不会屏息等待这个问题很快得到解决。是的,我写了一个错误报告。
2.使用filterRes
tl;dr 有效,但仅在下一个 Safari 版本之前
在 SVG 1.1filterRes
中定义了属性。这指定了过滤器的分辨率。值 1 表示过滤器的源将被视为单个像素。因此,过滤器不必遍历所有像素,而必须仅应用于一个像素。由于我们使用单色矩形作为输入图像,因此在我们的例子中将整个图像视为一个像素是可以的。
在我们的示例中,过滤器将如下所示:
<filter id="filter1" filterRes="1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
这实际上大大提高了性能,并且不会发生崩溃!
可悲的是,filterRes
它已从 2.0 版的 SVG 标准中删除。WebKit 很快就适应了 SVG 2.0 的这一部分,并决定在下一个版本中将其删除。如果我们依赖filterRes
,同样的问题很快就会再次出现。
3.过滤一个小矩形,然后放大
tl;博士不起作用
上面的方法表明,在小区域上应用过滤器可以大大提高性能。我们在一个小矩形上应用过滤器,然后将结果放大到所需的大小。对于缩放,我们使用transform
属性。如果我们将矩形设置为所需大小的 1/64,我们将对其进行缩放transform="scale(64)"
以达到 1024 的最终大小。
不幸的是,这根本不起作用。性能没有改变,它仍然在 Safari 移动设备上崩溃。有趣的是,这不能单独归因于过滤器或变换。只有结合起来,这会变慢并崩溃。
4 过滤一个小矩形,然后用于feTile
填充目标矩形
tl;dr 有点作用,但在 Chrome 上是人工制品
这种方法与以前的方法相似。我们首先将过滤器应用于一个小矩形,然后使用第二个过滤器feTile
填充目标矩形。
这适用于 Safari 手机。比不上filterRes
,但是有很大的提升。
但是对于这种方法,Chrome 是最糟糕的。如果您在 Chrome 上按比例放大和缩小,则在某些缩放级别中会显示光栅。这是它的外观(正确的是一个大正方形):
5 对过滤器的宽度和高度使用巨大的值
tl;dr 工作,但 hack 并导致 Internet Explorer 出现问题
这是一种非常反直觉的方法。将过滤器区域的宽度和高度设置为非常大的值应该会使过滤器更慢,因为必须处理数以亿计的像素。但实际上情况恰恰相反。过滤器在 WebKit 上显着加速并且不会发生崩溃。
以下是在我们的示例中如何实现这一点:
<filter id="filter-1" x="0" y="0" width="102400" height="102400" filterUnits="userSpaceOnUse">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
我认为这里发生的是过滤器首先缩小原始过滤器区域,然后处理将在 SVG 的 viewBox 中可见的部分(缩小后是一个小区域),然后再次放大该区域。这似乎很快。
不幸的是,这是一个相当大的技巧,我们现在不知道未来的浏览器版本将如何处理具有如此大值的 SVG。此外,这会导致 Internet Explorer 出现问题,文档的滚动区域将变得非常大(可能是因为过滤器的边界被计算为大小),使得滚动几乎不可能。如果您选择的值太小,Internet Explorer 实际上会以过滤器边界的大小显示矩形。
解决办法是什么?
其实,我不知道。使用 有一个简单的解决方案filterRes
,但很快就会过期(WebKit 技术预览版已经不支持它),所以这不是一个选项。所有其他方法都会导致其他浏览器出现问题。
你能想到我可以尝试的任何其他方法吗?我不敢相信在 SVG 1.1 规范发布十五年后跨浏览器使用 SVG 过滤器是一次冒险。
解决方案
尝试这个:
将所有矩形放入一个组中,然后将过滤器应用于该组。
var rectCount = 1;
var svgRectElem = document.getElementById('svg-rect');
var rectCountElem = document.getElementById('rect-count');
var fpsElem = document.getElementById('fps');
(function insertRect() {
window.requestAnimationFrame(function() {
var start = new Date().getTime();
var clonedSvgRectElem = svgRectElem.cloneNode();
// insert cloned rect SVG element
svgRectElem.parentNode.appendChild(clonedSvgRectElem);
rectCountElem.textContent = ++rectCount;
window.requestAnimationFrame(function() {
var fps = Math.round(100000 / (new Date().getTime() - start)) / 100;
fpsElem.textContent = fps;
window.setTimeout(insertRect, 500);
}, 100);
});
})();
<p>SVG Rect Count: <span id="rect-count">1</span></p>
<p>fps: <span id="fps"></span></p>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0,0,1024,1024">
<defs>
<filter id="filter-1">
<feColorMatrix values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 1 0" />
</filter>
</defs>
<g filter="url(#filter-1)">
<rect id="svg-rect" fill="red" x="0" y="0" width="1024" height="1024" />
</g>
</svg>
推荐阅读
- amazon-web-services - 由于微服务速度慢,API 网关连接断开
- python - 错误的光标移动win32api
- arrays - 在承诺中使用 Array.push 时,数组保持为空
- elasticsearch - 我们可以禁用 Kibana 版本不匹配警报吗
- flutter - 在颤振应用程序中保存 jwt 令牌的最佳方法是什么?
- redux-saga - redux-saga 中的 takeSequential?
- ironpython - 如何在 spotfire 中以彩色或任何 python 脚本显示前 5 个客户 ID
- c# - 从另一个字符串c#中删除一个字符串
- ruby-on-rails - 初始化程序 rspec 测试通过,但手动测试时实际代码中断
- qt - Qt线程中exec()之后的代码没有实现