在<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。