タッチ操作に対応した画像ビューワーをJavaScriptで作るならD3.jsが便利

スマホやタブレットで写真を表示していると、ピンチでズームしたり、ドラッグで移動したりができて便利なので、あれを Web 上で実現してみたくなった。

最近のブラウザーでは touchstarttouchmove イベントでタッチ情報を取れたり、イベントの touches でマルチタッチを扱えたりするので、実現するための基盤はそろっている。

適当なライブラリーがあるかと思って探してみたが、意外と苦労してしまった。

Hammer.js が使えない

タッチを扱うためのライブラリーとしては Hammer.js がメジャーらしい。スワイプ・ピンチ・ドラッグなど、各種イベントにも対応していて、これを使えば一発解決してくれそうだ。

ところが、画像ビューワーを作るには不向きだった。困ったのは次の 2 点。

  • ピンチやドラッグは個別には動くが、組み合わせたときに「表示位置」と「倍率」の関係を自前で計算する必要がある
  • 人差し指でドラッグしながら親指を追加してピンチすると、ピンチイベントは来るもののスケールがいい加減 (ドラッグ開始位置を基準に算出している気配)

いちおう公式のサンプルに pinchzoom.html というものはあるんだけど、動かしてみると分かる通り、毎回、移動の初期位置は初期化される。

コードも読んでみたが、複雑なものを作るには少し不向きである予感がした。

D3.js の d3.behavior.zoom で万事解決!

自前で実装するにしても難しそうだし、しばらく途方に暮れていたのだけど、D3.jsZoomable Geography のサンプルを見たら、見事に希望の動作を実現していた。

ドラッグ中にピンチしてもきれいに動いている。マウスのドラッグやホイールにも対応してたり、ダブルクリック・ダブルタップでの拡大もしてくれる。Nexus 7 と Windows 8 + Google Chrome で動作することを確認した。すばらしい。

しかもサンプルのコードが超絶短い。どうやら、D3.js の d3.behavior.zoom() がタッチ操作を全部面倒みてくれるようだ。

画像ファイルでやってみた

そこで、試しに自分も作ってみた。本家のサンプルは SVG だけども、こっちのサンプルは単なる画像を CSS3 の 2D Transform で動かしている。

↑をピンチで拡大縮小、ドラッグで移動できるよ。

まとめ

D3.js がタッチ操作に対応してるのは、たぶんビジュアライズ結果を拡大・移動したいからだろう。他にも同じようなことを実現するライブラリーはありそうなのだが、意外と見つからなかったので記事にしておく。

D3.js については D3.js の Data-Driven な DOM 操作がおもしろい もご覧あれ。そうそう、このエントリーは d3.js Advent Calendar の 13 日目の記事だったりもする。

ソースは以下に (40行)。2D Transform では transform-origin の設定が必要なところで少しはまった。

<!DOCTYPE html>
<meta charset="utf-8">
<style>
html,body {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: white;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var zoom = d3.behavior.zoom()
    .scale(1)
    .scaleExtent([.1, 10])
    .on("zoom", zoomed);

var svg = d3.select("body")
    .call(zoom);

function zoomed() {
    var t = "translate(" + d3.event.translate[0] + 'px,' + d3.event.translate[1] +"px) " +
        "scale(" + d3.event.scale + ',' + d3.event.scale + ")";
    d3.select("img")
        .style("transform-origin", "0 0")
        .style("-moz-transform-origin", "0 0")
        .style("-webkit-transform-origin", "0 0")
        .style("-o-transform-origin", "0 0")
        .style("-ms-transform-origin", "0 0")
        .style("transform", t)
        .style("-moz-transform", t)
        .style("-webkit-transform", t)
        .style("-o-transform", t)
        .style("-ms-transform", t);
}
</script>
<img src="/images/logo-blog.png">
</body>