WebKit策略:<foreignObject>可用於繪製svg中的html標籤,但與<use>搭配不生效

2022-11-12 18:00:18

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