Safari/WebKit 无法正确渲染 foreignObject 中的 HTML 元素
实锤了,Safari 就是新时代的 IE 浏览器。原因是有些东西在 Safari 渲染表现与预期(标准)不一致,而且 Safari for Mac 跟 Safari for iOS 的表现还不一定是相同的。
背景
今天遇到了这样一个问题。举个例子,假设外层一个 max-width: 430px 的 section 元素,里面是一个 svg 元素,里面包含动画还有嵌套了一些元素。预期表现是:点击红色区域,绿色背景透明度匀速从 0 切换至 1。
<section style="max-width: 430px; margin: auto; overflow: hidden; font-size: 0"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="pointer-events: none; width: 100%; background-color: red"> <foreignObject x="0" y="0" width="100%" height="100%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; background-color: green"> <animate attributeName="opacity" begin="click" from="0" to="1" calcMode="linear" dur="1s" fill="freeze" restart="never" /> <rect x="0" y="0" width="100%" height="100%" fill="transparent" style="pointer-events: visible"> <set attributeName="visibility" begin="click" to="hidden" fill="freeze" restart="never" /> </rect> </svg> </foreignObject> </svg> </section>
body { margin: 20px; }
根据所设置的 viewBox="0 0 350 350"
、preserveAspectRatio="xMidYMin meet"
以及 width: 100%
,按道理的话,红色的 <svg>
及其内嵌套 <foreignObject>
和 <svg>
,应该都是同等大小的正方形,而且取决于父元素 <section>
的宽度。
是的,这个在 Chrome 表现没问题,但在 Safari for Mac 上就出现问题了,离奇的是 Safari for iOS 也是正常的。
案例一
如下图,此时 <body>
的宽度是大于 430px,因此 <section>
的宽度为 430px,自然 <svg>
的宽度就是 430px
。
但是,当我们点击蓝色框之外,红色区域(截图由于选中元素,该区域表现为橘色)以内的位置,你知道 Safari 定位到的元素是什么吗?
嗯......它定位到 <section>
元素了。意思就是说,内部的 元素区域并未覆盖到点击区,但我宽高明明设置的都是 100%
,就很离谱。
但是,我在右侧 Elements 选项卡选中 <rect>
元素时,它表现的区域明明就是占满的啊,也就是 430 * 430
。
Safari 你在玩我?
经多次测试,它可点击区域只有 350 * 350
,也就是 viewBox
那个空间。
解决办法
由于是 Safari 的 bug,目前只能用一些治标不治本的方法,用魔法打败魔法。
给 <rect>
设置 transform: scale(2); transform-origin: left top;
,其父级的 <svg>
设置 overflow: visible
。由于 <foreignObject>
元素默认为 overflow: hidden
,因此不用担心点击 430 * 430
之外的位置会触发事件。
案例二
利用 <svg>
做了一个循环切换的交互,同样地,它在 Chrome 一切安好,而在 Safari 下则惊喜满满。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="width: 100%"> <foreignObject x="0" y="0" width="100%" height="100%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="width: 100%"> <foreignObject x="0" y="0" width="100%" height="100%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 1; width: 100%; background-size: cover; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475354583.png); background-color: red"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.22222222; 0.33333333; 1" values="1; 1; 0; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </foreignObject> <foreignObject x="0" y="0" width="100%" height="100%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; width: 100%; background-repeat: no-repeat; background-size: cover; background-position: top center; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475369330.png); background-color: green"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.22222222; 0.33333333; 0.55555556; 0.66666667; 0.666666670001; 1" values="0; 0; 1; 1; 0; 0; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </foreignObject> <foreignObject x="0" y="0" width="100%" height="100%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; width: 100%; background-repeat: no-repeat; background-size: cover; background-position: top center; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475408407.png); background-color: blue"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.55555556; 0.66666667; 0.88888889; 0.99999999; 1" values="0; 0; 1; 1; 0; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </foreignObject> <foreignObject x="0" y="0" width="100%" height="100%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; width: 100%; background-repeat: no-repeat; background-size: cover; background-position: top center; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475354583.png); background-color: red"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.88888889; 0.99999999; 1" values="0; 0; 1; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </foreignObject> </svg> </foreignObject> </svg>
Safari 表现出「忽大忽小」的问题。如下图,灰色背景大小为 430 * 430,而红色背景处则是 350 * 350。
由于录制 GIF 太麻烦了,你可以使用 Safari 打开链接体验一下:https://codepen.io/tofrankie/full/abRWpaE。
解决方法
由于 <foreignObject>
的坑,那就不要嵌套多层,所以可以这样处理,结构上也更清晰。
<section> <section style="height: 0"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 1; width: 100%; background-size: cover; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475354583.png); background-color: red"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.22222222; 0.33333333; 1" values="1; 1; 0; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </section> <section style="height: 0"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; width: 100%; background-repeat: no-repeat; background-size: cover; background-position: top center; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475369330.png); background-color: green"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.22222222; 0.33333333; 0.55555556; 0.66666667; 0.666666670001; 1" values="0; 0; 1; 1; 0; 0; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </section> <section style="height: 0"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; width: 100%; background-repeat: no-repeat; background-size: cover; background-position: top center; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475408407.png); background-color: blue"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.55555556; 0.66666667; 0.88888889; 0.99999999; 1" values="0; 0; 1; 1; 0; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </section> <section style="height: 0"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="opacity: 0; width: 100%; background-repeat: no-repeat; background-size: cover; background-position: top center; background-image: url(https://cdn.jsdelivr.net/gh/toFrankie/blog/images/1682475354583.png); background-color: red"> <animate attributeName="opacity" begin="0s" keyTimes="0; 0.88888889; 0.99999999; 1" values="0; 0; 1; 0" calcMode="linear" dur="9s" repeatCount="indefinite" /> </svg> </section> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 350" preserveAspectRatio="xMidYMin meet" style="width: 100%"></svg> </section>
原因
这是 Webkit 的 Bug,相关链接:
- Layer content inside HTML in SVG foreignObject renders in the wrong place
- Support zoom on foreignObject
该问题早在 2009 年就提出了,至今仍然没有任何进展,隔壁 Chromium 的 Blink 已在 2020 年 9 月修复。其中一个可复现的示例:https://codesandbox.io/s/chrome-foreignobject-defect-wf91j。在 Safari 打开使用 ⌘
+ +
或 ⌘
+ -
去缩放页面就能看到。
我用 Chrome 62 亲测了一下,确实也有问题,而且区域更小了。
简言之,根本原因就是 Safari/WebKit 无法正确渲染 <foreignObject>
中的 HTML 元素。实锤了,Safari 就是新时代的 IE 浏览器。
References
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论