Two.jsにzindexが欲しい時の対処方法

Two.jsにzindexが欲しい時の対処方法:


Two.jsにはzindexが無い

Two.jsを使っていて困ることの1つにzindexが無い所です。

SVGを選択した場合はDOMを並び替えるメソッドがありますが、CanvasとWebGLの場合にはそれも無いので、何とかして対応する必要があります。

本記事では、以下のように場合を分けて考えます

  • SVGを選択した場合
  • CanvasまたはWebGLを選択した場合

    • 方針1:一度描画した順番を変更しない
    • 方針2:複数の描画エリアを重ねる
    • 方針3:再描画処理時にオブジェクトを再生成する
    • 方針4:汎用的なzindex機構を実装する
なお、方針4の汎用的なzindex機構はとても大変なので、今回は触れません。


SVGを選択した場合

Node.insertBeforeメソッドを使用してノードの重なり順をコントロールします。

引用

基本構文は以下の通りです。

var insertedElement = parentElement.insertBefore(newElement, referenceElement) 
parentElement : 挿入したい位置の親要素 
newElement : 挿入したい要素 ( 戻り値 insertedElement も同じ ) 
referenceElement : この要素の前に newElement が挿入される初期表示としてのサンプルコードです。 
【JavaScript】要素を追加するinsertBeforeとappendChildについて より。

このメソッドを用いる前準備としてのサンプルコードが以下の通りです。

<html> 
 
<head> 
    <script src="./two.js"></script> 
</head> 
 
<body> 
    <div id="draw-shapes" style="position:absolute;"> 
    </div> 
    <script> 
        var elem = document.getElementById('draw-shapes'); 
        var two = new Two({ width: 320, height: 300, type: Two.Types.svg }).appendTo(elem); 
 
        // 下から赤緑青の順で重なる円を作成 
        var circle1 = two.makeCircle(100, 100, 50); 
        circle1.fill = "red"; 
        var circle2 = two.makeCircle(120, 120, 50); 
        circle2.fill = "green"; 
        var circle3 = two.makeCircle(80, 120, 50); 
        circle3.fill = "blue"; 
 
        // DOM要素を生成するために一度update()する 
        // この状態では青が一番上に重なる 
        two.update(); 
    </script> 
</body> 
 
</html> 
実行結果


svgsample02.png


先程のNode.insertBeforeメソッドを用い、この画像の青と緑の順番を入れ替えるには次のように追記します。

// 緑円のDOMを取得 
var circleDomGreen = document.getElementById(circle2.id); 
// 青円のDOMを取得 
var circleDomBlue = document.getElementById(circle3.id); 
 
// 緑円の前に青円を挿入 
// DOMは先にあるほうから順に描画され重なったとき下になるので 
// 挿入前:赤緑青 
// 挿入後:赤青緑 
// これを表示すると緑の円が一番上に重なる 
circleDomGreen.parentNode.insertBefore(circleDomBlue, circleDomGreen); 
 
// update()をせずとも表示は変更されるし 
// update()してもDOMを操作した状態は保持される 
two.update(); 
実行結果


svgsample01.png



CanvasまたはWebGLを選択した場合


方針1:一度描画した順番を変更しない

変更しなくて済むのならば当然問題ありません。そいういう順番でtwo.makeCicle()でもtwo.makeCirve()でもしましょう。

場合によっては一時的に非表示をすることで要件を満たす事が出来るなら次のような対応方法もあります。


一時的な非表示方法

  • 要素のopacity=0として透明度をゼロにする
  • 要素を画面外に移動する
ですが、デメリットとしては


デメリット

  • やはり要素を上下したい場合はある
  • 要素が増えると動作が遅くなる

    • 特に描画方法にSVGを選択した場合は要素が増えるとすぐに遅くなりやすい
が考えられます。

そして、もちろんこの対策で全て解決するならば本記事は要りません。


方針2:複数の描画エリアを重ねる

