В этой заметке я опишу, как, относительно несложным способом, можно организовать 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
всего этого не умеет.