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

🎯 タスク
このプロジェクトで学ぶことは以下の通りです。
- 画像クロッピングツールの HTML 構造を作成する方法
- 視覚的に魅力的にするために CSS を使ってウェブページをスタイリッシングする方法
- ユーザーインタラクションを処理するために JavaScript を使って変数とイベントリスナーを初期化する方法
- JavaScript の FileReader API を使って画像のアップロードと表示を処理する方法
- JavaScript の Canvas API を使ってクロッピングメカニズムを実装する方法
- クロップされた画像を保存して結果を表示する方法
🏆 成果
このプロジェクトを完了すると、以下のことができるようになります。
- HTML タグと構造を理解する
- CSS プロパティとセレクタを効果的に適用する
- JavaScript の構文、変数、イベントリスナーを活用する
- JavaScript の FileReader API を使ってファイルアップロードを処理する
- JavaScript の Canvas API を使って画像操作を実装する
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.pxとt.pyは、リアルタイムプレビューエリアにおける背景画像の座標を表します。t.sx、t.sy、t.sHeight、およびt.sWidthはそれぞれ画像の x、y 座標と幅、高さを表します。
また、後で操作するいくつかの要素もdocument.getElementByIdを通じて取得しています。
document
.getElementById("post_file")
.addEventListener("change", t.handleFiles, false);
idがpost_fileのinputフォームのchangeイベントをリッスンして、ユーザーがアップロードしたファイルを処理します。ここではこれをhandleFiles関数に委譲しています。ですから、次にhandleFiles関数を実装します。
画像のアップロードと表示を処理する
要件:
- JavaScript の FileReader API の基本的な知識。
機能:
- ユーザーが画像をアップロードしたとき、それが正しく処理され、読み取られ、画面に表示されることを確認する。
アップロードされた画像を処理して表示する関数でmain.jsを拡張する。
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);
};
},
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に関連するメソッドを追加する。
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";
},
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.sxとt.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の下のimgのsrc属性に割り当てます。このようにして、画像のクロッピングと保存が完了します。
ツールをテストする
- ウェブブラウザで
index.htmlを開く。
- 画像をアップロードして、クロッピング機能をテストする。
- ページの表示は以下の通りである。

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