複数の描画エリアを重ねて表示し、必要に応じて描画するエリアを切り替えます。

このサンプルでは三層のエリアに分けています。

<html> 
 
<head> 
    <script src="./two.js"></script> 
</head> 
<style> 
    #draw-shapes1 { 
        z-index: 3; 
        position:absolute; 
    } 
    #draw-shapes2 { 
        z-index: 2; 
        position:absolute; 
    } 
    #draw-shapes3 { 
        z-index: 4; 
        position:absolute; 
    } 
</style> 
 
<body> 
    <div id="draw-shapes1"> 
    </div> 
    <div id="draw-shapes2"> 
    </div> 
    <div id="draw-shapes3"> 
    </div> 
    <script> 
        var elem1 = document.getElementById('draw-shapes1'); 
        var elem2 = document.getElementById('draw-shapes2'); 
        var elem3 = document.getElementById('draw-shapes3'); 
 
        // typeは統一しておくと後でいいことがある 
        var two1 = new Two({ width: 320, height: 300, type:Two.Types.canvas}).appendTo(elem1); 
        var two2 = new Two({ width: 320, height: 300, type:Two.Types.canvas}).appendTo(elem2); 
        var two3 = new Two({ width: 320, height: 300, type:Two.Types.canvas}).appendTo(elem3); 
 
        var circleRed = two1.makeCircle(100, 100, 50); 
        circleRed.fill = "red"; 
        var circleGreen = two2.makeCircle(120, 120, 50); 
        circleGreen.fill = "green"; 
        var circleBlue = two3.makeCircle(80, 120, 50); 
        circleBlue.fill = "blue"; 
 
        two1.update(); 
        two2.update(); 
        two3.update(); 
    </script> 
</body> 
 
</html> 
実行結果


multicanvas02.png


この状態でcircleGreenをDOMの並びで一番後ろ = 画面上の一番上に移動したい場合は次のコードを追記します

// typeを統一しておくと、add/removeするだけでcircle2を削除・作成で作り直す事なく移動させることが出来る 
// circle2を作ったtwo2とtwo3のtypeが異なる場合、 
// add/removeではなく再度circle2をmakeCircle()し直す必要がある。 
two3.add(circleGreen); 
two2.remove(circleGreen); 
 
two2.update(); 
two3.update(); 
実行結果


multicanvas01.png



注意

typeをWebGL(Two.Types.webgl)にした場合、表示は意図通りされますが警告ログがコンソールに出力されます。

WebGL: INVALID_OPERATION: delete: object does not belong to this context  
WebGL: INVALID_OPERATION: delete: object does not belong to this context 
いずれも最後の

two3.update(); 
の行で出力されます。

警告を出ないようにするにはadd/removeで移動するのではなく、この場合ならばmakeCircle/removeでcircleGreenを作り直す必要があるようです。


方針3:再描画処理時にオブジェクトを再生成する

画面を再描画する際に既存のオブジェクトを廃棄し、表示順を考慮して表示要素を再生成します。

具体的には以下の様に実行します。

var circle1 = two.makeCircle(100, 100, 50); 
var circle2 = two.makeCircle(100, 100, 50); 
var circle3 = two.makeCircle(100, 100, 50); 
 
//一つ一つ廃棄する場合 
two.remove(circle1); 
two.remove(circle2); 
two.remove(circle3); 
 
//まとめて廃棄する場合 
two.clear(); 
先に生成したオブジェクトが画面上は下に隠れて表示され、後から生成したオブジェクトが画面上は上に表示されます。一般に移動より再生成の計算コストの方がより多くかかると思われるので(未測定)、その点に気をつける必要があります。


まとめ

  • SVGの場合

    • Node.insertBeforeメソッドを使用する
  • CanvasまたはWebGLの場合

    • 複数の描画エリアを重ねて重ね合わせを表現する
    • 再描画をする時に重なり合わせ順を考慮して再描画する
以上


参考

コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)