銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

SVGの重なり順序を操作する「svg-z-order」

f:id:takamints:20170423172653p:plain

JavaScriptからSVG要素の重なり順(Z-Order)を操作するモジュールのご紹介。

SVG要素には、HTMLに使えるz-indexスタイルが効きません。 なので、重なり順を変更するには、単純に要素を並べ替える必要があります。

コードからDOM要素を並べ替えるにはNodeクラスinsertBeforeメソッドが使えますNodeクラスはSVG要素SVGElementの基本クラスですから、SVG要素でも使えます。

ところで、SVG要素にz-indexが使えないことを「SVGの致命的な欠陥」と言ってる人がいますが、さすがにそれは言い過ぎです。SVGは単体で存在できる画像ファイルであってHTMLとの直接的なつながりはありません。単体のSVGファイル中で図形の重なり順序をz-indexで定義されていると、画像としてどのように表示されるか理解しにくいはずです。要素の順序で重なり順序が決定される方が、SVG的に「よりシンプル」なんですね。

さらに「HTMLのDIVにSVGを入れてz-indexで…」などというヤヤコシイ方法が検索で出てくるのですが、これは既にSVGではなくなっていますから、一般的な解決法とはいえないように思います。全体のスケールを変えるとかViewportを移動したいとかいったシーンで困りそう。

というか、DOM要素の並べ替え自体は前述のように結構簡単なんですが。。。

しかし、ベタにやると結構コードが煩雑になるんですよね。 ということで、この(↓)モジュールを作りました。以降、このモジュールを使ったサンプルやAPIの説明です。

www.npmjs.com

とりあえずは動くサンプル

以下は、このモジュールを使ったサンプルです。コードは下の方に掲載してます。 3つの円をクリックすると、その要素にドロップダウンで選ばれているメソッドを適用します。

使い方

このモジュールを使って、特定要素を最前面へ持ってくるコードを少し冗長に書いてみました。

//モジュール取得
var svgz = require("svg-z-order");// (1)

//SVG要素 g#foo を最前面へ表示
var g = svg.getElementById("foo");
var svgzG = svgz.element(g); //(2)
svgzG.toTop(); //(3)
 
// D3.jsで使う(4)
var d3 = require("d3");
var d3g = d3.select("#foo");
svgz.element(d3g.node()).toTop();
  1. モジュールをインポートして、
  2. element メソッドで、DOM要素を参照するインスタンスを作り
  3. 最前面へ表示します。
  4. D3.jsを使っていますが、例として書いているだけで、依存関係はありません。

上の例では、Browserifyを使って、 var svgz = require("svg-z-orger");でモジュールを取り込んでいますが、 HTMLで<script src="svg-z-order.js"/>としている場合は、 (2)の行の svgz.element(g) を、 svgz_element(g) に変更して下さい。 requireできない環境ではグローバルスコープに定義します。

API

element(e:Element)

SVG要素を参照する SVGZElement クラスのインスタンスを返します。 Z-Orderを操作するAPIは SVGZElementクラスのメソッドです。

パラメータ

  • e:Element - 参照するDOM要素を指定します。

SVGZElementクラス

SVGZElement.toTop()

参照している要素を最前面に移動。

SVGZElement.toBottom()

参照している要素を最背面に移動。

SVGZElement.moveUp(e:Element / n:number)

参照している要素を上(前面)へ移動。

パラメータ

  • e:Element - この要素よりも上になるように移動します。
  • n:number - 要素の並びの中でこの回数分だけ上へ移動します。

要素を指定したときはその要素よりも上へ。数値指定した場合は、その回数だけ上へ移動。

SVGZElement.moveDown(e:Element / n:number)

参照している要素を下(背面)へ移動。

パラメータ

  • e:Element - この要素よりも下になるように移動します。
  • n:number - 要素の並びの中でこの回数分だけ下へ移動します。

要素を指定したときはその要素よりも下へ。数値指定の場合は、その回数だけ下へ移動。

サンプルコード

一番上にあるサンプルのコードです。

/sample/web/index.js

リポジトリ

www.npmjs.com github.com

いくつになってもお勉強

