Тарифы Услуги Сим-карты

Css обрезка изображения. Форматирование изображений с помощью Canvas. Фактическое изменение размеров изображения

Узнайте, как изменять размер и обрезать изображения с помощью JavaScript и элемента HTML5 Canvas , используя при этом инструменты управления, которые вы могли видеть в приложениях для редактирования фотографий:

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

В реальной среде веб-сайт или приложение могут использовать эту технику для изменения размера и создания фотографии профиля перед загрузкой.

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

Для этого мы создадим элемент HTML5 и выведем изображение на холсте в определенном размере, а затем извлечем новые данные изображения с холста в виде данных URI .

Большинство браузеров уже поддерживают эти методы, так что вы, скорее всего, сможете применить эту технику прямо сейчас. Однако вам нужно знать о некоторых ограничениях, не связанных с поддержкой браузерами, таких как качество изображений и производительность.

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

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

Окончательный результат вы можете увидеть в этой демо-версии , или вы можете скачать ZIP-архив .

Что ж, теперь давайте приступим!

Разметка

В нашей демо-версии мы начнем с существующего изображения:

Вот и все! Это весь HTML -код, который нам потребуется для этой демонстрации.

CSS

CSS также самый минимальный. Во-первых, определим стили для контейнера изменения размера и изображения:

Resize-container { position: relative; display: inline-block; cursor: move; margin: 0 auto; } .resize-container img { display: block } .resize-container:hover img, .resize-container:active img { outline: 2px dashed rgba(222,60,80,.9); }

Resize-handle-ne, .resize-handle-ne, .resize-handle-se, .resize-handle-nw, .resize-handle-sw { position: absolute; display: block; width: 10px; height: 10px; background: rgba(222,60,80,.9); z-index: 999; } .resize-handle-nw { top: -5px; left: -5px; cursor: nw-resize; } .resize-handle-sw { bottom: -5px; left: -5px; cursor: sw-resize; } .resize-handle-ne { top: -5px; right: -5px; cursor: ne-resize; } .resize-handle-se { bottom: -5px; right: -5px; cursor: se-resize; }

JavaScript

JavaScript мы начинаем с определения некоторых переменных и инициализации Canvas и целевого изображения:

var resizeableImage = function(image_target) { var $container, orig_src = new Image(), image_target = $(image_target).get(0), event_state = {}, constrain = false, min_width = 60, min_height = 60, max_width = 800, max_height = 900, resize_canvas = document.createElement("canvas"); }); resizeableImage($(".resize-image"));

Далее мы создаем функцию init , которая будет вызываться сразу при загрузке. Эта функция обертывает изображение в контейнер, создает маркеры изменения размера и создает копию исходного изображения, используемую для изменения размера.

Мы также назначаем объект JQuery для элемента контейнера в переменную, чтобы можно было обратиться к ней позже и добавить отслеживатель события mousedown , который определяет, когда кто-то начинает перетаскивать один из маркеров:

var resizeableImage = function(image_target) { // ... init = function(){ // Создаем новое изображение с копией оригинального src // Когда мы изменяем размер изображения, мы всегда берем за основу эту копию orig_src.src=image_target.src; // Добавляем маркеры изменения размера $(image_target).wrap("

") .before("") .before("") .after("") .after(""); // Получаем переменные для контейнера $container = $(image_target).parent(".resize-container"); // Добавляем события $container.on("mousedown", ".resize-handle", startResize); }; //... init(); }

Функции startResize и endResize только указывают браузеру начать отслеживать, куда перемещается мышь, и прекратить отслеживание:

startResize = function(e){ e.preventDefault(); e.stopPropagation(); saveEventState(e); $(document).on("mousemove", resizing); $(document).on("mouseup", endResize); }; endResize = function(e){ e.preventDefault(); $(document).off("mouseup touchend", endResize); $(document).off("mousemove touchmove", resizing); };

Прежде чем мы начнем отслеживать положение мыши, нам нужно сделать снимок размеров и других ключевых данных контейнера.

Мы сохраняем их в переменной с именем event_state и используем ее позже в качестве отправной точки при изменении высоты и ширины:

saveEventState = function(e){ // Сохраняем изначальные параметры события и состояние контейнера event_state.container_width = $container.width(); event_state.container_height = $container.height(); event_state.container_left = $container.offset().left; event_state.container_top = $container.offset().top; event_state.mouse_x = (e.clientX || e.pageX || e.originalEvent.touches.clientX) + $(window).scrollLeft(); event_state.mouse_y = (e.clientY || e.pageY || e.originalEvent.touches.clientY) + $(window).scrollTop(); // Это заплатка для мобильного safari // Почему-то в нем нельзя напрямую копировать сенсорные свойства if(typeof e.originalEvent.touches !== "undefined"){ event_state.touches = ; $.each(e.originalEvent.touches, function(i, ob){ event_state.touches[i] = {}; event_state.touches[i].clientX = 0+ob.clientX; event_state.touches[i].clientY = 0+ob.clientY; }); } event_state.evnt = e; }

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

resizing = function(e){ var mouse={},width,height,left,top,offset=$container.offset(); mouse.x = (e.clientX || e.pageX || e.originalEvent.touches.clientX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY || e.originalEvent.touches.clientY) + $(window).scrollTop(); width = mouse.x - event_state.container_left; height = mouse.y - event_state.container_top; left = event_state.container_left; top = event_state.container_top; if(constrain || e.shiftKey){ height = width / orig_src.width * orig_src.height; } if(width > min_width && height > min_height && width < max_width && height < max_height){ resizeImage(width, height); // Без этого Firefox не будем пересчитывать размеры изображения, пока перетаскивание не завершилось $container.offset({"left": left, "top": top}); } }

Затем мы добавляем опцию для ограничения размеров изображения при переключении с помощью клавиши Shift или переменной.

Наконец, мы изменяем размер изображения, но только если новая ширина и высота не выходят за пределы минимального и максимального значений переменных, которые мы задали в начале.

Примечание: Так как мы на самом деле измененяем размеры изображения, а не только атрибуты высоты и ширины, вы из соображений повышения производительности можете рассмотреть вопрос об ограничении того, как часто можно вызывать resizeImage . Это называется дебаунсинг или дросселинг.

Фактическое изменение размеров изображения

Вывести изображение можно просто с помощью drawImage. Мы сначала устанавливаем высоту и ширину холста и всегда используем оригинальную копию полноразмерного изображения. Затем мы используем toDataURL для холста, чтобы получить в кодировке Base64 версию недавно измененного изображения и поместить ее на странице.

В разделе, посвященном обрезке, приведено полное объяснение для всех параметров, которые могут быть использованы с методом drawImage:

resizeImage = function(width, height){ resize_canvas.width = width; resize_canvas.height = height; resize_canvas.getContext("2d").drawImage(orig_src, 0, 0, width, height); $(image_target).attr("src", resize_canvas.toDataURL("image/png")); };

Слишком просто? Существует одна маленькая оговорка: изображение должно быть размещено на том же домене, что и страница, или на сервере с включенной возможностью обмена с разными исходными источниками (CORS) . В противном случае у вас могут возникнуть проблемы с ошибками «испорченного холста «.

Изменение размеров с разных углов

Теперь у вас уже должна быть рабочая демо-версия. Но это еще не все. На данный момент, она еще не работает так, чтобы изменение размера происходило, будто мы тянем за нижний правый угол, независимо от того, с какого угла мы осуществляем изменение размера изображения.

Нам нужно обеспечить возможность изменения размеров изображения с любого угла. Для этого мы должны понять поведение нашей демо-модели.

При изменении размеров угол, за который мы тянем, а также прилегающие к нему стороны должны перемещаться, в то время как противоположный угол и прилегающие к нему стороны должны оставаться на месте:

Когда мы изменяем ширину и высоту изображения, правый и нижний края перемещаются, в то время как верхний и левый края останутся на месте. Это означает, что по умолчанию, изображения изменяются с правого нижнего угла.

Мы не можем изменить это поведение по умолчанию, но при изменении размера из любого угла, кроме правого нижнего, мы можем изменить общее положение изображения так, что будет казаться, будто противоположный угол и прилегающие к нему края остаются на месте. Давайте обновим нашу функцию resizing :

resizing = function(e){ var mouse={},width,height,left,top,offset=$container.offset(); mouse.x = (e.clientX || e.pageX || e.originalEvent.touches.clientX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY || e.originalEvent.touches.clientY) + $(window).scrollTop(); // Позиция изображения по разному зависит от угла, за который мы тянем if($(event_state.evnt.target).hasClass("resize-handle-se")){ width = mouse.x - event_state.container_left; height = mouse.y - event_state.container_top; left = event_state.container_left; top = event_state.container_top; } else if($(event_state.evnt.target).hasClass("resize-handle-sw")){ width = event_state.container_width - (mouse.x - event_state.container_left); height = mouse.y - event_state.container_top; left = mouse.x; top = event_state.container_top; } else if($(event_state.evnt.target).hasClass("resize-handle-nw")){ width = event_state.container_width - (mouse.x - event_state.container_left); height = event_state.container_height - (mouse.y - event_state.container_top); left = mouse.x; top = mouse.y; if(constrain || e.shiftKey){ top = mouse.y - ((width / orig_src.width * orig_src.height) - height); } } else if($(event_state.evnt.target).hasClass("resize-handle-ne")){ width = mouse.x - event_state.container_left; height = event_state.container_height - (mouse.y - event_state.container_top); left = event_state.container_left; top = mouse.y; if(constrain || e.shiftKey){ top = mouse.y - ((width / orig_src.width * orig_src.height) - height); } } // Опционально поддерживаем соотношение сторон if(constrain || e.shiftKey){ height = width / orig_src.width * orig_src.height; } if(width > min_width && height > min_height && width < max_width && height < max_height){ // Для увеличения производительности вы можете ограничить количество вызовов resizeImage() resizeImage(width, height); // Без этого Firefox не будет пересчитывать размеры изображения, пока перетаскивание не завершилось $container.offset({"left": left, "top": top}); } }

Теперь наш код проверяет, какой из маркеров resize-handle перетягивается, и перемещает наше изображение так, что, кажется, будто соответствующий угол остается неподвижным.

Перемещения изображения

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

Сейчас мы должны обеспечить, чтобы изображение возвращалось обратно в центр кадра. В функции init давайте добавим еще один отслеживатель событий, похожий на тот, который мы уже создали раньше:

init = function(){ //... $container.on("mousedown", "img", startMoving); }

Теперь мы добавляем функции startMoving и endMoving, аналогично тому, как мы добавили startResize и endResize:

startMoving = function(e){ e.preventDefault(); e.stopPropagation(); saveEventState(e); $(document).on("mousemove", moving); $(document).on("mouseup", endMoving); }; endMoving = function(e){ e.preventDefault(); $(document).off("mouseup", endMoving); $(document).off("mousemove", moving); };

В функции moving нужно вычислить новую позицию для левого верхнего края контейнера. Она будет равна текущей позиции мыши, смещенной на расстояние от левого верхнего угла до мыши, когда мы начали перетаскивать изображение:

moving = function(e){ var mouse={}; e.preventDefault(); e.stopPropagation(); mouse.x = (e.clientX || e.pageX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY) + $(window).scrollTop(); $container.offset({ "left": mouse.x - (event_state.mouse_x - event_state.container_left), "top": mouse.y - (event_state.mouse_y - event_state.container_top) }); };

Обрезка изображения

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

Они смогут сами определять масштаб и позицию обрезаемого фрейма в исходном изображении, мы же в свою очередь будем уверены, что окончательное изображение всегда будет иметь одну форму и размеры.

Для этого нам нужно добавить следующий HTML-код:

Стили для фрейма наложения очень важны, особенно позиция, ширина и высота, так как они используются, чтобы определить, какая часть изображения будет обрезана.

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

Overlay { position: absolute; left: 50%; top: 50%; margin-left: -100px; margin-top: -100px; z-index: 999; width: 200px; height: 200px; border: solid 2px rgba(222,60,80,.9); box-sizing: content-box; pointer-events: none; } .overlay:after, .overlay:before { content: ""; position: absolute; display: block; width: 204px; height: 40px; border-left: dashed 2px rgba(222,60,80,.9); border-right: dashed 2px rgba(222,60,80,.9); } .overlay:before { top: 0; margin-left: -2px; margin-top: -40px; } .overlay:after { bottom: 0; margin-left: -2px; margin-bottom: -40px; } .overlay-inner:after, .overlay-inner:before { content: ""; position: absolute; display: block; width: 40px; height: 204px; border-top: dashed 2px rgba(222,60,80,.9); border-bottom: dashed 2px rgba(222,60,80,.9); } .overlay-inner:before { left: 0; margin-left: -40px; margin-top: -2px; } .overlay-inner:after { right: 0; margin-right: -40px; margin-top: -2px; } .btn-crop { position: absolute; vertical-align: bottom; right: 5px; bottom: 5px; padding: 6px 10px; z-index: 999; background-color: rgb(222,60,80); border: none; border-radius: 5px; color: #FFF; }

Добавьте в JavaScript следующую функцию и отслеживатель событий:

init = function(){ //... $(".js-crop").on("click", crop); }; crop = function(){ var crop_canvas, left = $(".overlay").offset().left - $container.offset().left, top = $(".overlay").offset().top - $container.offset().top, width = $(".overlay").width(), height = $(".overlay").height(); crop_canvas = document.createElement("canvas"); crop_canvas.width = width; crop_canvas.height = height; crop_canvas.getContext("2d").drawImage(image_target, left, top, width, height, 0, 0, width, height); window.open(crop_canvas.toDataURL("image/png")); }

Функция crop похожа на функцию resizeImage , только вместо передачи ей значения высоты и ширины мы получаем высоту и ширину от элемента наложения.

Для обрезки метод холста drawImage требует девять параметров. Первым параметром является источник изображения. Следующие четыре параметра указывают, какая часть исходного изображения используется (бокс отсечения ). Последние четыре параметра указывают, в каком месте холста выводить изображение и в каком размере.

Добавление сенсорных событий

Для mousedown и mouseup существуют эквивалентные сенсорные события — touchstart и touchend , для mousemov есть эквивалентное событие touchmove . Кому-то, кто называл эти события, явно не доставало чувства юмора, иначе он вполне мог бы назвать их “touchdown ” и “touchup ”.

Давайте добавим touchstart и touchend везде, где у нас встречается mousedown и mouseup , а mousemove заменим на touchmove :

// В init()... $container.on("mousedown touchstart", ".resize-handle", startResize); $container.on("mousedown touchstart", "img", startMoving); //В startResize() ... $(document).on("mousemove touchmove", moving); $(document).on("mouseup touchend", endMoving); //В endResize()... $(document).off("mouseup touchend", endMoving); $(document).off("mousemove touchmove", moving); //В startMoving()... $(document).on("mousemove touchmove", moving); $(document).on("mouseup touchend", endMoving); //В endMoving()... $(document).off("mouseup touchend", endMoving); $(document).off("mousemove touchmove", moving);

Так как мы изменяем размеры изображения, было бы справедливо ожидать, что некоторые пользователи захотят применить общепринятые действия, например растягивание изображения. Есть такая библиотека Hammer , которая содержит много удобных инструментов для работы с сенсорными событиями.

Но так как нам нужно только растяжение, то можно обойтись и без нее. Сейчас я покажу, как легко можно создать растяжение без помощи сторонней библиотеки.

Вы могли заметить, что в функции saveEventState уже хранятся изначальные сенсорные данные; сейчас нам это пригодится.

Во-первых, мы проверяем, содержит ли событие два «прикосновения » и измеряем расстояние между ними. Отмечаем это как начальное расстояние, а затем измеряем, на сколько это расстояние изменяется во время перемещения. Давайте обновим функцию moving:

moving = function(e){ var mouse={}, touches; e.preventDefault(); e.stopPropagation(); touches = e.originalEvent.touches; mouse.x = (e.clientX || e.pageX || touches.clientX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY || touches.clientY) + $(window).scrollTop(); $container.offset({ "left": mouse.x - (event_state.mouse_x - event_state.container_left), "top": mouse.y - (event_state.mouse_y - event_state.container_top) }); // Отслеживаем растягивание во время перемещения if(event_state.touches && event_state.touches.length > 1 && touches.length > 1){ var width = event_state.container_width, height = event_state.container_height; var a = event_state.touches.clientX - event_state.touches.clientX; a = a * a; var b = event_state.touches.clientY - event_state.touches.clientY; b = b * b; var dist1 = Math.sqrt(a + b); a = e.originalEvent.touches.clientX - touches.clientX; a = a * a; b = e.originalEvent.touches.clientY - touches.clientY; b = b * b; var dist2 = Math.sqrt(a + b); var ratio = dist2 /dist1; width = width * ratio; height = height * ratio; // Для увеличения производительности вы можете ограничить количество вызовов resizeImage() resizeImage(width, height); } };

Мы делим текущее расстояние на начальное расстояние, чтобы определить соотношение и соответственно применить его для масштабирования изображения. Мы вычисляем новую ширину и высоту, а затем изменяем размер изображения:

Вот все. Посмотрите еще раз демо-версию или скачайте ZIP-архив .

В ходе тестирования я увидел, что Chrome блокирует реакцию браузера по умолчанию на растягивание, но Firefox работает нормально.

Я надеюсь, что вы нашли эту статью полезной для себя. Я советую вам почитать другие статьи по перетягиваемым элементам и методам загрузки файлов, и узнать, как еще люди объединяют эти методы, чтобы создавать красивые пользовательские интерфейсы.

Данная публикация представляет собой перевод статьи «RESIZING AND CROPPING IMAGES WITH CANVAS » , подготовленной дружной командой проекта

Разрезание изображения на фрагменты с последующим их объединением в одну целую картинку — давний прием, вошедший в арсенал вёрстки веб-страниц. Предварительно подготовленный рисунок разрезают на части в графическом редакторе, сохраняют части как отдельные графические изображения, а затем соединяют их вместе с помощью таблицы.

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

Плюсы разрезания изображений

Создание ссылок

Отдельные рисунки при необходимости можно превращать в ссылки, причём для них можно назначать своё описание (атрибут title ) и альтернативный текст (атрибут alt ), который виден при отключении показа картинок в браузере или при наведении курсора мыши на изображение.

Эффект перекатывания

Набор отдельных фрагментов позволяет создавать эффект перекатывания — динамическое изменение одного рисунка на другой при наведении на него курсора мыши, и обратно на прежний, когда курсор уводится прочь.

Уменьшение объема файлов

Отдельными частями изображения удобней манипулировать, подбирая для них графический формат и его параметры таким образом, чтобы объём файла был минимален при сохранении приемлемого качества изображения. В итоге набор графических файлов будет занимать меньше места, и загружаться быстрее, чем один файл, содержащий целый рисунок.

Анимированный GIF

Использование анимированного GIF-a для изображений большого размера чревато существенным увеличением объёма файла. Но можно пойти на хитрость и заменить лишь часть изображения анимацией, а остальные фрагменты оставить статичными. При этом общий объём нескольких файлов будет гораздо меньше, чем анимирование одного изображения.

Особенности вёрстки

Изображения на веб-странице по своей природе прямоугольны, но, разрезав один рисунок на составляющие элементы, получим конструктор, из которого можно сложить другую фигуру. Это напоминает детские кубики, на одну из сторон которых наклеена картинка. Складывать подобные фигуры на веб-странице требуется в силу разных причин, например, вместо фрагмента изображения требуется добавить текст. Кроме того, некоторые рисунки можно заменить их фоновым аналогом и тогда конечное изображения, сохраняя свою целостность, будет занимать всю доступную область документа.

Психологический аспект

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

Подготовка изображения

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

Рис. 2.12. Исходное изображение

Теоретически, в данном случае можно обойтись и без разрезания, если использовать изображение-карту (теги и ). Однако этот вариант неприемлем в силу следующих соображений. При открытии любого раздела, иконка ему соответствующая, трансформируется, что в целом меняет изображение целиком (рис. 2.13). Если применять изображение-карту, то придётся заготовить четыре различных изображений (одну для главной страницы и еще три для каждого раздела), а это скажется в итоге на объёме пересылаемых данных, скорости отображения сайта и качестве рисунков.

Рис. 2.13. Вид изображения при открытии раздела

Теперь требуется решить, как разрезать изображение. Вариантов здесь может быть несколько — в конечном итоге это зависит от воли автора, предназначения рисунка и его содержимого.

Разрезание изображения

Разрезание и «сборку» рисунка лучше доверить специализированной программе, в частности, Adobe Photoshop, так что все упоминания об инструментах и меню относятся именно к этой программе.

Для удобства разрезания изображения вначале следует добавить направляющие линии, по которым затем и будет происходить разделение на фрагменты (рис. 2.14). Линию снизу добавим потом через стили, поэтому она не участвует в изображении.

Рис. 2.14. Добавляем в изображение направляющие

Теперь используем инструмент Slice (, активация клавишей K ) и по направляющим обводим требуемую прямоугольную область. Обозначенная область отмечается синей рамкой с номером фрагмента в левом верхнем углу. Размер областей можно изменять через специальный инструмент Slice Select — . Щелкаем мышью с этим инструментом по желаемому фрагменту — цвет рамки вокруг области становится желтым, а также изменяется тональность рисунка. После чего курсором мыши можно перемещать границы фрагмента за специальные маркеры по бокам и в углах области (рис. 2.15).

Рис. 2.15. Изменение области фрагмента

Для быстрого переключения между инструментами Slice и Slice Select нажмите и удерживайте клавишу Ctrl .

Во время изменения размеров фрагментов, следите за тем, чтобы области не пересекались друг с другом, и между ними не возникало промежутков. Хотя Photoshop сам отмечает подобные недочёты и принимает меры к их устранению, лучше держать всё под своим контролем.

После предварительного анализа и применения инструмента Slice, получим 13 фрагментов (рис. 2.16). Синим цветом показаны фрагменты созданные инструментом Slice, серым цветом показаны автоматически созданные фрагменты.

Рис. 2.16. Разрезанное на фрагменты изображение

Опасаться того, что получилось много рисунков, не стоит из-за того, что большая часть фрагментов содержит пустое изображение. Таким образом, число картинок сокращается, поскольку часть из них можно выкинуть, установив у ячейки таблицы размеры рисунка.

Использование таблицы для склейки фрагментов

После того, как фрагменты обозначены, требуется сохранить все изображения на диск. Для этого выбираем пункт меню File > Save for Web & Devices... (Файл > Сохранить для Web, Alt +Shift +Ctrl +S ) чтобы открыть панель оптимизации графики (рис. 2.17).

Рис. 2.17. Панель оптимизации изображений

С помощью инструмента Slice Select можно выбирать требуемый фрагмент и устанавливать для него персональные параметры вроде количества цветов, значение потерь качества, прозрачность и т.д. Допускается выделять сразу несколько фрагментов, удерживая клавишу Shift , что позволяет устанавливать для них одинаковые параметры.

По окончанию работы с фрагментами нажимаем кнопку «Save», указываем место на диске, куда будет сохранен HTML-документ, его имя, тип и настройки (рис. 2.18).

Рис. 2.18. Настройки при сохранении файла

Рисунки сохраняются автоматически в папку images, а их имя образуется от имени HTML-файла с добавлением номера фрагмента. Например, сохраняемое имя будет splash.html, тогда первый фрагмент называется splash_01.png, а последний — splash_13.png. Кроме того, создается файл spacer.gif, который представляет собой прозрачный рисунок размером 1х1 пиксел. Он используется для правильного формирования изображений в таблице.

Настройки, по которым строится HTML-код и формируются имена изображений можно изменить, если при сохранении файла в разделе Settings выбрать пункт Other... В окне параметров можно выбирать папку, куда сохранять рисунки, способ формирования имен файлов, а также HTML-кода (рис. 2.19).

Рис. 2.19. Панель для выбора выходных настроек

Полученный в результате сохранения файлов HTML-код, после небольшого редактирования представлен в примере 2.12.

Пример 2.12. Таблица для склейки изображений

Разрезание изображений copy

Данный код еще требует доработки, поскольку необходимо, чтобы горизонтальная серая и белая полоса занимали всю доступную ширину веб-страницы. Кроме того, часть фрагментов повторяется и от них можно избавиться.

Чтобы получить требуемый результат, введем слой с фоновым рисунком и нашу таблицу наложим поверх него. Такое изображение представлено на рис. 2.20.

Рис. 2.20. Фоновый рисунок, рамка вокруг приведена для наглядности

Теперь создаем нужный слой, назовем его toplayer , и в стилях указываем его параметры (пример 2.13).

Пример 2.13. Слой для формирования полос

XHTML 1.0 CSS 2.1 IE Cr Op Sa Fx

Нортландские байки

...

В данном примере параметры фона слоя устанавливаются через универсальное свойство background , которое определяет путь к графическому файлу, цвет заливки и повторяемость рисунка. Хотя цвет фона в таких случаях можно не указывать раз есть фоновый рисунок, но на случай того, что пользователь отключил загрузку изображений, лучше это сделать. Высота слоя также не является обязательным параметром из-за того, что таблица внутри слоя имеет заданную высоту.

Остается убрать рисунки с незначительными фрагментами, сохранив их размеры у ячеек таблицы (пример 2.14).

Пример 2.14. Окончательный код

XHTML 1.0 CSS 2.1 IE Cr Op Sa Fx

Нортландские байки

В данном примере изменён доктайп на строгий, что приводит к появлению небольшого отступа внизу изображений. Чтобы его убрать, в стиле к селектору IMG добавлен display : block .

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

Для управления положением рисунков родительскому классу menu зададим относительное позиционирование, а рисункам абсолютное. Тогда применение свойств left и top будет задавать координаты изображения относительно его родителя, т.е. слоя menu . Сам слой при этом можно легко перемещать и это никак не повлияет на его дочерние элементы (пример 2.15).

Пример 2.15. Вёрстка с помощью слоёв

XHTML 1.0 CSS 2.1 IE Cr Op Sa Fx

Нортландские байки

Здесь слой toplayer создаёт с помощью фонового изображения полосу заданной высоты на всю ширину окна. Слой menu задаёт родительский элемент, который выстраивается по центру поверх фоновой полосы. Положение изображений внутри слоя menu управляется свойствами top и left . За счёт активного применения стилей HTML-код сильно сокращается, рисунки независимы друг от друга, их можно легко сдвигать, менять на другие, добавлять новые. Это как раз тот случай, когда у таблицы при вёрстке нет ни единого шанса.

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

Эти методы могут быть очень полезными, если необходимо привести изображение к определенному размеру или создать предпросмотр, например, в ленте новостей и т.п.

Способ 1. Использование отрицательных полей

Изображение необходимо поместить в родительский элемент, в нашем случае - div. Родительский элемент должен быть обтекаемым элементом (или с заданной шириной). Метод не будет работать на блочных элементах или элементах во всю ширину.

Установим отрицательные поля для всех четырех сторон: top (сверху), right (справа), bottom (снизу) и left (слева). Отрицательные поля определяют, насколько изображение, находящееся в родительском элементе обрезано в каждом направлении. Затем установим свойство родительского элемента overflow (перекрывание) на hidden (скрыть), чтобы скрыть поля, которые находятся за областью вырезанного изображения.

    <div class = "crop" >

    Crop

    float : left ;

    overflow : hidden ;

    Crop img

    margin : -70px -50px -160px -175px ;

Способ 2. Использование абсолютного позиционирования

По этому методу задаем ширину и высоту родительского эдемента, свойство position (позиционирование) устанавливаем relative (относительным). Ширина и высота определяют размеры отображаемого поля. Для изображения внутри родительского элемента свойство позиционирования задаем absolute (абсолютным). Затем с помощью свойств top (сверху) и left (слева) задаем, какую часть изображения показывать.

    <div class = "crop" >

    Crop

    float : left ;

    overflow : hidden ;

    position : relative ;

    width : 270px ;

    height : 260px ;

    Crop img

    position : absolute ;

    top : -70px ;

    left : -175px ;

Способ 3. Использование свойства сlip

Этот способ самый простой, так как свойство clip определяет часть элемента, которую надо показать. Но этот метод имеет два недостатка.

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

Во-вторых, размер видимой области не уменьшается до размера вырезанной части, а остается равным размеру всего изображения (область за пределами вырезанной части только скрывается). Мы должны использовать абсолютное позиционирование, чтобы переместить видимую область в верхний левый угол родителя.

    <div class = "crop" >

    Crop

    float : left ;

    position : relative ;

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

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

Мы будем выполнять это через canvas , рисуя изображения в нужном размере, затем получая новые изображения. Многие браузеры поддерживают этот метод, так что мы можем начать прямо сейчас, рассчитывая лишь на небольшие ограничения в производительности.

Форматирование больших изображений может замедлить браузер или вообще привести его к остановке. Это заставляет нас задуматься об установке лимитов на загружаемые изображения. Если качаство полученных изображений важно, то нас может удивить то, во что их превращает браузер. В интернете можно найти несколько технологий для улучшения качества обработки картинок , но мы не будем рассматривать их здесь.

С этой целью мы начинаем работу!

Разметка

В нашем демо мы будем работать с одним заданным изображением:

Все! Другого HTML нам не нужно.

CSS

CSS-код тоже будет не очень большим. Определим стили для resize-container и самого изображения.

Resize-container { position: relative; display: inline-block; cursor: move; margin: 0 auto; } .resize-container img { display: block } .resize-container:hover img, .resize-container:active img { outline: 2px dashed rgba(222,60,80,.9); }

Теперь зададим позиции и стили для каждого ‘resize handles’. Это небольшие квадратики, находящиеся в углах изображений, которые мы перетаскиваем для изменения размеров картинки.

Resize-handle-ne, .resize-handle-ne, .resize-handle-se, .resize-handle-nw, .resize-handle-sw { position: absolute; display: block; width: 10px; height: 10px; background: rgba(222,60,80,.9); z-index: 999; } .resize-handle-nw { top: -5px; left: -5px; cursor: nw-resize; } .resize-handle-sw { bottom: -5px; left: -5px; cursor: sw-resize; } .resize-handle-ne { top: -5px; right: -5px; cursor: ne-resize; } .resize-handle-se { bottom: -5px; right: -5px; cursor: se-resize; }

JavaScript

Начнем с создания переменной и полотна в Canvas.

Var resizeableImage = function(image_target) { var $container, orig_src = new Image(), image_target = $(image_target).get(0), event_state = {}, constrain = false, min_width = 60, min_height = 60, max_width = 800, max_height = 900, resize_canvas = document.createElement("canvas"); }); resizeableImage($(".resize-image"));

Теперь создадим инициирующую функцию, которая будет запущена немедленно. Эта функция работает с контейнером, внутри которого расположено изображение, задает размер и копирует оригинал изображения для обрезки. Мы также присваиваем объект jQuery для того, чтобы сослаться на него позднее и задействовать операторы перемещения мыши для реагирования на перетаскивание кравдратиков.

Var resizeableImage = function(image_target) { // ... init = function(){ // Create a new image with a copy of the original src // When resizing, we will always use this original copy as the base orig_src.src=image_target.src; // Add resize handles $(image_target).wrap("

") .before("") .before("") .after("") .after(""); // Get a variable for the container $container = $(image_target).parent(".resize-container"); // Add events $container.on("mousedown", ".resize-handle", startResize); }; //... init(); }

Функции startResize и endResize дают браузеру понять, когда нужно начать обращать внимание на перемещение мыши, и когда это следует прекратить.

StartResize = function(e){ e.preventDefault(); e.stopPropagation(); saveEventState(e); $(document).on("mousemove", resizing); $(document).on("mouseup", endResize); }; endResize = function(e){ e.preventDefault(); $(document).off("mouseup touchend", endResize); $(document).off("mousemove touchmove", resizing); };

Перед началом отслеживания мыши необходимо просканировать текущие параметры клиента при запросе страницы. мы храним их в переменной event_state и используем далее при работе.

SaveEventState = function(e){ // Save the initial event details and container state event_state.container_width = $container.width(); event_state.container_height = $container.height(); event_state.container_left = $container.offset().left; event_state.container_top = $container.offset().top; event_state.mouse_x = (e.clientX || e.pageX || e.originalEvent.touches.clientX) + $(window).scrollLeft(); event_state.mouse_y = (e.clientY || e.pageY || e.originalEvent.touches.clientY) + $(window).scrollTop(); // This is a fix for mobile safari // For some reason it does not allow a direct copy of the touches property if(typeof e.originalEvent.touches !== "undefined"){ event_state.touches = ; $.each(e.originalEvent.touches, function(i, ob){ event_state.touches[i] = {}; event_state.touches[i].clientX = 0+ob.clientX; event_state.touches[i].clientY = 0+ob.clientY; }); } event_state.evnt = e; }

Функция resizing - самая важная. Она активируется при растягивании изображения. Каждый раз мы вычисляем новые размеры изображения в зависимости от нового положения квадратиков.

Resizing = function(e){ var mouse={},width,height,left,top,offset=$container.offset(); mouse.x = (e.clientX || e.pageX || e.originalEvent.touches.clientX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY || e.originalEvent.touches.clientY) + $(window).scrollTop(); width = mouse.x - event_state.container_left; height = mouse.y - event_state.container_top; left = event_state.container_left; top = event_state.container_top; if(constrain || e.shiftKey){ height = width / orig_src.width * orig_src.height; } if(width > min_width && height > min_height && width < max_width && height < max_height){ resizeImage(width, height); // Without this Firefox will not re-calculate the the image dimensions until drag end $container.offset({"left": left, "top": top}); } }

Затем добавим опцию для ограничения размеров изображения с помощью клавиши Shift или переменной.

Обратите внимание : Так как мы реально изменяем изображение, а не просто задаем новую длину и высоту, то стоит подумать о допустимом количестве использования функции resizeImage для контроля над производительностью сервера.

Новые размеры изображения

Рисовать изображения в Canvas так же просто, как и drawImage . Мы задаем высоту и длину картинки, а затем предоставляем оригинал. Также используем toDataURL для получения Base64-encoded версии результата операции.

Приведены все объяснения доступных для данной операции параметров.

ResizeImage = function(width, height){ resize_canvas.width = width; resize_canvas.height = height; resize_canvas.getContext("2d").drawImage(orig_src, 0, 0, width, height); $(image_target).attr("src", resize_canvas.toDataURL("image/png")); };

Слишком просто? Есть одна оговорка: изображение должно быть размещено на одном домене с нашей страницей, либо на сервере должна быть активирована функция . Если это не так, то у Вас возникнут проблемы с ‘tainted canvas’.

Увеличение через другие вершины

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

Когда мы растягиваем изображение, соседние к удерживаемому углу тоже должны двигаться, а вот противоположный конец картинки должен быть зафиксирован.

Мы можем поменять код так, чтобы при растягивании картинки за любой угол она менялась. Давайте обновим функцию resizing:

Resizing = function(e){ var mouse={},width,height,left,top,offset=$container.offset(); mouse.x = (e.clientX || e.pageX || e.originalEvent.touches.clientX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY || e.originalEvent.touches.clientY) + $(window).scrollTop(); // Position image differently depending on the corner dragged and constraints if($(event_state.evnt.target).hasClass("resize-handle-se")){ width = mouse.x - event_state.container_left; height = mouse.y - event_state.container_top; left = event_state.container_left; top = event_state.container_top; } else if($(event_state.evnt.target).hasClass("resize-handle-sw")){ width = event_state.container_width - (mouse.x - event_state.container_left); height = mouse.y - event_state.container_top; left = mouse.x; top = event_state.container_top; } else if($(event_state.evnt.target).hasClass("resize-handle-nw")){ width = event_state.container_width - (mouse.x - event_state.container_left); height = event_state.container_height - (mouse.y - event_state.container_top); left = mouse.x; top = mouse.y; if(constrain || e.shiftKey){ top = mouse.y - ((width / orig_src.width * orig_src.height) - height); } } else if($(event_state.evnt.target).hasClass("resize-handle-ne")){ width = mouse.x - event_state.container_left; height = event_state.container_height - (mouse.y - event_state.container_top); left = event_state.container_left; top = mouse.y; if(constrain || e.shiftKey){ top = mouse.y - ((width / orig_src.width * orig_src.height) - height); } } // Optionally maintain aspect ratio if(constrain || e.shiftKey){ height = width / orig_src.width * orig_src.height; } if(width > min_width && height > min_height && width < max_width && height < max_height){ // To improve performance you might limit how often resizeImage() is called resizeImage(width, height); // Without this Firefox will not re-calculate the the image dimensions until drag end $container.offset({"left": left, "top": top}); } }

Теперь мы проверяем, какой из resize-handle был задействован, и применяем необходимые изменения.

Перемещение изображения

Теперь, когда мы можем менять размер картинки, Вы, наверное, заметили, что оно иногда "съезжает". Необходимо добавить возможность перемещения объекта в центр рамки. Давайте немного дополним нашу инициализирующую функцию.

Init = function(){ //... $container.on("mousedown", "img", startMoving); }

Теперь мы добавляем функции startMoving и endMoving , похожие на startResize и endResize .

StartMoving = function(e){ e.preventDefault(); e.stopPropagation(); saveEventState(e); $(document).on("mousemove", moving); $(document).on("mouseup", endMoving); }; endMoving = function(e){ e.preventDefault(); $(document).off("mouseup", endMoving); $(document).off("mousemove", moving); };

В функции moving мы должны проработать позицию левого верхнего квадратика. Она должна быть равно начальной с небольшим смещением, вычисленным по перемещению других квадратов.

Moving = function(e){ var mouse={}; e.preventDefault(); e.stopPropagation(); mouse.x = (e.clientX || e.pageX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY) + $(window).scrollTop(); $container.offset({ "left": mouse.x - (event_state.mouse_x - event_state.container_left), "top": mouse.y - (event_state.mouse_y - event_state.container_top) }); };

Обрезка изображения

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

Для этого нужно добавить такой HTML код:

Необходимо помнить, что рамка должна всегда быть отличного цвета от фона страницы, иначе могут возникнуть проблемы.

Overlay { position: absolute; left: 50%; top: 50%; margin-left: -100px; margin-top: -100px; z-index: 999; width: 200px; height: 200px; border: solid 2px rgba(222,60,80,.9); box-sizing: content-box; pointer-events: none; } .overlay:after, .overlay:before { content: ""; position: absolute; display: block; width: 204px; height: 40px; border-left: dashed 2px rgba(222,60,80,.9); border-right: dashed 2px rgba(222,60,80,.9); } .overlay:before { top: 0; margin-left: -2px; margin-top: -40px; } .overlay:after { bottom: 0; margin-left: -2px; margin-bottom: -40px; } .overlay-inner:after, .overlay-inner:before { content: ""; position: absolute; display: block; width: 40px; height: 204px; border-top: dashed 2px rgba(222,60,80,.9); border-bottom: dashed 2px rgba(222,60,80,.9); } .overlay-inner:before { left: 0; margin-left: -40px; margin-top: -2px; } .overlay-inner:after { right: 0; margin-right: -40px; margin-top: -2px; } .btn-crop { position: absolute; vertical-align: bottom; right: 5px; bottom: 5px; padding: 6px 10px; z-index: 999; background-color: rgb(222,60,80); border: none; border-radius: 5px; color: #FFF; }

Также обновим JavaScript код:

Init = function(){ //... $(".js-crop").on("click", crop); }; crop = function(){ var crop_canvas, left = $(".overlay").offset().left - $container.offset().left, top = $(".overlay").offset().top - $container.offset().top, width = $(".overlay").width(), height = $(".overlay").height(); crop_canvas = document.createElement("canvas"); crop_canvas.width = width; crop_canvas.height = height; crop_canvas.getContext("2d").drawImage(image_target, left, top, width, height, 0, 0, width, height); window.open(crop_canvas.toDataURL("image/png")); }

Функция crop похожа на resizeImage . Различия лишь в том, что мы получаем размеры и позицию обрезки из положения рамки.

Для обрезки необходимо задать девять параметров оператора drawImage в canvas. Первый - исходное изображение. Следующие четыре - место, задействованное под операцию. Еще четыре - координаты места, в котором стоит начать рисование в canvas, и какого размера будет изображение.

Добавление прикосновений и распознавания жестов

Мы создали поддержку мышки. Давайте не будем обделять вниманием и мобильные устройства.

Для mousedown и mouseup есть эквивалентные операторы - touchstart и touchend , а для mousemove есть touchmove . Нужно быть внимательным, чтобы не перепутать их с touchup и touchdown (А то будет смешно).

Давайте добавим touchstart и touchend везде, где у нас есть mousedown , и mouseup вместе с touchmove туда, где есть mousemove .

// In init()... $container.on("mousedown touchstart", ".resize-handle", startResize); $container.on("mousedown touchstart", "img", startMoving); //In startResize() ... $(document).on("mousemove touchmove", moving); $(document).on("mouseup touchend", endMoving); //In endResize()... $(document).off("mouseup touchend", endMoving); $(document).off("mousemove touchmove", moving); //In startMoving()... $(document).on("mousemove touchmove", moving); $(document).on("mouseup touchend", endMoving); //In endMoving()... $(document).off("mouseup touchend", endMoving); $(document).off("mousemove touchmove", moving);

Так как мы подключили мобильные устройства, то есть вероятность использования пользователем жеста "сжатия" пальцами изображения для его уменьшения. Есть одна очень удобная библиотека под названием Hammer, позволяющая распознавать множество жестов. Но, так как нам нужен только один, напишем его коротко без всяких дополнительных скриптов.

Вы, наверное, заметили, что функция saveEventState уже хранится в информации о прикосновениях. Теперь она нам пригодится.

Сначала мы проверяем наличие "двух прикосновений" и расстояние между ними. Также мы смотрим на то, уменьшается ли расстояние между ними по ходу движения. Теперь обновим moving:

Moving = function(e){ var mouse={}, touches; e.preventDefault(); e.stopPropagation(); touches = e.originalEvent.touches; mouse.x = (e.clientX || e.pageX || touches.clientX) + $(window).scrollLeft(); mouse.y = (e.clientY || e.pageY || touches.clientY) + $(window).scrollTop(); $container.offset({ "left": mouse.x - (event_state.mouse_x - event_state.container_left), "top": mouse.y - (event_state.mouse_y - event_state.container_top) }); // Watch for pinch zoom gesture while moving if(event_state.touches && event_state.touches.length > 1 && touches.length > 1){ var width = event_state.container_width, height = event_state.container_height; var a = event_state.touches.clientX - event_state.touches.clientX; a = a * a; var b = event_state.touches.clientY - event_state.touches.clientY; b = b * b; var dist1 = Math.sqrt(a + b); a = e.originalEvent.touches.clientX - touches.clientX; a = a * a; b = e.originalEvent.touches.clientY - touches.clientY; b = b * b; var dist2 = Math.sqrt(a + b); var ratio = dist2 /dist1; width = width * ratio; height = height * ratio; // To improve performance you might limit how often resizeImage() is called resizeImage(width, height); } };

На основе этих данных мы уменьшаем или увеличиваем наше изображение и корректируем его высоту и длину.

На этом все. Вы можете открыть