Webブラウザで、クリップボードから画像を拾ってトリミングする。
みなさん、contentEditableというHTMLの属性を知ってますか?
任意のDOMの中身を書き換えることが出来るようにするという凄い属性です。
キャレットなんかが全然言うこときかなかったり、なかなか使いこなすのは難しいのですが、これを使って「そんな無茶な…」って要件を解決したので、是非共有したいと思います。
ある社内FAQサイトの投稿画面、返信画面のUIの打ち合わせの場で…。
なるほど画像の添付ができるんですね。でもファイルに保存しないと駄目なの面倒じゃないですかぁ。ほとんどスクリーンショットなんですよ、コピペできません?
一気に言われましたね。「えぇ…そんな…聞いたことねぇ」と思いましたが、「確かに便利」とも同時に思いました。
調査
出来ないと言うにはやった事無さ過ぎだった(と言うか初めて言われた)要件だったので、まずクリップボード関係のAPIを当たったのですが、単純には取れず。
よく考えれば、JavaScriptからクリップボードの値をホイホイ取得できると、パスワード貼り付けてる時とかに死ねますもんね。
更にいろいろあたり、contentEditableにへ
そんなこんなで煮詰まっていたら、qiitaのtatesukeさんと言う方の、JavaScriptでクリップボードの画像を取得するという記事を発見しました。
そのままではうまく行かなかったものの、少し改変させて頂いたところこれでイケる! と確信し、アイデアほとんどお借りして解決に至りました。
僕なりの解説
まず、contentEditableなdivを用意して、貼り付けてもらいます。
その瞬間、具体的にはonpasteが発火した瞬間に、pasteされたデータがイベント引数に入っていれば、それから読み込みを行います。
それで取れないときは、img要素を探して中身を引きずり出します。
element.addEventListener("paste", function (e) { var _this = this; if (!e.clipboardData || !e.clipboardData.types || (e.clipboardData.types.length != 1) || (e.clipboardData.types[0] != "Files")) { setTimeout(function () { var temp = document.createElement("div"); temp.innerHTML = _this.innerHTML; var pastedImage = temp.querySelector("img"); if (pastedImage) { var base64 = pastedImage.src; document.querySelector("#outputImage").setAttribute('style',"display:block;") document.querySelector("#outputImage").src = base64; setTimeout(function () { orgHeight = document.querySelector("#outputImage").clientHeight; orgWidth = document.querySelector("#outputImage").clientWidth; document.querySelector("#outputImage").setAttribute('style',"display:none;") drawImage() }, 100); } _this.innerHTML = "ここに貼り付けてください。"; }, 100) return true; } var imageFile = e.clipboardData.items[0].getAsFile(); // FileReaderで読み込む var fr = new FileReader(); fr.onload = function (e) { var base64 = e.target.result; document.querySelector("#outputImage").setAttribute('style',"display:block;") document.querySelector("#outputImage").src = base64; setTimeout(function () { orgHeight = document.querySelector("#outputImage").clientHeight; orgWidth = document.querySelector("#outputImage").clientWidth; document.querySelector("#outputImage").setAttribute('style',"display:none;") drawImage() }, 100); }; fr.readAsDataURL(imageFile); this.innerHTML = "ここに貼り付けてください"; });
それでも取れなければ、onchangeの段階でcontentEditableの中身からImg要素を探し出し、ガサっと取っちゃいます。
element.addEventListener("change", function (e) { // エレメントのコピーをつくる var temp = document.createElement("div"); temp.innerHTML = this.innerHTML; var pastedImage = temp.querySelector("img"); if (pastedImage) { var base64 = pastedImage.src; document.querySelector("#outputImage").setAttribute('style',"display:block;") document.querySelector("#outputImage").src = base64; setTimeout(function () { orgHeight = document.querySelector("#outputImage").clientHeight; orgWidth = document.querySelector("#outputImage").clientWidth; document.querySelector("#outputImage").setAttribute('style',"display:none;") drawImage() }, 100); } // contenteditableの内容は戻しておく this.innerHTML = "ここに貼り付けてください"; })
この2点で、だいたいいろんなブラウザに対応できました。
あとは、drawImageでcanvasにトリミングしつつコピって、出力を作ってるだけです。
動くサンプル
下の枠に画像を貼り付けてみてください。
スクリーンショットでも良いですよ。
サーバには送信してないのでお気軽に試してみてください。
貼り付けると、一瞬上でごちゃっと動き、その下の枠に画像が読み込めるはずです。
これは、取り込んだ画像をcanvasに描画することによって表示してます。
canvasを経由する事により、dataURIで保存が可能になります。
この仕組みで、クリップボードから画像を直接貼り付けるという荒業が可能になり、これで、晴れて要件を満たすことができたというお話でした。
結構これ重宝してます。
実運用してるものは、トリミング機能に加えて、個人情報を墨塗りするツールがついてます。
QもAもインスタントに出せる便利ツールになって、僕的にはやったった感のある画面に仕上がりました。
改めてですが、tatesukeさんには感謝です。
ライブデモ
上カット:
下カット:
左カット:
右カット: