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

});

Видите, как эта красивая цепочка делает текст очень легко читаемым? В любом случае, я надеюсь, что это помогло вам. Пожалуйста, дайте мне знать, если вам нужна дополнительная помощь в комментариях ниже, и я сделаю все возможное, чтобы помочь.