このブログではSVGオブジェクトをよく取り上げてきました。いわゆるペイントのようなソフトなしで図形を描けるので、とても便利です。しかし、不便なこともあります。その一つが重なり順序の統制の問題です。
SVGオブジェクトの重なり順
単一のSVGオブジェクト上に描かれた図形や画像の重なり順は、描き出した順番と完全に一致します。つまり、後に書いたものほど手前に来ます。SVGオブジェクト自体は純粋な2Dの概念で出来ているようなので、奥行きのような属性がありません。なので、少なくとも現状では、一度書きだした複数の図形の重なり順を後から変更できません。これが非常に不便な場合があります。例えば、D3.jsでたくさんの図形を一度に書きだした後で、transition()による操作を加えた後から、SVGオブジェクト上の背景画像を入れ替えるときを考えてみます。この場合、背景画像を入れ替えたつもりが、背景画像が最前列に来てしまうため、最初に書きだしておいた図形が隠れてしまいます。もう一度画面上に書きだすのでは、デバイスの描画の負担をむやみに上げてしまうような気がします。
解消策:SVGではなくDIVの奥行きを利用する
この問題に関する解消策をいろいろ考えてみましたが、結局、SVGの操作のみではどうにもなりません。そこで、奥行きの概念を持っているDIV要素を利用することにしました。
例題:3つの円の重なり順を次々に入れ替えてみよう
では例題として、3つの円を書きだして、ボタンを押すごとに重なり順を変えるようなデモを作ってみます。
CSSにDIVのstyleを定義しておく
3つのDIV要素の重なり順をz-indexで区別させます。ポイントは2つです。1つは、z-indexが大きくなるほど前景に来ること。それに、3つのDIV要素の位置を完全に一致させ置くことです。
div.front { position: absolute; z-index: 3; top: 50px; left:10px; width:600px; height:480px; } div.middle{ position: absolute; z-index: 2; top: 50px; left:10px; width:600px; height:480px; } div.back { position: absolute; z-index: 1; top: 50px; left:10px; width:600px; height:480px; }
HTMLでDIV要素とボタンを定義を作成する
DIV要素のクラスはあえてここに書きこまないでおきます。
<div id ="div1"> </div> <div id ="div2"> </div> <div id = "div3"> </div> <button type = "button" id = "ordershift" onclick = "shiftorder(event);"> Order Change </button>
JavaScriptで円を描き、重なり順を変える関数を作成する。
円の作成と重なり順の変更にはD3.jsを利用します。重なり順を動的に変更できるように、重なり順のクラス名のリストを作成し、ボタンを押すごとに、3つのDIV要素のクラスが変更されます。
重なり順の変更にはtransition()を利用しています。
var orderlist = ["back","middle","front"]; var order1 = 0; var order2 = 1; var order3 = 2; var diff = 0; var svg1 = d3.select("#div1").attr("class",orderlist[(order1 + diff)%3]).append("svg").attr("width",600).attr("height",480); var svg2 = d3.select("#div2").attr("class",orderlist[(order2 + diff)%3]).append("svg").attr("width",600).attr("height",480); var svg3 = d3.select("#div3").attr("class",orderlist[(order3 + diff)%3]).append("svg").attr("width",600).attr("height",480); var circle1 = svg1.append("circle").attr("cx",300).attr("cy",140).attr("r",100).attr("fill","blue").attr("stroke","orange").style("stroke-width","5px"); var circle2 = svg2.append("circle").attr("cx",250).attr("cy",240).attr("r",100).attr("fill","lightgreen").attr("stroke","pink").style("stroke-width","5px"); var circle3 = svg3.append("circle").attr("cx",350).attr("cy",240).attr("r",100).attr("fill","red").attr("stroke","lightblue").style("stroke-width","5px"); function shiftorder(event) { diff += 1; d3.select("#div1").transition().delay(0).duration(2000).attr("class",orderlist[(order1 + diff)%3]); d3.select("#div2").transition().delay(0).duration(2000).attr("class",orderlist[(order2 + diff)%3]); d3.select("#div3").transition().delay(0).duration(2000).attr("class",orderlist[(order3 + diff)%3]); }
ここまでをまとめるとこうなります。
もしかしたら将来的にはSVGに奥行きの概念が加わるかもしれませんが、今のところ、このやり方が最も簡単そうです。冒頭で述べたような背景画像を使用する場合は、常に後景のDIV要素上に画像を張り付ければいいでしょう。