JavaScriptで画像ファイルをリサイズしたい時があると思います。僕はありました。
単にFileを投入して変換するコードが意外となかったのでここに共有します。
この例では、リサイズ後の画像をブラウザからダウンロードの形で取得できるようにしました。
DEMO
See the Pen GRJRrPW by hiroism (@jukaism) on CodePen.
Canvasを利用します
Canvasは図を表示するための仕様ですが、機能が豊富なので画像加工にも利用できます。
<body>
<canvas style="width: 484px; height: 253px;" id="canvas"></canvas>
</body>
画像をリサイズしたい幅と高さを指定しておきます。邪魔ならdisplay: none;
も可。
コード全体
htmlファイルにしてブラウザで開けば動くコードです。
スクリプト内の**resize(file)**にFileを投入するほか、
添付フィールドを用意したので、ここから画像を選んでも動作します。
<html lang="ja">
<body>
<input type="file" id="image_zone">
<br>
<canvas style="width: 484px; height: 253px;" id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script> <!-- IEで必要。不要なら削除を -->
<script>
// image_zoneに新しいファイルがセットされた時、resizeを呼ぶ
const imageZone = document.getElementById('image_zone')
imageZone.addEventListener('change', resizePinnedImage, false)
function resizePinnedImage(e) {
const file = e.target.files[0]
if (!file.type.match('image.*')) { return }
resize(file)
}
// Canvasに画像をリサイズして貼り、終わったらブラウザからダウンロードっぽく取得
function resize(file) {
imageToCanvas(file).then(function (canvas) {
// [5] キャンバスから画像を取得
if (canvas.msToBlob) {
window.navigator.msSaveBlob(canvas.msToBlob(), 'resized.png')
} else {
const a = document.createElement('a')
a.href = canvas.toDataURL(file.type)
a.download = 'resized.' + file.type.split('/')[1]
a.click()
}
}).catch(function (error) {
console.error(error)
})
}
function imageToCanvas (imageFile) {
// [1] Fileから画像データを得る
return new Promise(function (resolve, reject) {
readImage(imageFile).then(function (src) {
loadImage(src).then(function (image) {
// [2] Canvasの呼び出し
const canvas = document.getElementById("canvas")
const ctx = canvas.getContext('2d')
// [3] 縮尺を計算
const scale = Math.max((canvas.height / image.height), (canvas.width / image.width))
// [4] 画像を切り取り、Canvasに展開して返す
ctx.drawImage(
// Canvasに貼り付ける画像
image,
// 画像の切り取り開始位置(左上からの横距離、縦距離)
(image.width - (canvas.width / scale)) / 2, (image.height - (canvas.height / scale)) / 2,
// 画像の切り取り範囲(幅、高さ)
canvas.width / scale, canvas.height / scale,
// キャンバスの貼り付け開始位置(左上からの横距離、縦距離)
0, 0,
// 画像への貼り付け範囲(幅、高さ)
canvas.width, canvas.height
)
resolve(canvas)
}).catch(function (error) {
reject(error)
})
}).catch(function (error) {
reject(error)
})
})
}
// [1-A] Fileの中身を得る
function readImage(image) {
return new Promise(function (resolve, reject) {
const reader = new FileReader()
reader.onload = function () { resolve(reader.result) }
reader.onerror = function (e) { reject(e) }
reader.readAsDataURL(image)
})
}
// [1-B] 空の画像データを作った後に中身を投入
function loadImage(src) {
return new Promise(function (resolve, reject) {
const img = new Image()
img.onload = function () { resolve(img) }
img.onerror = function (e) { reject(e) }
img.src = src
})
}
</script>
</body>
</html>
[1] FileをImageにする
FileReaderでFileの中身を取り出し、空のImageに投入します。
このあたりでPromiseを多用しているためIE用のPolyfillを入れています。
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
// ..略
function imageToCanvas (imageFile) {
// [1] Fileから画像データを得る
return new Promise(function (resolve, reject) {
readImage(imageFile).then(function (src) {
loadImage(src).then(function (image) {
// ここに画像データ(image)が渡ってくるので処理
}).catch(function (error) {
reject(error)
})
}).catch(function (error) {
reject(error)
})
})
}
// [1-A] Fileの中身を得る
function readImage(image) {
return new Promise(function (resolve, reject) {
const reader = new FileReader()
reader.onload = function () { resolve(reader.result) }
reader.onerror = function (e) { reject(e) }
reader.readAsDataURL(image)
})
}
// [1-B] 空の画像データを作った後に中身を投入
function loadImage(src) {
return new Promise(function (resolve, reject) {
const img = new Image()
img.onload = function () { resolve(img) }
img.onerror = function (e) { reject(e) }
img.src = src
})
}
[2] HTML上のCanvasの呼び出し
キャンバスと、その描画用のコンテキストを呼び出します。
// [2] Canvasの呼び出し
const canvas = document.getElementById("canvas")
const ctx = canvas.getContext('2d')
[3] 縮尺の計算
アスペクト比を維持したいので、縦横どちらか大きい方の縮小率を採用します。
// [3] 縮尺を計算
const scale = Math.max((canvas.height / image.height), (canvas.width / image.width))
[4] 画像を切り取り、Canvasに展開
CanvasのメソッドdrawImage
を使って画像を貼り付け。
この時画像の切り取り範囲とCanvasへの貼付け範囲を一気に指定する。混乱しがち。
本当はここで画像にしたかったが、ブラウザによってこの後のデータの取り出し方が違うのでここではCanvasを返す。
// [4] 画像を切り取り、Canvasに展開して返す
ctx.drawImage(
// Canvasに貼り付ける画像
image,
// 画像の切り取り開始位置(左上からの横距離、縦距離)
(image.width - (canvas.width / scale)) / 2, (image.height - (canvas.height / scale)) / 2,
// 画像の切り取り範囲(幅、高さ)
canvas.width / scale, canvas.height / scale,
// キャンバスの貼り付け開始位置(左上からの横距離、縦距離)
0, 0,
// 画像への貼り付け範囲(幅、高さ)
canvas.width, canvas.height
)
resolve(canvas)
[5] Canvasから画像を取得
Canvasにリサイズ後の画像が表示され、Canvasの各種メソッドでDataURLやBlobが取得出来る。
サンプルではブラウザからダウンロードできるようにした。IEでもいけますがpng固定になるので注意。
// [5] キャンバスから画像を取得
if (canvas.msToBlob) { // IEの場合
window.navigator.msSaveBlob(canvas.msToBlob(), 'resized.png')
} else { // その他ブラウザ
const a = document.createElement('a')
a.href = canvas.toDataURL(file.type)
a.download = 'resized.' + file.type.split('/')[1]
a.click()
}
以上です。