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