続・先取り! Google Chrome Extensions

第7回 Google Chrome拡張とHTML5 #1

この記事を読むのに必要な時間:およそ 4 分

Chrome拡張でのドラッグ・アンド・ドロップ

ドラッグ・アンド・ドロップは従来のJavaScriptでも実現可能な機能ですが,IEの独自実装をもとにHTML5のAPIとして標準化が行われています。ドラッグ・アンド・ドロップの仕様はまだ固まっていないこともあり,Chromeでの実装も十分な状態ではありません。ですが,シンプルな使い方であれば十分に利用できますので,今回は簡単な例をいくつか紹介します。

ドラッグ・アンド・ドロップのサンプルHTML

<!DOCTYPE html>
<html>
<head>
<title>drag&amp;drop sample</title>
<meta charset="utf-8">
</head>
<body>
<img id="icon" src="icon.png" draggable="true">
<div id="drop"></div>
<script src="drag.js"></script>
</body>
</html>

HTMLはあまり特徴がありません。一点だけ,ドラッグしたい要素にdraggableという属性をつけ,その値をtrueとすることでその要素がドラッグ可能になります。ただ,実際にはリンク(a要素)や画像(img要素)などはdraggable属性のデフォルト値がtrueになっているので,この例のようにわざわざ明示する必要は実はありません(以前のバージョンのWebKitでは,draggable属性の代わりに独自拡張としてスタイルシートでドラッグ可能な要素であることを指定する(-khtml-user-drag: element; もしくは-webkit-user-drag: element;)必要がありましたが,Chrome拡張では標準仕様のdraggable属性が使えますのでそちらを使いましょう⁠⁠。

draggable属性でドラッグはできるようになっているので,後はドロップされる側を指定します。ドロップされる側では,dragenter,dragoverのイベントにおいてpreventDefaultを呼び出す必要があります(実際には,dragenterの処理は省略しても動くようです⁠⁠。

ドラッグアンドドロップのサンプルJavaScript

var icon = document.getElementById('icon');
var drop = document.getElementById('drop');
drop.addEventListener('dragenter', function(evt){
  evt.preventDefault();
}, false);
drop.addEventListener('dragover', function(evt){
  evt.preventDefault();
}, false);
drop.addEventListener('drop', function(evt){
  drop.appendChild(icon.cloneNode(false));
  evt.preventDefault();
}, false);

ドロップ対象の要素に対して,addEventListenerメソッドで dragenter(要素にドラッグ中のマウスが載った際に発生する)イベントと,dragover(要素にマウスが載っている間に定期的に発生する)イベントをセットして,そのイベント中にpreventDefaultを呼び出しています。このpreventDefaultを呼び出した際のtarget要素(上記の例では#dropな要素)にドロップすることが可能になります。これで,シンプルなドラッグアンドドロップができました。

なお,この例ではドロップ時にはドラッグした画像をcloneしてドロップ先に置くようにしました。もしcloneせずにappendChildすれば,元の画像がドロップ先に移動することになります。

上記の例ではドロップ後の画像もさらにドラッグ・アンド・ドロップできます。ここでドラッグ可能な要素を限定する方法を考えてみます。まずシンプルな答えは,先程のdraggable属性をfalseにすることです。が,現在のChromeではこの方法ではドラッグを止めることができません。

ドロップ時に再度ドラッグできないようにする

drop.addEventListener('drop', function(evt){
  var new_icon = icon.cloneNode(false);
  new_icon.draggable = false;
  drop.appendChild(new_icon);
  evt.preventDefault();
}, false);

上記の方法はFirefoxでは正常に動作します。本来ならChromeでも動作するはずなのですが,現状の実装が十分でないことは先述の通りです。では,代わりの方法を考えてみます。

ドラッグ可能な要素を限定する実装例

var icon = document.getElementById('icon');
var drop = document.getElementById('drop');
icon.addEventListener('dragstart', function(evt){
  evt.stopPropagation();
  evt.dataTransfer.setData('Text', 'original');
}, false);
drop.addEventListener('dragenter', function(evt){
  if (evt.dataTransfer.getData('Text') === 'original') {
    evt.preventDefault();
  }
}, false);
drop.addEventListener('dragover', function(evt){
  if (evt.dataTransfer.getData('Text') === 'original') {
    evt.preventDefault();
  }
}, false);
drop.addEventListener('drop', function(evt){
  drop.appendChild(icon.cloneNode(false));
  evt.preventDefault();
}, false);

dragstartイベントで指定の画像のドラッグを開始したタイミングを捉え,dataTransferにキーワードをセットしています。dragenter,dragoverの各イベントにおいて,指定のキーワードがセットされていたらpreventDefaultを呼び出すようにしました。これで指定の画像以外ではドロップが許可されないようになりました。

なお,dataTransferを使わずとも,draggingフラグを立てて,そのフラグが立っている時のみpreventDefaultを呼び出すという方法でも実現できます。

拡張への応用

第5回で作成したStart Tileにドラッグ・アンド・ドロップによるタブ操作を実装してみました。

図1 ドロップによるタブ操作

図1 ドロップによるタブ操作

3つのブロックの真ん中がブックマークシートになっており,各ブックマークの手前のグリップを掴んで左のタブシートにドロップするとそのブックマークをバックグラウンドのタブで開くことができます。

基本的な処理はシンプルで,まずドラッグの開始時にdataTransferにドラッグするブックマークのURLを記録します。

ドラッグ開始時にURLをセット

bookmark_list.addEventListener("dragstart", function(evt){
  evt.stopPropagation();
  evt.dataTransfer.setData("URL", evt.target.querySelector('a').href);
}, false);

ドラッグの最中は現在どこにドロップしようとしているのか,視覚的にわかりやすくなるように背景色を変えています(マウスが乗ったり降りたりを繰り返すので,この部分の処理は少々複雑になっています⁠⁠。基本的には,dragenterイベントで背景色を設定し,dragleaveイベントで背景色を元に戻す処理です。ちなみに,Firefoxでは-moz-drag-overという独自拡張の疑似クラスがあるため,この処理を簡単に記述できるようになっています。

ドロップ時の処理

tabs_area.addEventListener("drop", function(evt){
  var href = evt.dataTransfer.getData("URL");
  chrome.tabs.create({url:href, selected:false});
}, false);

ドロップが成功した場合,dataTransferからURLを受け取って新規タブに開きます。

Start Tileのソースはbitbucket.orgで確認できますので,よろしければご一読ください。

まとめ

今回はChrome拡張で使えるHTML5としてcanvasとドラッグ・アンド・ドロップを取り上げました。次回はHTML5周辺技術として,Web Storageの復習や,Web SQL database,ECMAScript 5実装などを取り上げる予定です。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/