HTML5 асинхронная загрузка изображений
Статья рассматривает применение новой технологии html5 для асинхронной загрузки изображений на сервер. Я рассмотрю новое API jsScript и приведу несколько примеров кода выбора файлов, создание предварительных изображений, предварительного поворота на 90 градусов, предварительного изменения размеров картинок на стороне браузера и последующую загрузку файлов на сервер. Данные примеры были протестированы и работают пока лишь в браузерах Mozilla Firefox, Google Chrome последних версий.
Для работы примеров создадим документ html:
<!DOCTYPE html> HTML5 uploader images Send
В документе обязательным условием является указание doctype html. Форма выбора файлов создержит поле input с type=”file”. Для обеспечения выбора нескольких файлов для input применен аттрибут multiple=”true”. Обработчик события окончания выбора файлов handleFiles имеет аргумент, представляющий собой массив выбранных файлов.
Создаем файл js, где будет весь алгоритм загрузчика:
window.URL = window.URL || window.webkitURL; //в google chrome нет window.URL, но есть //window.webkitURL. Он необходим для создания URL локального изображения var currImg = 0; //номер текущего загружаемого в браузер изображения var k = 0;//номер текущего обрабатываемого изображения var list = document.createElement(“ul”); //создаем список ul var imgData = [];//массив canvas, где хранятся canvas изображений для последующей загрузки function handleFiles(files) { //обработчик события выбора файлов for(var i = 0; i < files.length; i++) { var file = files[i]; var li = document.createElement("li"); list.appendChild(li); var img = document.createElement("img"); var canvas = document.createElement("canvas"); canvas.setAttribute("id","canvas_" + k); img.onload = function(e) {//обработка события загрузки изображения. //e.target – текущий объект изображения window.URL.revokeObjectURL(e.target.src);//очищаем ObjectURL var canvas = document.getElementById("canvas_" + currImg); //мы создаем //canvas для последующей работы с изображением в браузере. Тем самым мы даем //пользователю предварительно поправить (например, повернуть) картинку перед ее загрузкой. canvas.width = e.target.width; canvas.height = e.target.height; canvas.getContext("2d").drawImage(e.target,0,0,e.target.width,e.target.height);//здесь рисуем на //canvas наше изображение imgData[currImg] = canvas;//добавляем в массив currImg++;//отдельный счетчик сделан из-за того, что картинки загружаются //параллельно работе обработчика выбора файлов, и как следствие намного медленнее } img.src = window.URL.createObjectURL(file);//присваиваем локальный адрес //картинки в src объекта img var info = document.createElement("span"); info.innerHTML = files[i].name + ": " + Math.floor(files[i].size / 1024) + " Kb"; //выводим информацию о загруженной картинке li.appendChild(info); li.appendChild(canvas); k++;//увеличиваем счетчик обрабатываемых изображений } document.getElementById("selectedFiles").appendChild(list); }
Далее напишем обработчик нажатия кнопки загрузки (id=”sendfile”):
var sendBtn = document.getElementById(“sendfile); sendBtn.addEventListener(“click”,upload,false);//добавляем «прослушиватель события нажатия» function upload(e) { for(var i = 0; i < imgData.length; i++) { var canvas = imgData[i]; var dataurl = canvas.toDataURL("image/jpeg"); //для отправки изображения я воспользовался библиотекой jQuery jQuery.post("/get.php",{image:dataurl},function(data){ div.innerHTML = data; }); }
Поясню некоторые особо важные моменты. В функции upload мы пробегаемся по массиву canvas`ов. Далее нам необходимо получить данные изображения из текущей канвы для отправки на сервер. Для этого у canvas есть три метода:
- toDataURL(in optional DOMString type, in any …args)
- toBlob(in Function callback, in optional DOMString type, in any …args)
- mozGetAsFile(in DOMString name, in optional DOMString type)
Метод toBlob я не рассматривал, и, если я не ошибаюсь у этого метода есть баг. Метод mozGetAsFile возвращает объект File из canvas. Он специфичный и работает только лишь в браузерах на движке Gecko 2.0 (Mozilla Firefox), и как следствие, не стал работать в Google Chrome.
Метод toDataURL возвращает строку вида: «data:image/png;base64,/9j/4AAQSkZJR…»
В зависимости от первого аргумента, после слова data: может быть image/png либо image/jpeg, смотря в каком формате вам нужна картинка. По-умолчанию формат будет image/png. Вы можете установить image/jpeg передав в первый параметр image/jpeg. У метода toDataURL при установке первого параметра image/jpeg есть возможность установить второй числовой параметр от 0.0 до 1.0, который будет обозначать качество сохранения изображения в формате jpeg. Но здесь подстерегает баг. В Mozilla Firefox при установке второго параметра, будет вызвано исключение с ошибкой безопасности номер 1000. Пока баг не исправлен, можем довольствоваться сохранением картинки в jpeg формате с качеством сжатия по-умолчанию.
В строке, что возвращает метод toDataURL сразу после слова base64 и запятой идет строка, закодированная в base64, которая является данными изображения. Следовательно на сервере нам будет необходимо перед сохранением в файл вырезать эту строку и расшифровать ее. Вот как я сделал на php для формата image/jpeg:
<?php $filedata = substr($_POST['image'], 23); $fp = fopen($file, "w"); fwrite($fp, base64_decode($filedata)); fclose($fp);
Теперь о изменении размеров изображений на стороне браузера. Я приведу пример функции осуществляющий алгортим изменения размеров на canvas:
function resizeImg(canvas, max_width, max_height) { canvas.style.display = "none"; var width = canvas.width; var height = canvas.height; if (width > height) { if (width > max_width) { height *= max_width / width; width = max_width; } } else { if (height > max_height) { width *= max_height / height; height = max_height; } } var copy = document.createElement("canvas"); copy.width = width; copy.height = height; copy.getContext("2d").drawImage(canvas,0,0,width,height); canvas.width = width; canvas.height = height; canvas.getContext("2d").drawImage(copy,0,0); canvas.style.display = "block"; }
Данная функция пропорционально изменяет размер изображения. Пример применения с функцией handleFiles:
img.onload = function(e) {//обработка события загрузки изображения. //e.target – текущий объект изображения window.URL.revokeObjectURL(e.target.src);//очищаем ObjectURL var canvas = document.getElementById("canvas_" + currImg); //мы создаем //canvas для последующей работы с изображением в браузере. Тем самым мы даем //пользователю предварительно поправить (например, повернуть) картинку перед ее загрузкой. canvas.width = e.target.width; canvas.height = e.target.height; canvas.getContext("2d").drawImage(e.target,0,0,e.target.width,e.target.height);//здесь рисуем на //canvas наше изображение resizeImg(canvas, 188, 188); //создаем thumbnail imgData[currImg] = canvas;//добавляем в массив currImg++;//отдельный счетчик сделан из-за того, что картинки загружаются //параллельно работе обработчика выбора файлов, и как следствие намного медленнее }
Функция поворота изображения выглядит так:
/** * Процедура поворта изображения на холсте * @param canvas холст * @param width ширина * @param height высота * @param angle угол в градусах * @return void */ function rotateImg(canvas, width, height, angle) { var copy = document.createElement('canvas'); copy.width = width; copy.height = height; copy.getContext("2d").drawImage(canvas, 0,0,width,height); angle = -parseFloat(angle) * Math.PI / 180; var dimAngle = angle; if (dimAngle > Math.PI*0.5) dimAngle = Math.PI - dimAngle; if (dimAngle < -Math.PI*0.5) dimAngle = -Math.PI - dimAngle; var diag = Math.sqrt(width*width + height*height); var diagAngle1 = Math.abs(dimAngle) - Math.abs(Math.atan2(height, width)); var diagAngle2 = Math.abs(dimAngle) + Math.abs(Math.atan2(height, width)); var newWidth = Math.abs(Math.cos(diagAngle1) * diag); var newHeight = Math.abs(Math.sin(diagAngle2) * diag); canvas.width = newWidth; canvas.height = newHeight; var ctx = canvas.getContext("2d"); ctx.translate(newWidth/2, newHeight/2); ctx.rotate(angle); ctx.drawImage(copy,-width/2,-height/2); }
Здесь так же ничего сложного нет. Для применения вам необходимо будет создать кнопку и повесить на ее событие клика эту функцию.
Кроме поворота можно применить еще множество различных эффектов перед загрузкой на сервер.
В ходе исследования и разработки загрузчика изображений были использованы материалы следующих источников:
How to develop a HTML5 Image Uploader
Информация о canvas