📖

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