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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章