📖
Quill 画像のドラッグアンドドロップとimageデータの圧縮
経緯
electronでノートアプリを制作している。画像の表示も可能なニッチなエディタです。
画像を設定できるということで、張り切って10枚ほど設定し、表示してみました。
そうすると、予想外にデータを食うことが判明しました。
20MBほどになってしまいました。
20MBの画像を含むファイルですと、ロードに時間がかかり、ユーザ体験が低下してしまいます。
実際、10MBほどのページになるともっさり感があり、操作性がよくありませんでした。
そのため、エディタに挿入される前に、画像圧縮をしてから挿入するようにしました。
具体的に1枚当たり、0.1Mバイトを目標に圧縮を行います。
webp 横1000px 品質0.8が、大体要件を満たす設定です。
10枚で1MB。
これであれば、問題ないということになりました。
Quillのtoolbarからの読み込みの場合、この処理は簡潔に書くことができました。
しかし、ドラッグアンドドロップの場合、うまく実装することができませんでした。
以下が、ドラッグした画像の要素を取得し、変換後、画像を張り付けるコードです。
ノートソフトなので、見開きで二つありますが、そこは気にしないでください。
// 画像をリサイズし、Base64に変換する関数
function resizeImage(file, maxWidth = 720, quality = 0.9) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (readerEvent) => {
const image = new Image();
image.onload = (imageEvent) => {
const canvas = document.createElement('canvas');
let width = image.width;
let height = image.height;
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
// webp形式でBase64データURLを取得(品質を指定可能)
const dataUrl = canvas.toDataURL('image/webp', quality);
resolve(dataUrl);
};
image.src = readerEvent.target.result;
};
reader.onerror = (error) => {
console.error('File reading error for resize:', error);
resolve(null); // エラー時はnullを返す
};
reader.readAsDataURL(file);
});
}
// 画像を Base64 に変換してエディタに挿入する関数
async function handleImage(editor) {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async () => {
const file = input.files[0];
if (file) {
try {
const resizedBase64 = await resizeImage(file, 1000, 0.80);
if (resizedBase64) {
const range = editor.getSelection();
if (range) {
editor.insertEmbed(range.index, 'image', resizedBase64);
} else {
editor.insertEmbed(editor.getLength(), 'image', resizedBase64);
}
} else {
console.error('Image resize failed or returned null.');
}
} catch (error) {
console.error('Error handling image:', error);
}
}
};
}
// Quillエディタを初期化
const quill_left = new Quill('#editor-left', {
modules: {
toolbar: {
container: '#toolbar-container-left',
handlers: {
'image': () => handleImage(quill_left)
}
},
uploader: {
handler: async (range, files) => {
const file = files[0];
if (file && file.type.startsWith('image/')) {
try {
const resizedBase64 = await resizeImage(file, 1000, 0.80);
if (resizedBase64) {
quill_left.insertEmbed(range.index, 'image', resizedBase64);
} else {
console.error('Image resize failed or returned null.');
}
} catch (error) {
console.error('Error handling dropped image:', error);
}
}
}
}
},
theme: 'snow'
});
const quill_right = new Quill('#editor-right', {
modules: {
toolbar: {
container: '#toolbar-container-right',
handlers: {
'image': () => handleImage(quill_right)
}
},
uploader: {
handler: async (range, files) => {
const file = files[0];
if (file && file.type.startsWith('image/')) {
try {
const resizedBase64 = await resizeImage(file, 1000, 0.80);
if (resizedBase64) {
quill_right.insertEmbed(range.index, 'image', resizedBase64);
} else {
console.error('Image resize failed or returned null.');
}
} catch (error) {
console.error('Error handling dropped image:', error);
}
}
}
}
},
theme: 'snow'
});
Discussion