JavaScript
Snap.svg

Snap.svgで内側領域の塗り分けの設定(fill-rule)に気をつけようという話

Snap.svg、すごいですよね。
あれでアニメーション作るとめちゃ楽しいですね。

さて、遊んでいる時に、困ったことがあり、数十分作業が止まったので、それの解決方法をメモしておきます。

環境

  • Snap.svg 0.5.1 (読み込んで使用)
  • Firefox 57.0.1 (表示用ブラウザ)
  • InkScape 0.92 (パス作成用)

内側が思ったように塗り分けられなかった

問題のPath図形

問題のPath図形は以下の様なものです。長いので、以後省略と書きます。
(パスが異常に長いのは、趣味垢用に作っていたものの流用なので許してください。)

m 208.90541,109.29049 
a 2.776174,1.2825703 56.427847 0 1 0.17457,2.82556 2.776174,1.2825703 56.427847 0 1 -2.63953,-1.92948 2.776174,1.2825703 56.427847 0 1 -0.17457,-2.82557 2.776174,1.2825703 56.427847 0 1 2.63953,1.92949 
z 
m -7.6781,-9.03334 
c -1.38619,7.72423 -0.87929,12.29629 2.92643,15.66947 3.63687,2.44904 6.89158,-0.27701 7.59069,-3.58952 l 7.31827,3.98818 -3.91191,-7.35347 c 4.18733,-0.94355 5.73619,-4.5737 3.68133,-7.54654 -3.13739,-3.778909 -8.23264,-4.527779 -15.63278,-3.116419 -0.0367,0.008 0.48808,-1.9531 0.48808,-1.9531 0.70114,-0.23214 2.17497,-0.7302 2.17431,-1.07625 -0.003,-0.31091 -2.15625,-0.73908 -2.15625,-0.73908 -0.4811,0.26621 -1.18982,1.43616 -1.95937,2.67939 -0.94574,-0.51419 -2.17534,-1.16488 -2.49479,-0.88413 -0.24015,0.24378 0.29808,1.47343 0.9046,2.45516 0,0 -2.71018,1.650499 -2.70297,1.926699 0.043,0.0607 0.41039,2.18349 0.72963,2.16888 0.32026,-0.006 0.9949,-1.77835 1.08578,-2.16491 0,0 2.00074,-0.47543 1.95888,-0.46435 
z 
m 10.86239,5.84155 
a 2.7761738,1.2825702 34.267371 0 0 2.82323,0.20883 2.7761738,1.2825702 34.267371 0 0 -1.89732,-2.66272 2.7761738,1.2825702 34.267371 0 0 -2.82323,-0.20885 2.7761738,1.2825702 34.267371 0 0 1.89732,2.66274 
z

InkScapeで以下のようなSVGファイルを作り、読み込みました。

sample_risou.html
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="420px" height="297px" style="background-color:#B2243C"> <path
       id="path8443-6-3"
       style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29069689;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
       d="省略"
       inkscape:connector-curvature="0"
       inkscape:label="center_icon" /></svg>

すると、下のように表示されます。
正しい写真.PNG

うまく行かなかったソース

sample.html
  <script src="./snap.svg.js"></script>
  <svg id="p" version="1.1" xmlns="http://www.w3.org/2000/svg"
    width="420px" height="297px"
    style="background-color:#B2243C"> 
  </svg>

このSVGに以下のようなソースでパス図形を表示させます。

sample.js
  Snap("#p").path(省略).attr({
    "fill":'#ffffff'
  })

で、longpathですが、
これを作画してみると、以下のようになってしまいます。
穴が一つしか空いていない写真.PNG

穴が一つしか空いていません。
これは大きな問題です。

原因

InkScapeが生成したSVGファイルの、長ったらしいstyle属性の中身に原因がありました。

  opacity:1;
  fill:#ffffff;
  fill-opacity:1;
  fill-rule:evenodd;
  stroke:none;
  stroke-width:0.29069689;
  stroke-linecap:round;
  stroke-linejoin:miter;
  stroke-miterlimit:4;
  stroke-dasharray:none;
  stroke-dashoffset:0;
  stroke-opacity:1;
  paint-order:fill markers stroke

多くは大したことのないデータですが、問題はこれの

  fill-rule:evenodd;

です。
これをうまくいかなかったJavaScriptに、

sample.js
  Snap("#p").path(省略).attr({
    "fill":'#ffffff',
    "fill-rule":"evenodd"
  });

このようにいれてやると、思った通りに表示されました。

解説

そもそも、何故、一つのパス図形で、内側でくり抜かれるという表現ができるかというと、SVGが"内側"についてのルールを定めているからなんです。
そのルールを指定してあげるのが、このfill-ruleでした。
SVG仕様(日本語訳)の当該説明箇所

自動生成で過剰に生成される長いソースの中に、とても重要な情報が含まれていることもあるので注意しよう、という話でした。