インラインSVGがボケる場合への対処

wittern by @DEFGHI1977.

インラインSVGがボケる原理

スタンドアロンのSVGでは描画結果をくっきりとさせるため, strokeプロパティに依る線の描画に細工を施す場合がある. 例を示す.

素朴な例 (1)素朴な例 0.5pxずらす例 (2)0.5pxずらす例 2px幅とする例 (3)2px幅とする例

上記の格子をスクリーンデバイスにおける1画素とし, 黄線をストローク(線)を引く基準ラインとする.

(1)では幅1pxの黒色のストロークを引こうとしている. この場合, SVGの仕様により描画内容が画素の境界を跨いでしまうが, 一般的なwebブラウザではこのようなケースに於いてグラフィックの内容を近似しようとする. そのため出力結果に本来の黒色よりも薄いグレーの部分が発生してしまうのだが, これがSVGがボケる原因である.

この問題を解決するには(2)のように基準となるラインを予め0.5pxずらしておくか, (3)のようにストローク幅を調整しグラフィックの内容が画素を跨がないようにする等の細工を行う必要がある.

しかしHTMLにおけるインラインSVGはグラフィック描画の基準を他のHTML要素のスタイルやテキストの有無に依存しており, フォントサイズや位置の%指定等によりSVGの描画基準が1px未満の大きさでスクリーン画素からずれる事がある. そのため先ほどの対処を施したにも関わらず2画素にまたがる描画が発生し, 結果グラフィックがボケることになる.

検証

下のSVGにはスタイルposition:relative;left:0.4px;top:0.4px;を施すことで強制的に半ピクセル分描画位置をずらしている. (borderによる描画はぼけていない点に注意)
そのため環境によって縦線がボケて描画されてしまうのだが, これをメジャーなブラウザ環境全てでくっきりと描画する方法を探る.

説明SVGFFCRIEOP備考
(0)未設定 ×× CRは何もせずともSVGの描画位置が微調整される.
(1)shape-rendering:crispEdges × 最も標準的な方法だが, 円が汚くなる上IEでは描画処理順が他と異なるため上手く行かない.
※IEを無視して良い場合は, 適用する要素を限定することで対処可能.
(2)transform:translate(0,0) × FFではなぜかCSS-transformを施すことで描画位置の調整がなされる.
(3)transform+ScreenCTMで微調整 ×× ScreenCTMを用いて1px未満のずれをtransformで吸収する方法. IEでのみ有効.
なおレイアウト変更時に再設定の必要がある.
FF/CRでは微調整が施された上で1px未満のスライドが発生するため上手く行かない.
(4)-ms-transformで微調整 × 上記の微調整をIEでのみ有効とするもの. FFではtransform未設定となるためボケてしまう.
(A)全対応版 translate(0,0)を施し, IEでのみ(4)の調整を行うもの. 全てのブラウザでキレイに描画されている.
※もしくは-moz-transform/-webkit-transformでtranslate(0,0)を指定する方法もあるか. 何れにせよベンダプレフィクスが廃止されてしまうと手の施しようがない.

実際のコード

ライブラリ化すると良いかも知れない. なおIEを無視して良いのであればsvg要素にCSSにてtransform:translate(0,0)を指定するだけで良い.

document.addEventListener("DOMContentLoaded", function(){
	var svg, m;
	//(1)
	svg = document.querySelector("tr:nth-child(2) svg");
	svg.style.shapeRendering = "crispEdges";
	//(2)
	svg = document.querySelector("tr:nth-child(3) svg");
	svg.style.transform = "translate(0,0)";
	//(3)
	svg = document.querySelector("tr:nth-child(4) svg");
	m = svg.getScreenCTM();
	svg.style.transform = "translate(" + (Math.floor(m.e)-m.e) + "px," + (Math.floor(m.f)-m.f) + "px)";
	//(4)
	svg = document.querySelector("tr:nth-child(5) svg");
	m = svg.getScreenCTM();
	svg.style.msTransform = "translate(" + (Math.floor(m.e)-m.e) + "px," + (Math.floor(m.f)-m.f) + "px)";
	//(A)
	svg = document.querySelector("tr:nth-child(6) svg");
	m = svg.getScreenCTM();
	svg.style.transform = "translate(0,0)";
	svg.style.msTransform = "translate(" + (Math.floor(m.e)-m.e) + "px," + (Math.floor(m.f)-m.f) + "px)";
}, false);