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さんには感謝です。

ライブデモ

ここに貼り付けてください。
拡大率:
上カット:
下カット:
左カット:
右カット:
Your Browswe does not support canvas

2019/09/23 微調整しました