SVG 过滤器:不同的结果取决于浏览器
SVG filter: different result depending on browser
我正在尝试添加一个 point light effect to an SVG 矩形。问题是根据我使用的浏览器,我得到了不同的结果。例如,在 Chrome 和 Safari 中,我得到以下内容:
如何在不同的浏览器上使用 svg 过滤器获得一致的结果?
*, *:before, *:after {
box-sizing: border-box;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" rel="stylesheet"/>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="customPointLight">
<feSpecularLighting result="lightBuffer" specularConstant="1.5"
specularExponent="80" lighting-color="#fff">
<fePointLight x="100" y="100" z="80"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="lightBuffer" operator="out"
k1="0" k2="1" k3="1" k4="0"/>
</filter>
</defs>
<rect x="50" y="50" width="100" height="100" fill="blue" filter="url(#customPointLight)"></rect>
</svg>
对于在计算中使用相邻像素的所有过滤器,如 feSpecularLighting
、feGaussianBlur
或 feConvolveMatrix
,结果受属性 filterRes
的影响,该属性定义计算过滤效果的分辨率。与其他属性不同,specification 没有定义默认值:
If not provided, then the user agent will use reasonable values to produce a high-quality result on the output device.
这为 UA 之间的许多差异留下了空间。
feSpecularLighting
本身有一个属性 kernelUnitLength
明确引用过滤器分辨率:
For some level of consistency (sic!) across display media and user agents, it is necessary that a value be provided for at least one of ‘filterRes’ and ‘kernelUnitLength’.
Safari 会自动选择不正确的滤镜分辨率,这可能是因为没有人为 Retina 显示器更新代码。您可以通过向过滤器元素添加 filterRes="200" 来让 Safari 做 "mostly" 正确的事情,因为它还没有放弃对 filterRes 的支持。
就是说,今天,跨浏览器的正确做法是完全依靠光源,只使用填充有 black/white 径向渐变的矩形,导入为 data:URI feImage(用于 Firefox 和 Edge 兼容性)。正如我认为您想要的那样,屏幕混合会将白色高光添加到原件中。像这样:
svg {
background: red;
}
<svg width="400px" height="200px" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<radialGradient id="lightHack">
<stop offset="35%" stop-color="white"/>
<stop offset="80%" stop-color="black"/>
</radialGradient>
<filter id="customPointLight">
<feSpecularLighting result="lightBuffer" specularConstant="1.5"
specularExponent="80" lighting-color="#fff">
<fePointLight x="100" y="100" z="80"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="lightBuffer" operator="out"
k1="0" k2="1" k3="1" k4="0"/>
</filter>
<filter id="pointLightHack" x="0%" y="0%" width="100%" height="100%">
<feImage width="100" height="100" xlink:href="data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KDQogIDxkZWZzPg0KICAgIDxyYWRpYWxHcmFkaWVudCBpZD0iZXhhbXBsZUdyYWRpZW50Ij4NCiAgICAgIDxzdG9wIG9mZnNldD0iNDAlIiBzdG9wLWNvbG9yPSJ3aGl0ZSIvPg0KICAgICAgPHN0b3Agb2Zmc2V0PSI3NSUiIHN0b3AtY29sb3I9ImJsYWNrIi8+DQogICAgPC9yYWRpYWxHcmFkaWVudD4NCiAgPC9kZWZzPg0KICA8Y2lyY2xlIGZpbGw9InVybCgjZXhhbXBsZUdyYWRpZW50KSIgY3g9IjUwIiBjeT0iNTAiIHI9IjUwIi8+DQo8L3N2Zz4="/>
<feBlend mode="screen" in="SourceGraphic"/>
</filter>
</defs>
<rect x="50" y="50" width="100" height="100" fill="blue" filter="url(#customPointLight)"/>
<rect x="250" y="50" height="100" width="100" fill="blue" filter="url(#pointLightHack)"/>
</svg>
<!-- SVG source of the base64 encoded feImage -->
<svg width="100" height="100" viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="exampleGradient">
<stop offset="40%" stop-color="white"/>
<stop offset="75%" stop-color="black"/>
</radialGradient>
</defs>
<circle fill="url(#exampleGradient)" cx="50" cy="50" r="50"/>
</svg>
顺便说一句,你没有正确使用光照效果,镜面光照应该添加 "shiny" 高光,所以正确的用法是在源之上合成结果。漫射照明应该添加 "regular" 光,并且应该与原始图形相乘。在这两种情况下,您都不应该使用 "out" 复合操作 - 它会在您的矩形中打一个透明孔,正如您在上面添加红色背景时所看到的那样。
现在解决了添加一个带有模糊基元的圆圈,它提供了类似的效果并且似乎在浏览器中正确呈现。
*, *:before, *:after {
box-sizing: border-box;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" rel="stylesheet"/>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="blur">
<feGaussianBlur in="SourceGraphic" stdDeviation="4"></feGaussianBlur>
</filter>
</defs>
<rect x="50" y="50" width="100" height="100" fill="blue""></rect>
<circle cx="100" cy="100" r="20" fill="#fff" filter="url(#blur)"></circle>
</svg>
我正在尝试添加一个 point light effect to an SVG 矩形。问题是根据我使用的浏览器,我得到了不同的结果。例如,在 Chrome 和 Safari 中,我得到以下内容:
如何在不同的浏览器上使用 svg 过滤器获得一致的结果?
*, *:before, *:after {
box-sizing: border-box;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" rel="stylesheet"/>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="customPointLight">
<feSpecularLighting result="lightBuffer" specularConstant="1.5"
specularExponent="80" lighting-color="#fff">
<fePointLight x="100" y="100" z="80"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="lightBuffer" operator="out"
k1="0" k2="1" k3="1" k4="0"/>
</filter>
</defs>
<rect x="50" y="50" width="100" height="100" fill="blue" filter="url(#customPointLight)"></rect>
</svg>
对于在计算中使用相邻像素的所有过滤器,如 feSpecularLighting
、feGaussianBlur
或 feConvolveMatrix
,结果受属性 filterRes
的影响,该属性定义计算过滤效果的分辨率。与其他属性不同,specification 没有定义默认值:
If not provided, then the user agent will use reasonable values to produce a high-quality result on the output device.
这为 UA 之间的许多差异留下了空间。
feSpecularLighting
本身有一个属性 kernelUnitLength
明确引用过滤器分辨率:
For some level of consistency (sic!) across display media and user agents, it is necessary that a value be provided for at least one of ‘filterRes’ and ‘kernelUnitLength’.
Safari 会自动选择不正确的滤镜分辨率,这可能是因为没有人为 Retina 显示器更新代码。您可以通过向过滤器元素添加 filterRes="200" 来让 Safari 做 "mostly" 正确的事情,因为它还没有放弃对 filterRes 的支持。
就是说,今天,跨浏览器的正确做法是完全依靠光源,只使用填充有 black/white 径向渐变的矩形,导入为 data:URI feImage(用于 Firefox 和 Edge 兼容性)。正如我认为您想要的那样,屏幕混合会将白色高光添加到原件中。像这样:
svg {
background: red;
}
<svg width="400px" height="200px" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<radialGradient id="lightHack">
<stop offset="35%" stop-color="white"/>
<stop offset="80%" stop-color="black"/>
</radialGradient>
<filter id="customPointLight">
<feSpecularLighting result="lightBuffer" specularConstant="1.5"
specularExponent="80" lighting-color="#fff">
<fePointLight x="100" y="100" z="80"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="lightBuffer" operator="out"
k1="0" k2="1" k3="1" k4="0"/>
</filter>
<filter id="pointLightHack" x="0%" y="0%" width="100%" height="100%">
<feImage width="100" height="100" xlink:href="data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KDQogIDxkZWZzPg0KICAgIDxyYWRpYWxHcmFkaWVudCBpZD0iZXhhbXBsZUdyYWRpZW50Ij4NCiAgICAgIDxzdG9wIG9mZnNldD0iNDAlIiBzdG9wLWNvbG9yPSJ3aGl0ZSIvPg0KICAgICAgPHN0b3Agb2Zmc2V0PSI3NSUiIHN0b3AtY29sb3I9ImJsYWNrIi8+DQogICAgPC9yYWRpYWxHcmFkaWVudD4NCiAgPC9kZWZzPg0KICA8Y2lyY2xlIGZpbGw9InVybCgjZXhhbXBsZUdyYWRpZW50KSIgY3g9IjUwIiBjeT0iNTAiIHI9IjUwIi8+DQo8L3N2Zz4="/>
<feBlend mode="screen" in="SourceGraphic"/>
</filter>
</defs>
<rect x="50" y="50" width="100" height="100" fill="blue" filter="url(#customPointLight)"/>
<rect x="250" y="50" height="100" width="100" fill="blue" filter="url(#pointLightHack)"/>
</svg>
<!-- SVG source of the base64 encoded feImage -->
<svg width="100" height="100" viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="exampleGradient">
<stop offset="40%" stop-color="white"/>
<stop offset="75%" stop-color="black"/>
</radialGradient>
</defs>
<circle fill="url(#exampleGradient)" cx="50" cy="50" r="50"/>
</svg>
顺便说一句,你没有正确使用光照效果,镜面光照应该添加 "shiny" 高光,所以正确的用法是在源之上合成结果。漫射照明应该添加 "regular" 光,并且应该与原始图形相乘。在这两种情况下,您都不应该使用 "out" 复合操作 - 它会在您的矩形中打一个透明孔,正如您在上面添加红色背景时所看到的那样。
现在解决了添加一个带有模糊基元的圆圈,它提供了类似的效果并且似乎在浏览器中正确呈现。
*, *:before, *:after {
box-sizing: border-box;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" rel="stylesheet"/>
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="blur">
<feGaussianBlur in="SourceGraphic" stdDeviation="4"></feGaussianBlur>
</filter>
</defs>
<rect x="50" y="50" width="100" height="100" fill="blue""></rect>
<circle cx="100" cy="100" r="20" fill="#fff" filter="url(#blur)"></circle>
</svg>