[ng] 讓 SVG 在 ng 裡面不 ng

只是工作筆記,想想之後一定會忘記這件事情,所以就記錄一下。大抵上就好像是,明知道 transform 搭配 position: fixed 會有雷,然後我還是傻傻的踩下去一樣。


關於上面提及的雷,可以參考 這裡,並不是每一種瀏覽器都會這樣,不過就是 Chrome/Firefox 會爆炸就是了。

這種時候要讚揚一下 IE11,他在搭配使用的時候,fixed 還是 fixed,並不會因為 transform 而有所改變。

只是說,有沒有改變到底是不是要依照 w3c 說的那樣,這個留給後人繼續發掘了(喂

SVG

向量圖形正夯,為了一些相容性的問題,所以坊間建議使用的 <img> 來包 SVG 圖檔,在某些行動裝置上的瀏覽器會中邪(對,就是 Safari

一般 SVG 要使用 #target 會事先利用 style 把 Group 給藏起來,

<def>
  <style><![CDATA[
    .flag { display: none; }
    .flag:target { display: block; }
  ]]></style>
</def>

類似這樣,如果使用 #target 來指定想顯示什麼圖層的話,在 Safari 上面會出包。當你的 SVG 有好幾個圖層隱藏,然後在同一個畫面畫出來,例如,

<img src="flag.svg#tw">
<img src="flag.svg#jp">
<img src="flag.svg#us">

然後你的 <img> 會在畫面上亂跳,有興趣的可以使用 Safari 試試看這件事情。基本上在其他的瀏覽器都沒問題,偏偏 Safari/Mobile Safari 都會,著實令人頭痛。

Object Tag

坊間的解法是,使用 <object> 來包 SVG,以達到相容性最大值的效果,

<object data="flag.svg#tw" type="image/svg+xml" width="100%" height="100%"></object>

這樣打包的話,當你使用 :target 屬性,就不會有什麼問題了,所有瀏覽器也能正常顯示。

Object Tag with AngularJS

然而,問題來了,如果有使用 AngularJS 的話,<object> 包起來的 SVG 就無法吃到 ng 的相關動作,舉例來說,

<button ng-click="clickme()">
  <object data="flag.svg#tw" type="image/svg+xml" width="100%" height="100%"></object>
</button>

基本上,上面的 ng-click 是無效的。原因在於,<object> 會把 SVG 讀入 DOM 裡面(當作是 Shadow DOM 來繪製,理所當然,既然是 Shadow DOM,如果 AngularJS 還碰得到的話,那就是奇蹟了(或許以後的 ng 可以摸到 Shadow DOM 也不一定?

Object Tag with SVG

利用 Directive 來 $compile

解法也是有的,利用 directive 把 SVG 讀取進來,然後 $compile 當中需要的元素即可,舉例來說,

angular.module('SVGMapApp')
.directive('SvgMap', ['$compile', function($compile) {
  return {
    restrict: 'A',
    scope: {
      language: '=bhSvgMap'
    },
    templateUrl: '/images/icon_locale_flag.svg',
    link: function($scope, $element, $attrs) {
      var element = angular.element('#'+$scope.language);
      $compile(element)($scope);
    }
  };
}]);

然後我們的 SVG 可能長這樣,

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 640 480" preserveAspectRatio="xMidYMid slice" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <g id="zh-tw" ng-if="language === 'zh-tw'>
    ...
  </g>
  <g id="en-us" ng-if="language === 'en-us'>
    ...
  </g>
</svg>

最後我們在 HTML 當中就這樣寫就好,

<div svg-map="'zh-tw'"></div>

這個時候,他畫出正常的 SVG,而這時候你也可以使用 ng,不管是外層還是內層。例如,我在 svgMap 加入一個程序讓 ng-click 來吃,然後在 SVG 當中加入 ng-click

<g id="zh-tw" ng-if="language === 'zh-tw' ng-click="showMe()">
  ...
</g>
link: function($scope, $element, $attrs) {
  $scope.showMe = function() {
    console.log($scope.language);
  };
  var element = angular.element('#'+$scope.language);
  $compile(element)($scope);
}

執行結果如圖,

ng-click in SVG

小結

說好之後要改 ReactJS 的吧?

SVG 這種狀況,雖然我不知道這到底算不算是雷,不過,依照目前使用 <object> 的結果看來,如果真的都是 Shadow DOM 的話,我想 ReactJS 應該也有屬於 ReactJS 的解法,畢竟,要另外寫東西去碰到 Shadow DOM 還是相當麻煩的一件事情。

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei