В недавнем проекте я решил написать свой собственный код для управления изображением после того, как библиотека Jimp доставила мне головную боль. Моя цель — создать расширяемый код для управления изображениями в JavaScript. Для целей этого поста я только изменю размер изображения и пропущу другие манипуляции, такие как кадрирование. Я надеюсь, что это послужит хорошей отправной точкой для вашего проекта!
Поддержка браузера: любой браузер, поддерживающий элемент canvas и Promise.
Давайте начнем! Во-первых, мы создаем класс, который мы сможем создавать каждый раз, когда нам нужно изменить размер изображения.
function ImageProcessor(file){ if (!(window.File && window.FileReader && window.FileList && window.Blob)) throw new Error('The File APIs are not fully supported in this browser.'); if (!file.type.match('image.*')) throw new Error('File is not an image'); this.image = null; this.queue = []; this.file = file; return this; }
this.image: ссылка на наш объект изображения
this.file: ссылка на наш файл изображения
this.queue : Очередь трансформаций
Потрясающий! Теперь нам нужно объявить некоторые функции класса. У меня будет два доступных преобразования: «ширина» и «высота». Они оба изменяют размер изображения, но в зависимости от ширины или высоты изображения.
ImageProcessor.prototype.width = function(width){}; ImageProcessor.prototype.height = function(height){};
Давайте определим функцию ImageProcessor.width()
ImageProcessor.prototype.width = function(width){ if(!width) throw new Error('Expected arguments'); this.queue.push(['width', width]); return this; };
Вместо того, чтобы сразу выполнять изменение размера, мы помещаем преобразование в очередь. Нажатый массив содержит два элемента. Первый элемент — это имя преобразования, которое позже будет сопоставлено с функцией, а второй элемент — ее значение. В этом случае, если бы разработчик написал ImageProcessor.width(256), то значение второго элемента в массиве было бы 256. Определение функции ImageProcessor.height() очень похоже:
ImageProcessor.prototype.height = function(height){ if(!height) throw new Error('Expected arguments'); this.queue.push(['height', height]); return this; };
Теперь, когда мы определили эти две функции, мы должны написать код, который фактически изменит размер изображения.
var $transformers = { 'width': function(value){}, 'height': function(value){} };
Этот объект $transformers содержит функции, преобразующие изображение. Давайте посмотрим, как выглядит код для преобразователя ширины:
var $transformers = { 'width': function(value){ var image = this; var canvas = document.createElement("canvas"), ctx = canvas.getContext("2d"); // Calculate new dimensions var multiplier = value / image.width; var width = image.width * multiplier, height = image.height * multiplier; // Set dimensions on canvas canvas.width = width; canvas.height = height; // Transform image ctx.drawImage(image, 0, 0, width, height); // Export canvas data to Image image.src = canvas.toDataURL(); }, 'height': function(value){ ... } };
Шаги, которые вы видите здесь, следующие: создание элемента холста, вычисление новых размеров на основе соотношения сторон, установка новых размеров на холсте, рисование изображения на холсте с новыми размерами и, наконец, извлечение URL-адреса данных из холста и установка изображения. src к этому. Достаточно просто, верно? По крайней мере, если вы возились с этим, все должно быть довольно просто. Трансформатор «высота» выглядит почти идентично, за исключением расчета соотношения сторон «значение/изображение.высота».
var $transformers = { 'width': function(value){ ... }, 'height': function(value){ var image = this; var canvas = document.createElement("canvas"), ctx = canvas.getContext("2d"); // Calculate new dimensions var multiplier = value / image.height; var width = image.width * multiplier, height = image.height * multiplier; // Set dimensions on canvas canvas.width = width; canvas.height = height; // Transform image ctx.drawImage(image, 0, 0, width, height); // Export canvas data to Image image.src = canvas.toDataURL(); } };
Самое сложное уже позади! Теперь давайте соединим всю эту красивую логику вместе. Мы объявляем последнюю функцию класса с именем «exec».
ImageProcessor.prototype.exec = function(){};
Эта функция будет перебирать очередь и вызывать преобразователи. Когда очередь пуста, изображение должно пройти все преобразования, установленные разработчиком, и обещание разрешается с новым URL-адресом данных изображения.
ImageProcessor.prototype.exec = function(){ var queue = this.queue, file = this.file, image = this.image; var getImageFromFile = function(file, callback){ var reader = new FileReader(); reader.onload = function(event) { var img = new Image(); img.src = event.target.result; callback(img); }; reader.readAsDataURL(file); }; return new Promise(function(resolve, reject){ // Get image first getImageFromFile(file, function(img){ // Set initial image image = img; // Iterate through transformers while(queue.length){ var action = queue.shift(), func = action[0], value = action[1]; $transformers[func].bind(image)(value); } // Resolve promise with final dataURL resolve(image.src) }) }); };
И это все! Должны ли мы посмотреть, как его использовать?
$('#img-input').on('change', function(event){ new ImageProcessor(event.target.files[0]) .width(64) .exec() .then(function(data){ console.log('Resized image dataURL:', data); }); });
Видите, как эта красивая цепочка делает текст очень легко читаемым? В любом случае, я надеюсь, что это помогло вам. Пожалуйста, дайте мне знать, если вам нужна дополнительная помощь в комментариях ниже, и я сделаю все возможное, чтобы помочь.