在<svg>裏面可以利用<foreignObject>繪製html標籤,原本是我在iconfont採用Font class方式引入svg的無奈之舉。
起初的設計是所有icon先在<defs>中先渲染,以達到icon複用的效果,icon採用Symbol方式引入svg感覺也是比較合適的,比較規範的。
<template> <defs> <g v-for="item in list" :key="item._id" :id="'icon-' + item._id"> <svg aria-hidden="true" width="16" height="16" x="0" y="0"> <use :xlink:href="'#' + item.icon"></use> </svg> </g> </defs> </template> <script> export default { data() { return { list: [], }; }, }; </script>
然後再需要用到的地方用<use :xlink:href="'#icon-' + id" />克隆下來,感覺很完美。
但是理想很豐滿,現實很骨感。由於某些功能會被影響到,不能使用Symbol方式引入,最後只能選擇Font class引入svg。於是代碼變爲了下列
<template> <defs> <g v-for="item in list" :key="item._id" :id="'icon-' + item._id"> <foreignObject width="16" height="16" x="16" y="16"> <div xmlns="http://www.w3.org/1999/xhtml"> <span class="iconfont" :class="item.icon"></span> </div> </foreignObject> </g> </defs> </template> <script> export default { data() { return { list: [], }; }, }; </script>
但是在需要的地方使用<use :xlink:href="'#icon-' + id" />克隆下來,會發現在谷歌瀏覽器上卻完全顯示不出<span>標籤的內容,即不顯示iconfont圖標。
剛開始,我以爲是不能在<defs>標籤中使用<foreignObject>標籤,於是我就去查看了SVG規範,傳送門:https://www.w3.org/TR/SVG/struct.html#DefsElement,SVG規範是支持這種寫法的。打開F12,查看<defs>標籤下的dom結構,也可以看到<foreignObject>標籤其實是有生成的,也是佐證了這一點。
但是查看引用<use>標籤的地方,就沒有生成對應的<foreignObject>標籤,我查看SVG規範文檔並沒有提到<use>標籤不能與<foreignObject>標籤共同使用的限制。最後我打開了github,在w3c的【SVG工作組規範】項目下尋找答案,傳送門:https://github.com/w3c/svgwg,最後找到了一個討論:https://github.com/w3c/svgwg/issues/511。這位程序員在討論中說除了 Gecko 之外的所有瀏覽器都限制<svg:use>元素中的<foreignObject>,他在思考爲什麼Gecko之類的瀏覽器允許這麼做。
這下就有點頭緒了,原來是瀏覽器內核原因。那簡單,我們找個Gecko內核的瀏覽器驗證下就知道了,Gecko內核最出名的就是FireFox瀏覽器(火狐瀏覽器)了。其實我的電腦也裝了火狐瀏覽器,但是由於我開發一直用的是谷歌瀏覽器,確實也是好久好久沒打開火狐了,放着喫灰,這次也確實沒想到可能是瀏覽器本身的問題。打開火狐瀏覽器,果然能顯示<span>標籤的內容,即顯示了iconfont圖標。
不過爲什麼會出現這樣的情況呢,另一個叫Dirk Schulze的程序員表示:出於複雜性的原因,WebKit不允許引用foreignObject。 我們沒有時間查看所有影響(包括安全影響),如果內容是基於HTML的,那麼對foreignObject的支持永遠不會很好。(Blink修復了後半部分)
也就是說Blink內核修復了後半段,使瀏覽器更好的支持了<foreignObject>標籤,但是對於引用<foreignObject>標籤的情況,還是沒有任何進展。那也就是說谷歌瀏覽器現在是支持的<foreignObject>標籤的,只是不支持被<use>標籤引用。
最後直接棄用<defs>和<use>,在需要的地方直接渲染。簡單粗暴,最有效。
<foreignObject width="16" height="16" x="16" y="16"> <div class="icon-div" xmlns="http://www.w3.org/1999/xhtml"> <span class="iconfont" :class="classRef.ModuleClassType.Icon"></span> </div> </foreignObject>
雖然不夠優雅,但是真香。
事情原本到這就應該結束了,但是我還是不死心,不知道爲什麼WebKit要做這樣的一個策略。最後,功夫不負有心人,我在Bugzilla又找到了一個提交給WebKit的bug:https://bugs.webkit.org/show_bug.cgi?id=91515。底下有一名名爲Nikolas Zimmermann的程序員對此進行了迴應:
原文大意是:
是的。由於與foreignObject相關的潛在問題,我們故意禁止它。它需要經過充分測試,僅此而已。
當啓用它時,我們需要注意新類型的循環引用,這就是它變得棘手的地方。
foo.svg,包含 <symbol id="symbol"><foreignObject> <iframe src="other.html"/></foreignObject></symbol>
blub.svg 引用"symbol"。other.html包含foo.svg作爲html:img。... -> 循環
或者考慮<foreignObject>包含<div style="background-image: blub.svg" 的情況...
我們基本上需要將循環檢測擴展到所有可以引用其他文件的 HTML 元素/屬性。
如果您感到有挑戰,請隨時開始,否則我將不實施解決這個問題。
不過這個bug之後在2020年被其他人重新提起,於是,應該是Nikolas Zimmermann的同事Said Abou-Hallawa在底下也對這個bug進行了補充評論。
原文大意是:
上述測試用例在FireFox中有效,但在WebKit或Chrome中無效。
由於foreignObjectTag不是createAllowedElementSet允許的標記之一,因此foreignObject 及其後代被removeDisallowedElementsFromSubtree() 刪除。但是即使添加它也不能解決問題,因爲 HTML<p>元素將被刪除(此處應該是指bug提交人的示例中的p標籤),因爲它的標籤是不允許的。
爲了解決這個問題,我們需要重新實現removeDisallowedElementsFromSubtree(),並且正如 Nikolas 上面提到的,我們需要將循環檢測擴展到所有 HTML 元素,以防它們中的任何一個引用其他文件。
所以,很明顯,到目前爲止,他們也沒解決這個問題,導致他們做出這個策略的一個原因是因爲removeDisallowedElementsFromSubtree()這個方法寫的不夠完善,在某些場景下會出現循環引用的bug,最簡單粗暴的辦法就是直接不讓你在<use>標籤中引用<foreignObject>標籤,於是他們直接就從源頭解決了這個問題。妙,妙,妙啊,真是妙蛙種子喫着妙脆角進了米奇妙妙屋,妙到家了。爲了確認這兩人的權威性,我特地去查看了WebKit團隊的名單,傳送門https://webkit.org/team/,確實找到了這兩個大佬的名字,上文提到的Dirk Schulze也是這個團隊中的一員。
這下事情是真的結束了,最後大致掃一眼名單,這個團隊的很多人最後都去了蘋果,不得不說蘋果真的挖人有一套,滿屏的apple。