在上傳前用JavaScript壓縮圖片
在這個(gè)快速教程中,我們將使用JavaScript壓縮帶有文件輸入元素的圖像。我們將壓縮圖像并將它們保存回文件輸入,以便上傳。
為了確保用戶可以上傳圖片,并防止超大圖片被上傳,我們可以在上傳前壓縮圖片數(shù)據(jù),而不必向用戶提出各種要求。
如果您趕時(shí)間,或者覺(jué)得閱讀代碼本身更方便,可以跳轉(zhuǎn)到這里的最終代碼片段[1]。
獲取選定的圖像文件
在下面的示例中,我們將接受所有類型的文件,但只壓縮圖像。multiple 屬性允許選擇多個(gè)文件。
為了防止添加其他文件,我們可以將文件輸入接受屬性設(shè)置為 image/*。
讓我們?cè)O(shè)置一個(gè)文件輸入框,當(dāng)用戶選擇了一個(gè)或多個(gè)文件時(shí),我們將偵聽(tīng) change 來(lái)檢測(cè)。我們將在下一節(jié)處理 TODO 項(xiàng)目。
<input type="file" multiple class="my-image-field" />
<script>
// 從文件輸入中獲取所選文件
const input = document.querySelector('.my-image-field');
input.addEventListener('change', (e) => {
// 獲取文件
const { files } = e.target;
// 未選定文件
if (!files.length) return;
// 對(duì)于文件列表中的每個(gè)文件
for (const file of files) {
// 我們不必壓縮非圖像文件
if (!file.type.startsWith('image')) {
// TODO: 不是圖像
}
// TODO: 壓縮圖像
}
// TODO: 儲(chǔ)存文件
});
</script>
現(xiàn)在,當(dāng)用戶選擇一個(gè)或多個(gè)圖像文件時(shí),我們的代碼就會(huì)運(yùn)行。接下來(lái)是壓縮圖像。
壓縮圖像
我們將實(shí)現(xiàn) compressImage 函數(shù)。該函數(shù)將把傳遞的文件對(duì)象轉(zhuǎn)換為 ImageBitmap,并將其繪制到 <canvas> 元素中,然后畫(huà)布將使用 toBlob[2] API 返回壓縮后的 JPEG。
請(qǐng)注意,我們也可以要求WEBP,但這(在撰寫(xiě)本文時(shí))在 Safari 上不支持???♂?
讓我們開(kāi)始吧。
<input type="file" multiple class="my-image-field" />
<script>
const compressImage = async (file, { quality = 1, type = file.type }) => {
// 獲取圖像數(shù)據(jù)
const imageBitmap = await createImageBitmap(file);
// 繪制到畫(huà)布上
const canvas = document.createElement('canvas');
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageBitmap, 0, 0);
// 變成Blob
return await new Promise((resolve) =>
canvas.toBlob(resolve, type, quality)
);
};
// 從文件輸入中獲取所選文件
const input = document.querySelector('.my-image-field');
input.addEventListener('change', async (e) => {
// 獲取文件
const { files } = e.target;
// 未選擇文件
if (!files.length) return;
// 對(duì)于文件列表中的每個(gè)文件
for (const file of files) {
// 我們不必壓縮非圖像文件
if (!file.type.startsWith('image')) {
// TODO: 不是圖像
}
// 我們將文件壓縮 50%
const compressedFile = await compressImage(file, {
// 0: 為最大壓縮率
// 1: 沒(méi)有壓縮
quality: 0.5,
// 我們想要一個(gè) JPEG 文件
type: 'image/jpeg',
});
}
// TODO: 儲(chǔ)存文件
});
</script>
現(xiàn)在,compressedFile 變量將包含壓縮后的圖像文件。
接下來(lái)就是將壓縮后的圖像文件(以及忽略的文件)保存回文件輸入端。
將壓縮圖像保存回文件輸入端
我們將創(chuàng)建一個(gè) DataTransfer 對(duì)象,用它來(lái)創(chuàng)建我們的新文件列表。之后,我們可以將該列表保存回文件輸入端
DataTransfer 對(duì)象只接受 File 對(duì)象,因此我們需要將 Blob 轉(zhuǎn)換為文件,讓我們更新 compressImage 函數(shù)。
<input type="file" multiple class="my-image-field" />
<script>
const compressImage = async (file, { quality = 1, type = file.type }) => {
// 獲取圖像數(shù)據(jù)
const imageBitmap = await createImageBitmap(file);
// 繪制到畫(huà)布上
const canvas = document.createElement('canvas');
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageBitmap, 0, 0);
// 變成Blob
const blob = await new Promise((resolve) =>
canvas.toBlob(resolve, type, quality)
);
// 將 Blob 變?yōu)?nbsp;File
return new File([blob], file.name, {
type: blob.type,
});
};
// 從文件輸入中獲取所選文件
const input = document.querySelector('.my-image-field');
input.addEventListener('change', async (e) => {
// 獲取文件
const { files } = e.target;
// 未選擇文件
if (!files.length) return;
// 對(duì)于文件列表中的每個(gè)文件
for (const file of files) {
// 我們不必壓縮非圖像文件
if (!file.type.startsWith('image')) {
// TODO: 不是圖像
}
// 我們將文件壓縮 50%
const compressedFile = await compressImage(file, {
// 0: 為最大壓縮率
// 1: 沒(méi)有壓縮
quality: 0.5,
// 我們想要一個(gè) JPEG 文件
type: 'image/jpeg',
});
// 保存壓縮文件而不是原始文件
dataTransfer.items.add(compressedFile);
}
// 將文件輸入值設(shè)置為我們的新文件列表
e.target.files = dataTransfer.files;
});
</script>
總結(jié)
我們已經(jīng)學(xué)會(huì)了如何使用 canvas 和 DataTransfer 來(lái)設(shè)置一個(gè)不起眼的小腳本,幫助我們?cè)谏蟼髑皦嚎s圖片。該腳本將使我們的用戶更容易上傳圖片。
當(dāng)然,我們還有很多需要改進(jìn)的地方,例如,我們可以
-
調(diào)整圖像大小以適應(yīng)最大邊界框。
-
將圖像裁剪成一定的長(zhǎng)寬比。
-
更好地處理錯(cuò)誤輸入并顯示合適的錯(cuò)誤信息。
-
確保我們能處理 Safari 內(nèi)存問(wèn)題。
如果我們需要一個(gè)更強(qiáng)大的解決方案,我們可以使用 FilePond[3] 來(lái)處理文件上傳,它可以調(diào)整圖像大小、壓縮等。
如果我們需要更多的控制,我們可以使用 Pintura[4] 讓用戶在上傳前編輯圖片,Pintura 也可以直接替代我們的 compressImage 功能。
原文:https://pqina.nl/blog/compress-image-before-upload
參考資料
跳轉(zhuǎn)到這里的最終代碼片段: https://pqina.nl/blog/compress-image-before-upload#saving-the-compressed-image-back-to-the-file-input
[2]toBlob: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
[3]FilePond: https://pqina.nl/filepond/
[4]Pintura: https://pqina.nl/pintura/
