♪Vector Vector, please. Oh, the mess I'm in (c) UFO
IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法
この記事はSVG Advent Calendar 2014 - Adventarの11日目の参加記事です。
実は、この記事のきっかけとなったのも、SVG Advent Calendarに投稿されていた@_tanshioさんの「IEにも対応したレスポンシブSVGの作り方」という記事でした。
記事を読んだとき、直感的に「IEの問題というよりもSVGやCSSの記述方法に問題がありそう」という印象を持ち、簡単にテストしてみてツイッターでつぶやいたら、ご本人からリプライがあっていろいろやりとりをしていました。 この記事は、その一連のやりとりをまとめなおしたものです。 ただ、自分の思い込みなどから見当違いなツイートがアレやコレやとあったりもしたので、この記事は、その一連のやりとりにおける自分の発言に対する訂正の意味合いも兼ねていますw
インラインSVGでIEがFirefoxやChromeと表示が異なるのはIEの「バグ」なのか?
ますはインラインSVGのテストケース。 以下、このテストケースを参照しながら記述していきます。
テストケースでは、IEでの表示サイズがおかしい(ように見える)、すなわち、FirefoxやChromeとは一致していない例は「赤丸」で示しています。
さて、「赤丸」となっている例には、共通する特徴が2つあることにお気づきでしょうか? (ただし、5-2~5-3の'slice'の例は除きます)
1つめは、svg要素に「高さ」が明示されていないときにIEでの表示が異なっている、という点です。
「でもCSSで'height:100%'って指定している例とかも赤いじゃんか」と思われるかもしれませんが、この場合、%での指定は固有の高さを与えるということにはなっていません。 FirefoxやChromeなどでテストケースの3-3と3-4を見比べてみて下さい。
SVGは「画像」ではありますが、言ってしまえば、単なる「座標」です。 PNGやJPGなどとは違って、あらかじめ幅や高さを持ってはいないので、CSSで幅や高さを明示してあげる必要があります。
では、適切な幅や高さが明示されていなかった場合にはどうなるのか? というと、SVG1.1の仕様ではフォールバックとしてSVGの「固有サイズ」(intrinsic size)を次のように規定しています。
他の言語の中に含められるようにするため、 SVG には何らかの固有の( intrinsic )大きさを表すプロパティの算出方法が指定される必要がある。 SVG 内容の ビューポート の固有の幅と高さは width, height 属性から決定されなければならない。 これらのいずれも指定されていない場合、値 '100%' と見なされなければならない。 注記: width, height 属性は CSS width, height プロパティと同じもの ではない 。 特に、百分率値は 固有の幅や高さを与えるものでも,内容ブロックの百分率を指示するものでもない。 それらはむしろ、ビューポートが一度確立された後,画像データに実際に覆われるビューポートの領域を指示する。
ちなみに、ここでいっているビューポート
とはsvgの描画領域、すなわちsvg要素自身です。
そしてFirefoxやChromeでの表示は、適切な幅と高さが与えられなかった場合にこのフォールバックが働いて、こちらの手抜き(指定の不備)を補って、よしなに表示してくれたその結果なわけです。
では、IEはなぜそうならないのか?
2つの共通する特徴がある、と先に述べましたが、その2つめの特徴とは、svg要素の「高さ」が常に150pxであるということです。
赤丸の画像自体の高さ(直径)ではなく点線で示しているsvg要素の高さであることにご注意下さい。
この「高さ150px」は一体どこから来ているのでしょうか? いろいろ調べたところ、どうやら、SVGの仕様からではなく、CSS2.1の仕様を根拠としているようです。
'height'および'width'が'auto'の算出値を持ち、かつその要素が固有比を持つが固有の高さまたは幅を持たない場合、'width'の使用値はCSS 2.1では未定義である。しかし、その包含ブロックの幅が置換要素の幅に依存しない場合、'width'の使用値は通常フローでの非置換ブロックレベル要素に用いる拘束方程式より計算されることが示唆される。
そうでなければ、'width'が'auto'の算出値を持つ場合、その要素は固有幅を持ち、その結果その固有幅は'width'の使用値となる。
そうでなければ、上記の条件に一致せず、'width'が'auto'の算出値を持つ場合、'width'の使用値は300pxになる。デバイスにあわせて300pxが広すぎる場合、ユーザーエージェントは2:1の比率を持つ最大の矩形の幅を使用し、代わりにデバイスに収めるべきである。
このCSS2.1の仕様に沿って、IEでは適切な幅や高さが取れない場合には「300px(以上)*150px」で表示しているわけです。
ただし、
- デバイスの幅が300pxに満たない場合には、
2:1の比率を持つ最大の矩形の幅を使用
しろってなっているのに、なぜ300px以上ある場合にも高さが150px固定となるのか - その一方で、テストケースの1-0の例を見ればおわかりの通り、そもそも幅が300pxではなく親要素の幅一杯まで広がっているのはなぜなのか
そのあたりの理由についてはよくわかりませんが、まぁそんなものかね、ということで先に進みます。 CSS3が出ている現在、過去の仕様との互換性をつきつめてもあまり意味はありませんw
いずれにせよ、IEの固有サイズ表示で高さが150pxに固定されるのは、「バグ」というわけではなく、単にSVG1.1の仕様をIEがまだ実装していないだけであり、実は、IEはCSS2.1の仕様に沿ってフォールバックを適切に(?)働かせている、ということになります。
IEもIEで、こちらの手抜き(指定の不備)を補って、よしなに表示してくれているわけですw
IEにも対応したレスポンシブSVGの作り方(JavaScript適用版)
@_tanshioさんのおっしゃっているレスポンシブ
とは、昔で言うところの「リキッドデザイン」的なことだと思いますが、その記事の主題であった「幅が可変(不定)な親要素にあわせて、svgの持つアスペクト比を保ちながらインラインsvgを親要素の幅一杯に表示(拡大・縮小)する」にはどうすればいいのでしょうか。
「幅」は'max-width:100%;'の指定でまだどうにかなるとして、適切な「高さ」がCSSで明示できない以上、これはCSSだけではいかんともしようがありません。
ただし、幸い、IEで固有サイズ表示がされている場合には、「必ず高さが150pxになる」ということはわかっています。 そこで、JavaScriptを使って、IEで固有サイズ表示がされている場合にはアスペクト比から適切な幅と高さを再指定してあげるような修正スクリプトを作ってみました。 ついでに、5-2と5-3の例にも対応してあります。
/**
* fixSvgIntrinsicSizing
* IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正
* おまけで、preserveAspectRatioが'slice'だった場合に'overflow:hidden'を適用
* 注意:svg要素にはviewBox属性を必須としています
* なお、特定のsvg要素への適用を避けたい場合には、そのsvg要素にclass="noFixSvgIntrinsicSizing"を付与してください
* Fix inline SVG intrinsic sizing in IE.
* Plus, add 'overflow:hidden' when preserveAspectRatio has 'slice' value.
* NOTE: 'viewBox' attribute is required in svg element.
* To prevent applying to the specific svg element, add class="noFixSvgIntrinsicSizing" to that svg element.
* Copyright (c) 2014-2016 Kazz
* http://asamuzak.jp
* Dual licensed under MIT or GPL
* http://asamuzak.jp/license
*/
(function(_win, _doc) {
"use strict";
if(_doc.documentMode) {
var fixSvgIntrinsicSizing = function() {
function getAspect(o) {
return (o = o.split(" ")) && o[3] / o[2];
}
var x = _doc.querySelectorAll("svg[viewBox]");
if(x) {
for(var y, z, a, b, i = 0, l = x.length; i < l; i++) {
y = x[i];
if(!/noFixSvgIntrinsicSizing/.test(y.className.baseVal)) {
y.hasAttribute("preserveAspectRatio") &&
/slice/.test(y.getAttribute("preserveAspectRatio")) &&
(y.style.overflow = "hidden");
a = _win.getComputedStyle(y, "").width;
b = _win.getComputedStyle(y, "").height;
y.style.width = "";
y.style.height = "";
z = _win.getComputedStyle(y, "").height;
if(z !== "150px") {
y.style.width = a;
y.style.height = b;
}
else {
z = _win.getComputedStyle(y, "").width;
a = /([0-9\.]+)px/.exec(z)[1] * 1;
b = getAspect(y.getAttribute("viewBox"));
a * b > _doc.documentElement.offsetHeight && (
y.style.height = (a * b) + "px",
z = _win.getComputedStyle(y, "").width,
a = /([0-9\.]+)px/.exec(z)[1] * 1
);
y.style.width = z;
y.style.height = (a * b) + "px";
}
}
}
}
};
_doc.addEventListener("DOMContentLoaded", fixSvgIntrinsicSizing, false);
_win.addEventListener("resize", fixSvgIntrinsicSizing, false);
}
})(window, document);
そして、テストケースの修正スクリプト適用後のサンプルはこちら。
スクリプトを使うにあたっての注意事項は2点。
- svg要素にはviewBox属性を付与しておくことが必要です。 viewBox属性がないとこのスクリプトは適用されません(アスペクト比が取れないので)。
- preserveAspectRatio="none"で、かつ、heightを150pxに指定していた場合、スクリプトによってアスペクト比が有効に戻されてしまいます。 このようなケースなど、もし特定のsvg要素への適用を避けたい場合には、そのsvg要素にclass="noFixSvgIntrinsicSizing"を付与してください。
svgの入れ子があった場合にどうなるかなどテストし切れていないケースもありますが、でもそれなりに対応できているのではないかと思います。
ということで、このスクリプトを使っておけば、Michael Schenker、まぁ、いけるし、ええんか~?
お後がよろしいようで…m(_ _)m (でも今のところ翌日のカレンダーは空いているようですが…orz)
比較参照できるよう、img要素でSVGを使った場合のテストも追加しました。
"♪Vector Vector, please. Oh, the mess I'm in (c) UFO"へのTwitter上でのコメントやRT
34件のツイートがあります。
ツイート 1
asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO ~ IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法 http://t.co/FSh01OKmUW
ツイート 2
SVG Advent Calenderに参加 今日中に記事書いておかないと年末まで時間が取れそうにないのでやっつけで書き上げたw ♪Vector Vector, please. Oh, the mess I'm in (c) UFO http://t.co/FSh01OKmUW
ツイート 3
スクリプトのコメントで 'そのsvg要素にclass="noFixSvgIntrinsicSizing"を…' と書くつもりが 'そのsvg要素にclass="fixSvgIntrinsicSizing"を…' とミスっていた…orz http://t.co/FSh01OKmUW
ツイート 4
.@_tanshio おかげさまで記事の公開にこぎ着けられました http://t.co/FSh01OKmUW あと簡略版ですが、img要素でのテストケースも追加しました img要素のwidth属性かCSSのwidthでOKですね http://t.co/i66070pTPr
ツイート 5
本文中でも同じ間違いしてた…orz やっぱやっつけでやるのはダメだな… http://t.co/FSh01OKmUW
ツイート 6
SVG Advent Calendar 2014 11 日目: IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法 http://t.co/apbXVEQHpm
ツイート 7
ちなみに、こちらにその理由を書いていたりする… http://t.co/cZqExeogsj https://t.co/yGYjv3C7BD
ツイート 8
[B!] asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO http://t.co/Js1LcYqiz3
ツイート 9
“asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO” http://t.co/uKXutWzkQM
ツイート 10
要するに http://t.co/cZqExeogsj で説明しようとしていたことと理由は一緒 もっとも上記の記事はインラインsvgであり元々高さも幅も持たず、今回はimg要素で置換要素であるという違いはあるけど
ツイート 11
RT @asamuzakjp: 要するに http://t.co/cZqExeogsj で説明しようとしていたことと理由は一緒 もっとも上記の記事はインラインsvgであり元々高さも幅も持たず、今回はimg要素で置換要素であるという違いはあるけど
ツイート 12
IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法 http://t.co/kZaPXKyWwJ
ツイート 13
#tech #IE #css asamuzaK.jp : IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法: ぼんやりとした考えで対処療法で対応していた点がようやく理解できた。(Adven.. http://t.co/5FTWqsaQWs
ツイート 14
RT @asamuzakjp: 要するに http://t.co/cZqExeogsj で説明しようとしていたことと理由は一緒 もっとも上記の記事はインラインsvgであり元々高さも幅も持たず、今回はimg要素で置換要素であるという違いはあるけど
ツイート 15
MS edgeでテスト インラインSVGの固有サイズ表示はSVG1.1仕様になっている♪ http://t.co/cZqExeogsj SVG#fragmentでimg srcsetが使える♪ http://t.co/KxOnXf9XCl
ツイート 16
@SaekiTominaga さん、初めまして。私も以前に似たような状況で困ったのですが、IEの挙動(仕様)についてはこちらの記事が参考になるかなと http://t.co/kZaPXKyWwJ 記事にもありますがJavaScriptで対応するのがやりやすいでしょうか。
ツイート 17
RT @rikuo: @SaekiTominaga さん、初めまして。私も以前に似たような状況で困ったのですが、IEの挙動(仕様)についてはこちらの記事が参考になるかなと http://t.co/kZaPXKyWwJ 記事にもありますがJavaScriptで対応するのがやりやす…
ツイート 18
asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO http://t.co/AwysHie9Ln
ツイート 19
asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO https://t.co/cZqExeogsj https://t.co/fGEo8W5WhU
ツイート 20
RT @asamuzakjp: asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO https://t.co/cZqExeogsj https://t.co/fGEo8W5WhU
ツイート 21
あれ?よくよく見なおしてみたら、これじゃあresizeイベントで再計算してないような… asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO https://t.co/cZqExeogsj
ツイート 22
あー、JavaScriptで幅と高さが与えられている場合の処理も付けた方がいいな… あとで書き直そう asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO https://t.co/cZqExeogsj
ツイート 23
JavaScriptで幅と高さが与えられている場合の処理を追加 テストケースは6-1~6-3 https://t.co/4MlWuY4tkk https://t.co/oi2QYpvTJa https://t.co/cZqExeogsj
ツイート 24
@kashiwagiyugehi さん、初めまして。IEでbackground-positionがずれる問題についてこちらが原因と(一応の)解決策になるでしょうか https://t.co/I9pH0PHyaj ,あとこちらの記事も https://t.co/kZaPXKyWwJ
ツイート 25
RT @rikuo: @kashiwagiyugehi さん、初めまして。IEでbackground-positionがずれる問題についてこちらが原因と(一応の)解決策になるでしょうか https://t.co/I9pH0PHyaj ,あとこちらの記事も https://t.c…
ツイート 26
2016年05月14日のSVG:SVG導入にあたってPros/Cons - 週刊SVG https://t.co/isvflP32gr おや、またうちの記事を紹介してくださっている https://t.co/cZqExeogsj
ツイート 27
asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO ~ IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法 https://t.co/8ahqNL7akl
ツイート 28
つ https://t.co/cZqExeogsj https://t.co/iAJ1L8bU1j
ツイート 29
IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法 https://t.co/q04OO14yZf @asamuzakjp
ツイート 30
つーか、何度もgetComputedStyle取ってきてるから非効率だし負荷が高いorz asamuzaK.jp : ♪Vector Vector, please. Oh, the mess I'm in (c) UFO https://t.co/cZqExeogsj
ツイート 31
“asamuzaK.jp : IEのインラインSVGの固有サイズ表示をSVG1.1仕様に修正する方法” https://t.co/w6omLiPwPp
ツイート 32
IEで見たらバグってた原因はSVGのの高さを指定していないことだった。150pxとみなされるときはコレを疑えばよいのね。。。https://t.co/5eHTOrEaiS
ツイート 33
https://t.co/GJ3w9sJjIR
ツイート 34
コードで書いたインラインSVG、IEでサイズが正しく反映されず全部150pxになる問題。 このリンク先の修正スクリプトをライブラリJSとかに書き込んで一括で正しいサイズになった! https://t.co/xP89P1KDCk