HTML5 を使って画像クロッピングツールを作成する

JavaScriptJavaScriptBeginner
今すぐ練習

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

このプロジェクトでは、簡単な画像クロッピングツールを作成するプロセスを案内します。最後まで行えば、ユーザーが画像をアップロード、表示、クロップできる対話型アプリケーションが完成します。

👀 プレビュー

画像クロッピングツールのデモ

🎯 タスク

このプロジェクトで学ぶことは以下の通りです。

  • 画像クロッピングツールのHTML構造を作成する方法
  • 視覚的に魅力的にするためにCSSを使ってウェブページをスタイリッシングする方法
  • ユーザーインタラクションを処理するためにJavaScriptを使って変数とイベントリスナーを初期化する方法
  • JavaScriptのFileReader APIを使って画像のアップロードと表示を処理する方法
  • JavaScriptのCanvas APIを使ってクロッピングメカニズムを実装する方法
  • クロップされた画像を保存して結果を表示する方法

🏆 成果

このプロジェクトを完了すると、以下のことができるようになります。

  • HTMLタグと構造を理解する
  • CSSプロパティとセレクタを効果的に適用する
  • JavaScriptの構文、変数、イベントリスナーを活用する
  • JavaScriptのFileReader APIを使ってファイルアップロードを処理する
  • JavaScriptのCanvas APIを使って画像操作を実装する

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL javascript(("JavaScript")) -.-> javascript/BasicConceptsGroup(["Basic Concepts"]) javascript(("JavaScript")) -.-> javascript/DOMManipulationGroup(["DOM Manipulation"]) javascript/BasicConceptsGroup -.-> javascript/variables("Variables") javascript/DOMManipulationGroup -.-> javascript/dom_select("DOM Selection") javascript/DOMManipulationGroup -.-> javascript/dom_manip("DOM Manipulation") javascript/DOMManipulationGroup -.-> javascript/event_handle("Event Handling") subgraph Lab Skills javascript/variables -.-> lab-445698{{"HTML5 を使って画像クロッピングツールを作成する"}} javascript/dom_select -.-> lab-445698{{"HTML5 を使って画像クロッピングツールを作成する"}} javascript/dom_manip -.-> lab-445698{{"HTML5 を使って画像クロッピングツールを作成する"}} javascript/event_handle -.-> lab-445698{{"HTML5 を使って画像クロッピングツールを作成する"}} end

HTML構造をレイアウトする

要件

  • HTMLタグと構造に関する知識。

機能

  • ユーザーが画像をアップロードしてクロッピングプロセスをトリガーできるようなインターフェイスをデザインする。

HTMLコードをindex.htmlに埋め込む。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>HTML5 Crop Image</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <input type="file" name="file" id="post_file" />

    <button id="save_button">SAVE</button>
    <div id="label">
      <canvas id="get_image"></canvas>
      <p>
        <canvas id="cover_box"></canvas>
        <canvas id="edit_pic"></canvas>
      </p>
    </div>
    <p>
      <span id="show_edit"></span>
      <span id="show_pic"><img src="" /></span>
    </p>
    <script type="text/javascript" src="main.js"></script>
  </body>
</html>

上記の3つの<canvas>タグは、画像に関連するコンテンツの処理に使用されます。詳細な処理は、後続のjs(JavaScript)コードで提供されます。idがshow_editとidがshow_picの要素は、画像のプレビューと最終的な画像生成結果の表示に使用されます。

✨ 解答を確認して練習

ウェブページをスタイリッシングする

要件

  • CSSプロパティとセレクタに精通していること。

機能

  • HTML要素をスタイリッシングして、インターフェイスをユーザーにやさしく、視覚的に魅力的にする。

CSSをstyle.cssに埋め込む。

body {
  background-color: #f6f6f6;
  margin: 0;
  padding: 20px;
  text-align: center;
}

#label {
  border: 1px solid #ccc;
  background-color: #fff;
  text-align: center;
  height: 300px;
  width: 300px;
  margin: 20px auto;
  position: relative;
}

#get_image {
  position: absolute;
}

#edit_pic {
  position: absolute;
  display: none;
  background: #000;
}

#cover_box {
  position: absolute;
  z-index: 9999;
  display: none;
  top: 0px;
  left: 0px;
}

#show_edit {
  margin: 0 auto;
  display: inline-block;
}

