HTML5 асинхронная загрузка изображений

8 октября 2011 г.

Статья рассматривает применение новой технологии html5 для асинхронной загрузки изображений на сервер. Я рассмотрю новое API jsScript и приведу несколько примеров кода выбора файлов, создание предварительных изображений, предварительного поворота на 90 градусов, предварительного изменения размеров картинок на стороне браузера и последующую загрузку файлов на сервер. Данные примеры были протестированы и работают пока лишь в браузерах Mozilla Firefox, Google Chrome последних версий.

Для работы примеров создадим документ html:

<!DOCTYPE html>
  HTML5 uploader images
    Send

HTML5В документе обязательным условием является указание 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 есть три метода:

  1. toDataURL(in optional DOMString type, in any …args)
  2. toBlob(in Function callback, in optional DOMString type, in any …args)
  3. 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

Теги:
рубрика JavaScript
  • Похожие статьи
  • Предыдущие из рубрики