JavaScriptのプログラムからSVG要素を最前面へもってくる方法を検索したら、結構微妙な情報が出てきたので少しインターネッツに失望しました。

曰く、

  1. SVG単体では不可能なので、HTMLのDIVにSVGを書いて重ね合わせて、DIVのz-indexで制御するしかありません。
  2. 対象のSVG要素をディープコピーして appendChild 。元要素は removeChild。

こういう情報を見て、当初「エラいヤヤコシイな」と思いましたが、どうにも納得がいかなくて、いろいろやってみているとDOMの標準機能で可能だと判明。 当初「複製作って、追加して、元のを消す」という処理手順をイメージしていましたが、移動できますね。 Node.appendChildNode.insertBeforeは、既にDOMツリーにある要素は移動するのですが、メソッド名を見る限りではそう思えないところが落とし穴かも。

いくつになってもお勉強です。

Node.js / npm 関連記事

takamints.hatenablog.jp takamints.hatenablog.jp takamints.hatenablog.jp takamints.hatenablog.jp takamints.hatenablog.jp

D3.js v4 でドラッグするには d3.drag() で behavior を取得する

f:id:takamints:20170418212231p:plain

D3.jsでドラッグイベントを処理する必要があったのですよ。

ほぼ初めてのD3ですからグーグル先生にいろいろ聞いて、「ほうほうなるほど」と学習していたのですけど、 ドラッグに関して各所で示されていたサンプル通りにやってみたら、まさかのエラー。

結局は、大きな問題ではありませんで、ひと言で言ってしまえばバージョン違いだったのですが、どうにも日本語の情報が少ないようなので書いておきます。

英語版です。v4に関して日本語の書籍が見当たらないです。

D3.js/v3でのコード

「D3.js ドラッグ」などと検索して出てくるサンプルコードの多くが、D3.jsのv3(バージョン3)のために書かれたコードで、最新のv4では動かないんですね。

v3でドラッグするには、d3.behavior.drag()で、Drag Behavior を取得するのですが、v4ではこれが動かない。

// D3.js v3でDrag Behaviorを取得

var drag = d3.behavior.drag(); //V4ではエラー

D3.js/v4でのOKコード

じゃあどうやるんだって―と、v4以降では以下のように、d3.drag()とするのだそうだ。

// D3.js/V4 でDrag Behaviorを取得

var drag = d3.drag(); // これでOK

リンク

リポジトリCHANGES にはしっかり書いてありました。

CHANGES
github.com

Dragに関する詳細は以下に
github.com

いやしかし

メジャーバージョンが上がっているので、APIに互換性がなくなってても文句は言わない約束だけど、 レガシーコードには警告を出すとかって対応があればモアベターね。

JavaScriptのラムダ式(アロー関数)は丸括弧で括らなければ即時実行できませんのね

f:id:takamints:20170328172435p:plain
Link: Flickr PAGE - CC BY 2.0

Node.jsで以下のようにラムダ式を即時実行していたのですが、ブラウザでは構文エラーとなって動かないんです。

(()=>{
    console.log("これ動きません");
}());

まさかコレが動かないとか思いもよらず。 どう見直しても問題があるとは思えなかったのだが動かないから仕方がない。

追記(2017-03-29)JavaScriptの言葉的には「ラムダ式」ではなく「アロー関数」のようですので、タイトルにのみ追記しました。

色々やってみた結果、ブラウザで動作させるには、以下のようにラムダ式全体を丸括弧で括る必要があるようですね。 しかし、なんだかカッコが多すぎ。ラムダ的に台無し感がありますなー。

((()=>{
    console.log("これでヨシ。だがしかし・・・");
})());

丸括弧なしでは関数オブジェクトとして評価されていないようです。 アロー演算子=>)の優先度の問題かな?

そもそも、どちらが正しいのかわからなかったので、調べてみると、どうやらECMAScript6の仕様のバグだそうで、 Node.jsでは便利な様に解釈して実装されているのかも知れませんが、今の所ブラウザではどうにもならないようですね。

d.hatena.ne.jp

即時実行するときはラムダ式を使わないというのが安全・安心? 関数内のthisが定義時にバインドされちゃうようなので、単なる無名関数の構文糖として使うのはマズいらしいし・・・