#show_pic {
  height: 100px;
  width: 100px;
  border: 2px solid #000;
  overflow: hidden;
  margin: 0 auto;
  display: inline-block;
}

canvas {
  position: absolute;
  top: 0;
  left: 0;
}

#save_button {
  padding: 8px 16px;
  background-color: #3498db;
  color: #fff;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.2s;
}

#save_button:hover {
  background-color: #2980b9;
}

input[type="file"] {
  margin-bottom: 20px;
}
✨ 解答を確認して練習

変数とイベントリスナーを初期化する

要件

  • JavaScriptの構文、変数、イベントリスナーの基本的な理解。

機能

  • クロッピングツールのプロパティと設定を初期化する。アップロードされた画像を処理するためのイベントリスナーを追加する。

main.jsに、クロッピングツールのプロパティを初期化し、イベントリスナーを設定するコードを書く。

var postFile = {
  init: function () {
    var t = this;
    t.regional = document.getElementById("label");
    t.getImage = document.getElementById("get_image");
    t.editPic = document.getElementById("edit_pic");
    t.editBox = document.getElementById("cover_box");
    t.px = 0; //background image x
    t.py = 0; //background image y
    t.sx = 15; //crop area x
    t.sy = 15; //crop area y
    t.sHeight = 150; //crop area height
    t.sWidth = 150; //crop area width
    document
      .getElementById("post_file")
      .addEventListener("change", t.handleFiles, false);
  }
};

私たちのすべての関数と変数はpostFileオブジェクト内にカプセル化されています。上記のinit関数は主にいくつかの初期値を設定します。

t.px = 0;
t.py = 0;
t.sx = 15;
t.sy = 15;
t.sHeight = 100;
t.sWidth = 100;

変数t.pxt.pyは、リアルタイムプレビューエリアにおける背景画像の座標を表します。t.sxt.syt.sHeight、およびt.sWidthはそれぞれ画像のx、y座標と幅、高さを表します。

また、後で操作するいくつかの要素もdocument.getElementByIdを通じて取得しています。

document
  .getElementById("post_file")
  .addEventListener("change", t.handleFiles, false);

idpost_fileinputフォームのchangeイベントをリッスンして、ユーザーがアップロードしたファイルを処理します。ここではこれをhandleFiles関数に委譲しています。ですから、次にhandleFiles関数を実装します。

✨ 解答を確認して練習

画像のアップロードと表示を処理する

要件

  • JavaScriptのFileReader APIの基本的な知識。

機能

  • ユーザーが画像をアップロードしたとき、それが正しく処理され、読み取られ、画面に表示されることを確認する。

アップロードされた画像を処理して表示する関数でmain.jsを拡張する。

  1. handleFiles関数を実装する

ここではHTML5のFile APIを使用しています。まず、new FileReader()を呼び出すことで、oFReaderという名前のFileReaderオブジェクトをインスタンス化します。次に、そのreadAsDataURL()メソッドを呼び出して、ファイルコンテンツを読み取り、base64エンコード形式に変換します。

最後に、ファイルが完全に読み取られてロードされたとき、読み取った画像をpostFile.paintImage(oFREvent.target.result)を使って処理します。簡単に言えば、読み取った画像データをブラウザに再描画しています。

handleFiles: function () {
        var fileList = this.files[0];
        var oFReader = new FileReader();
        oFReader.readAsDataURL(fileList);
        oFReader.onload = function (oFREvent) {
            postFile.paintImage(oFREvent.target.result);
        };
},
  1. paintImage関数を実装する

ここで最も重要なステップは、canvasを使ってコンテナのサイズに応じて画像を描画することです。前のステップでは、File APIのFileReaderを使って、アップロードしたい画像のURL(oFREvent.target.resultの値)を取得しました。次のステップは、canvasを使ってこの画像を描画することです。まず、getImage.getContextを使って<canvas id="get_image"></canvas>の2dコンテンツを取得します。これは簡単に言えば画像コンテンツです。その後、new Image()を使って<img>要素を作成し、そのsrc属性値を設定します。

