В этой заметке я опишу, как, относительно несложным способом, можно организовать resize изображений на стороне браузера перед отправкой на сервер.
Для начала нам потребуется <input type=“file”>. Без него нам не открыть диалог для выбора файла. Чтобы вызвать его вручную необходимо в обработчике вида onclick (onkeydown и пр. отпадают из-за Firefox) вызвать file.click(). Вызвать диалог в произвольный момент времени не получится.
Отслеживаем изменение выбора файла по onchange. Сам же файл достаём из file.files[index]. Если наш <input> работает не в multiple режиме, то index, соответственно, равен 0. Присмотревшись к свойствам этого объекта мы увидим его mime тип, размер, имя файла. Но не содержимое.
Для получения содержимого воспользуемся FileReader-ом.
const reader = new FileReader(); reader.onload = e => { e.target.result; /* base64 string */ }; reader.readAsDataURL(file);
Через него получаем DataURL вариант содержимого файла (асинхронно). Затем создаём новый Image, вешаем обработчик на onload, задаём этот dataURL ему как аттрибут src. В результате получаем валидный <img> тег с загруженным готовым к использованию изображением.
Resize изображения будем осуществлять за счёт <canvas>. У context-а canvas-а есть метод drawImage, который сделает за нас всю грязную работу. Ниже пример работы с ним (стояла задача вписать изображение в определённые рамки, но только если оно превышает эти сами рамки):
const canvas = document.createElement('canvas'); const w_ratio = img.width / width; const h_ratio = img.height / height; let ratio = Math.max(w_ratio, h_ratio); if(ratio < 1) ratio = 1; const w = Math.floor(img.width / ratio); const h = Math.floor(img.height / ratio); canvas.width = w; canvas.height = h; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, w, h); ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, h); return canvas.toDataURL('image/jpeg', 80);
Обратите внимание на то, что <canvas> даже не обязательно цеплять к DOM-у браузера, и тем более делать его видимым. В результате получаем снова dataURL, но уже правильного вида и размера. Но что нам теперь с ним делать?
Вариантов много. К примеру можно послать на сервер строкой, как и другие поля. Но можно воспользоваться FormData, и тогда работа с такой выдачей клиента ничем не будет отличаться от обычной:
const fd = new FormData(form); fd.append('image', blob);
Обратите внимание на blob. Дело в том, что для маскировки под обычный input[type=file] нам нужно нашу dataURL перевести в Blob вариант. Я взял готовое решение со stackoverflow:
function dataUriToBlob(dataURI) { let byteString; if( dataURI.split(',')[0].indexOf('base64') >= 0 ) byteString = atob(dataURI.split(',')[1]); else byteString = window.unescape(dataURI.split(',')[1]); // separate out the mime component const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to a typed array const ia = new Uint8Array(byteString.length); for(let i = 0; i < byteString.length; i ++) ia[i] = byteString.charCodeAt(i); return new Blob([ ia ], { type: mimeString }); }
Задача решена. Относительно без крови. Однако, мы воспользовались FormData, FileReader, Blob, Uint8Array, Canvas. Тот же самый IE9 всего этого не умеет.