paintImage: function (url) {
        var t = this;
        var createCanvas = t.getImage.getContext("2d");
        var img = new Image();
        img.src = url;
        img.onload = function () {
            if (
                img.width < t.regional.offsetWidth &&
                img.height < t.regional.offsetHeight
            ) {
                t.imgWidth = img.width;
                t.imgHeight = img.height;
            } else {
                var pWidth = img.width / (img.height / t.regional.offsetHeight);
                var pHeight = img.height / (img.width / t.regional.offsetWidth);
                t.imgWidth = img.width > img.height? t.regional.offsetWidth : pWidth;
                t.imgHeight =
                    img.height > img.width? t.regional.offsetHeight : pHeight;
            }
            t.px = (t.regional.offsetWidth - t.imgWidth) / 2 + "px";
            t.py = (t.regional.offsetHeight - t.imgHeight) / 2 + "px";

            t.getImage.height = t.imgHeight;
            t.getImage.width = t.imgWidth;
            t.getImage.style.left = t.px;
            t.getImage.style.top = t.py;

            createCanvas.drawImage(img, 0, 0, t.imgWidth, t.imgHeight);
            t.imgUrl = t.getImage.toDataURL();
            t.cutImage();
            t.drag();
        };
},

img.onload関数内では、画像を元のサイズで比率を維持して再描画することが主な目的です。そのためにif条件があります。最終的に、画像を実際に描画するのはcreateCanvas.drawImage(img,0,0,t.imgWidth,t.imgHeight);というコード行です。

✨ 解答を確認して練習

クロッピングメカニズムを実装する

要件

  • JavaScriptのCanvas APIを使った描画と画像操作に精通していること。

機能

  • 表示された画像にクロッピング領域を追加し、この領域をドラッグ可能にする。これにより、ユーザーはクロッピングするための希望の領域を選択する柔軟性が得られる。

main.jsに関連するメソッドを追加する。

  1. cutImageメソッドを作成する

cutImageメソッドは主に2つのタスクを担当します。1つはマスク層を作成することで、もう1つはCSSのbackgroundプロパティを使って選択されたクロッピング領域のリアルタイムプレビューを提供することです。

cutImage: function () {
        var t = this;

        t.editBox.height = t.imgHeight;
        t.editBox.width = t.imgWidth;
        t.editBox.style.display = "block";
        t.editBox.style.left = t.px;
        t.editBox.style.top = t.py;

        var cover = t.editBox.getContext("2d");
        cover.fillStyle = "rgba(0, 0, 0, 0.5)";
        cover.fillRect(0, 0, t.imgWidth, t.imgHeight);
        cover.clearRect(t.sx, t.sy, t.sHeight, t.sWidth);

        document.getElementById("show_edit").style.background =
            "url(" + t.imgUrl + ")" + -t.sx + "px " + -t.sy + "px no-repeat";
        document.getElementById("show_edit").style.height = t.sHeight + "px";
        document.getElementById("show_edit").style.width = t.sWidth + "px";
},
  1. dragメソッドを作成する
drag: function () {
        var t = this;
        var draging = false;
        var startX = 0;
        var startY = 0;

        document.getElementById("cover_box").onmousemove = function (e) {
            var pageX = e.pageX - (t.regional.offsetLeft + this.offsetLeft);
            var pageY = e.pageY - (t.regional.offsetTop + this.offsetTop);

            if (
                pageX > t.sx &&
                pageX < t.sx + t.sWidth &&
                pageY > t.sy &&
                pageY < t.sy + t.sHeight
            ) {
                this.style.cursor = "move";

                this.onmousedown = function () {
                    draging = true;

                    t.ex = t.sx;
                    t.ey = t.sy;

                    startX = e.pageX - (t.regional.offsetLeft + this.offsetLeft);
                    startY = e.pageY - (t.regional.offsetTop + this.offsetTop);
                };
                window.onmouseup = function () {
                    draging = false;
                };

                if (draging) {
                    if (t.ex + (pageX - startX) < 0) {
                        t.sx = 0;
                    } else if (t.ex + (pageX - startX) + t.sWidth > t.imgWidth) {
                        t.sx = t.imgWidth - t.sWidth;
                    } else {
                        t.sx = t.ex + (pageX - startX);
                    }

                    if (t.ey + (pageY - startY) < 0) {
                        t.sy = 0;
                    } else if (t.ey + (pageY - startY) + t.sHeight > t.imgHeight) {
                        t.sy = t.imgHeight - t.sHeight;
                    } else {
                        t.sy = t.ey + (pageY - startY);
                    }

                    t.cutImage();
                }
            } else {
                this.style.cursor = "auto";
            }
        };
},

このメソッドを理解するには、以下のポイントを把握する必要があります。

var pageX = e.pageX - (t.regional.offsetLeft + this.offsetLeft);
var pageY = e.pageY - (t.regional.offsetTop + this.offsetTop);

上記の2行のコードでは、マウスと背景画像の間の距離を取得しています。e.pageXはマウスからブラウザの左エッジまでの距離を表し、t.regional.offsetLeft + this.offsetLeftは画像からブラウザの左エッジまでの距離を計算します。同様に、上の距離も導き出せます。

 if ( pageX > t.sx && pageX < t.sx + t.sWidth && pageY > t.sy && pageY < t.sy + t.sHeight )

マウスと背景画像の間の距離を理解していれば、これは簡単に把握できます。これは、マウスが画像の領域内にあるかどうかを判断します。

t.ex = t.sx;
t.ey = t.sy;

startX = e.pageX - (t.regional.offsetLeft + this.offsetLeft);
startY = e.pageY - (t.regional.offsetTop + this.offsetTop);

これらの2つのコードスニペットは注目に値します。最初の2行は、前回のスクリーンショットの座標(前回なかった場合は初期座標)を記録します。次の2行は、マウスが押されたときの座標を記録します。console.log()を使って個別にこれらの値を確認することができます。

if (draging) {
  if (t.ex + (pageX - startX) < 0) {
    t.sx = 0;
  } else if (t.ex + (pageX - startX) + t.sWidth > t.imgWidth) {
    t.sx = t.imgWidth - t.sWidth;
  } else {
    t.sx = t.ex + (pageX - startX);
  }

  if (t.ey + (pageY - startY) < 0) {
    t.sy = 0;
  } else if (t.ey + (pageY - startY) + t.sHeight > t.imgHeight) {
    t.sy = t.imgHeight - t.sHeight;
  } else {
    t.sy = t.ey + (pageY - startY);
  }

  t.cutImage();
}

上記のコードは基本的に次のように言っています。ドラッグ中は、座標の変化に基づいてt.sxt.syの値をリアルタイムで更新し、cutImageメソッドを呼び出してリアルタイムプレビューを提供する必要があります。

移動中のクロッピング領域の座標 = 前回記録された位置 + (現在のマウス位置 - マウスが押されたときの位置)

✨ 解答を確認して練習

クロッピングされた画像を保存する

要件

  • canvasを使って画像データを抽出して表示する方法に関する知識。

機能

  • 希望の画像領域をクロッピングした後、ユーザーにこのクロッピングされた領域を保存し、画面に結果を表示させる。

main.jsの初期化関数を拡張して、クロッピングされた画像の保存を処理する。

var postFile = {
  init: function () {
    //...
    document.getElementById("save_button").onclick = function () {
      t.editPic.height = t.sHeight;
      t.editPic.width = t.sWidth;
      var ctx = t.editPic.getContext("2d");
      var images = new Image();
      images.src = t.imgUrl;

      images.onload = function () {
        ctx.drawImage(
          images,
          t.sx,
          t.sy,
          t.sHeight,
          t.sWidth,
          0,
          0,
          t.sHeight,
          t.sWidth
        );
        document.getElementById("show_pic").getElementsByTagName("img")[0].src =
          t.editPic.toDataURL();
      };
    };
  }
};
postFile.init();

paintImageメソッドの実装と同様に、まず保存ボタンのクリックイベントをリスンします。次に、drawImageメソッドを使って選択された画像領域を描画します。最後に、toDataURLメソッドを使って画像をbase64エンコード形式に変換します。この値を次に、show_picの下のimgsrc属性に割り当てます。このようにして、画像のクロッピングと保存が完了します。

✨ 解答を確認して練習

ツールをテストする

  • ウェブブラウザでindex.htmlを開く。
    open web
  • 画像をアップロードして、クロッピング機能をテストする。
  • ページの表示は以下の通りである。
    image cropping tool demo
✨ 解答を確認して練習

まとめ

おめでとうございます!HTML5とJavaScriptを使って基本的な画像クロッピングツールを構築しました。この基礎を使って、より高度な機能や他の画像操作タスクを実現することができます。ツールを拡張したり、他のウェブ開発プロジェクトを探って練習してみましょう!