Архив категории «Статьи»

  • От простого к сложному

    Насколько сложной может быть форма из двух полей, отправляющая данные аяксом на сервер? С современными фреймворками вроде jQuery, код может выглядеть примерно так:

    $.post('my-url.php', $('form#my-form').serialize());
    

    Но это минимальный код; код из серии «создаём свой блог/цмс за 5 минут», которым так любят заманивать создатели всяких движков и фреймворков. А что нужно сделать, чтобы такая форма стала удобной и понятной для конечного пользователя, то есть «человеческой»?

    Недавно мы запустили новый скидочный проект BigBuzzy. Так как на рынке уже существует масса подобных проектов, единственный способ выделиться из толпы — это сделать проект очень удобным для пользователя, как с концептуальной точки зрения (не надо ждать, пока наберётся определённая группа; удобные способы оплаты), так и с технологической.

    Одна из фич проекта — отправка купленного купона себе на телефон по СМС. Так как на месте проведения акции нужно предъявить только номер купона, нет особого смысла печатать его на бумаге, можно показать и с телефона. Увидеть форму в действии можно на бигбаззи, купив 10 купонов на этой странице. В этой форме как раз два поля: код и номер телефона. Посмотрим, что можно в ней улучшить.

    Алгоритм работы

    Прежде, чем приступать к работе, нам нужно определиться, какие функции несёт в себе эта форма. Когда пользователь оплатил купон, ему предоставляется на выбор два действия: распечатать или отправить по СМС. Чтобы отправить по СМС, нужно знать номер телефона пользователя. В принципе, можно заставить его ввести этот номер при регистрации, однако это с большой долей вероятности отпугнёт потенциальных покупателей: с чего это вдруг сайт требует с меня номер телефона? Поэтому форму будем показывать только тогда, когда пользователь нажмёт на кнопку «Отправить по СМС»: в этом случае ему будет понятно, почему требуется ввести номер телефона.

    А что делать, если пользователь уже ввёл номер при отправке первого купона и хочет отправить второй? Логично, что он захочет отправить купон на тот же самый номер. Нужно ли в этом случае показывать форму? Решаем, что нужно. Во-первых, пользователь может захотеть отправить купон своему другу в качестве подарка или этот друг только что позвонил и попросил выручить, докупив ещё два купона на боулинг. Заставлять пользователя идти в Личный кабинет и менять там номер телефона как-то уж совсем неправильно. Во-вторых, появляющаяся форма будет своеобразным подтверждением действия (вдруг он случайно нажал на эту кнопку?). Поэтому на нажатие конпки будем запускать анимацию появления формы, а повторное нажатие на эту же кнопку отправить данные на сервер:

    $(document.body).delegate('.coupon-button', 'click', function(/* Event */ evt) {
    	var elem = $(this);
    	if (elem.hasClass('coupon-button-sms')) {
    		var coupon = elem.closest('.coupon');
    		if (!coupon.length)
    			return;
    
    		if (!coupon.hasClass('coupon-sms-mode')) {
    			// переключаем купон в режим отправки СМС
    			switchSMSMode(coupon);
    		} else  {
    			// пытаемся отправить данные на сервер
    			tryToSendSMS(coupon);
    		}
    	}
    });
    

    Функцию анимации появления формы с вводом номера телефона я тут не привожу, так как она довольно большая и скучная (желающие могут посмотреть исходник). Первая проблема, которая может возникнуть на этом шаге: пользователь может случайно дважды кликнуть на кнопку, что приведёт к моментальной отправке СМС, если там уже был вбит номер. Поэтому нужно поставить блокировку на выполнение действия. Это стандартный подход в анимированных интерфейсах, который позволяет избежать случайных нажатий или даже поломки всего интерфейса (может наложиться несколько анимаций друг на друга). Введём переменную is_animating, которая будет равна true в момент анимации. Обнулять эту переменную будем после завершения анимации через callback-функцию для switchSMSMode():

    $(document.body).delegate('.coupon-button', 'click', function(/* Event */ evt) {
    	var elem = $(this),
    		is_animating = false;
    
    	if (elem.hasClass('coupon-button-sms')) {
    		var coupon = elem.closest('.coupon');
    		if (!coupon.length || is_animating)
    			return;
    
    		if (!coupon.hasClass('coupon-sms-mode')) {
    			// переключаем купон в режим отправки СМС
    			is_animating = true;
    			switchSMSMode(coupon, function(){
    				is_animating = false;
    			});
    		} else  {
    			// пытаемся отправить данные на сервер
    			tryToSendSMS(coupon);
    		}
    	}
    });
    

    Ввод данных

    Форму мы показали, теперь нужно упростить пользователю ввод данных. Тут надо помнить, что существует множество моделей поведения пользователя. Кто-то быстро введёт код и переключится табом на следующее поле, кто-то будет минуту набирать три цифры и схватиться за мышку, чтобы переключиться на следующее поле, кто-то будет ожидать, что введённая последовательность из 10 цифр автоматически распределиться между полями. Нужно постараться удовлетворить максимальное количество пользователей. Поэтому будем следить за полем «код», и когда там будет 3 цифры — автоматически перебросим фокус на поле с номером:

    $('.phone-code').keyup(function(){
    	if (this.value.length == 3)
    		$(this).next('.phone-num').focus();
    });
    

    Так делают многие и так делать ни в коем случае нельзя. Если пользователь допустил ошибку в коде, то попытка исправить её используя только клавиатуру превратиться в сущий ад: например, нажатие на стрелки клавиатуры всегда будет приводить к перебросу фокуса, потому что сработает событие событие keyup, а в поле уже будет 3 символа. Чтобы сделать поведение формы более естественным, нужно следить за вводом данных: перебрасывать фокус будем только если в предыдущем нажатии на кнопку (last_length) было меньше символов, чем в этом нажатии, количество символов равно трём и каретка находится в самом конце поля:

    $('.phone-code').bind('keyup change', function(/* Event */ evt) {
    	var field = $(evt.target),
    		last_length = field.data('last_length') || 0,
    		cur_length = field.val().length,
    		max_length = parseInt(field.attr('maxlength')) || 3,
    		selection = getSelectionRange(field[0]);
    
    	if (cur_length > last_length && cur_length == max_length && selection && selection.start == cur_length) {
    		field.next('.phone-number').focus();
    	}
    
    	field.data('last_length', cur_length);
    });
    
    function getSelectionRange(elem) {
    	if ('selectionStart' in elem) { // W3C's DOM
    		return {
    			start: elem.selectionStart,
    			end: elem.selectionEnd
    		};
    	} else if (document.selection) { // IE
    		elem.focus();
    
    		var range = document.selection.createRange(),
    			content = elem.value;
    
    		if (range === null) {
    			return {
    				start: 0,
    				end: content.length
    			};
    		}
    
    		var re = elem.createTextRange();
    		var rc = re.duplicate();
    		re.moveToBookmark(range.getBookmark());
    		rc.setEndPoint('EndToStart', re);
    
    		return {
    			start: rc.text.length,
    			end: rc.text.length + range.text.length
    		};
    	} else {
    		return null;
    	}
    }
    

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

    $('.phone-code').bind('keyup change', function(/* Event */ evt) {
    	var field = $(evt.target),
    		last_length = field.data('last_length') || 0,
    		cur_length = field.val().length,
    		max_length = parseInt(field.attr('maxlength')) || 3,
    		selection = getSelectionRange(field[0]);
    
    	if (cur_length > last_length && cur_length == max_length && selection && selection.start == cur_length) {
    		var num_field = field.next('.phone-number');
    
    		// временно блокируем Tab на следующем поле
    		num_field.data('tab_locked', true);
    		setTimeout(function() {
    			num_field.data('tab_locked', false);
    		}, 1000);
    
    		num_field.focus();
    	}
    
    	field.data('last_length', cur_length);
    });
    
    $('.phone-number').bind('keydown keyup keypress', function(/* Event */ evt) {
    	var field = $(evt.target);
    	if (field.data('tab_locked') === true && evt.keyCode == 9) {
    		// блокируем работу клавиши Tab
    		evt.preventDefault();
    	}
    });
    

    Валидация данных

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

    Вводить жёсткий формат записи числовых данных вроде номера телефона или кредитной карты — первый признак полового бессилия разработчика. Для номера телефона нам нужно 10 цифр (код + сам номер), и не важно, написаны цифры слитно или разделены пробелами или дефисами. Пользователь должен написать номер в привычном ему формате, чтобы легче было проверять правильность. Поэтому при валидации удалим из полей все не числовые символы и проверим длину того, что осталось:

    function validatePhoneNumber(coupon) {
    	coupon = $(coupon);
    
    	var re_num = /[^0-9]/g,
    		phone_code = coupon.find('.phone-code'),
    		phone_num = coupon.find('.phone-number'),
    		is_valid_code = phone_code.val().replace(re_num, '').length == 3,
    		is_valid_num = phone_num.val().replace(re_num, '').length == 7;
    
    	if (!is_valid_code)
    		blinkField(phone_code);
    	if (!is_valid_num)
    		blinkField(phone_num);
    
    	return is_valid_code && is_valid_num;
    }
    

    Если введённые данные не верны, то мы должны сообщить об этом пользователю. Пространство у нас очень ограничено, поэтому будем «моргать» полем, указывая, что там что-то не так. Функция добавляет и удаляет определённый класс у элемента через заданный промежуток времени, в CSS укажем этому классу красный цвет:

    function blinkField(fld) {
    	fld = $(fld);
    	if (fld.data('blink_timer'))
    		clearInterval(fld.data('blink_timer'));
    
    	var blink_count = 6;
    
    	fld.data('blink_timer', setInterval(function() {
    		fld.toggleClass('coupon-field-warning');
    		if (blink_count-- < 0) {
    			fld.removeClass('coupon-field-warning');
    			clearInterval(fld.data('blink_timer'));
    		}
    	}, 100));
    }
    

    Отправка данных

    Данные от пользователя получили и проверили, настало время отправить их на сервер. Опять же, тут есть две потенциальные проблемы. Первая — это слишком медленный интернет. Нужно показать пользователю, что процесс запущен и скоро что-то произойдёт. Вторая проблема не так очевидна — это слишком быстрый интернет. Настолько быстрый, что пользователь может и не понять, а произошло ли что-то, отправились ли данные или возникла ошибка? Чтобы избавиться от этих проблем, нужно показать показать некий индикатор процесса, причём этот индикатор должен искусственно замедлить получение фидбэка от сервера. Он должен отображаться на странице некоторое время, чтобы пользователь успел понять, что запущен некий процесс.

    Кто-то решает эту задачу так: показывает индикатор, ждёт определённое время и уже после этого отправляет запрос на сервер, а после получения ответа прячет индикатор. Так делать не правильно: в случае с медленным интернетом таймаут слишком сильно увеличивается; сам запрос мог бы запросто отработать за то время, пока показывается индикатор. Поэтому мы устроим небольшую гонку: что последним закончится выполняться — таймер индикатора или запрос на сервер — то и покажет фидбэк пользователю:

    function sendSMSRequest(coupon) {
    	coupon = $(coupon);
    	var phone_code = coupon.find('.phone-code').val(),
    		phone_num = coupon.find('.phone-number').val();
    
    	coupon.addClass('coupon-preloader');
    	// запускаем индикатор
    	preloader.start();
    
    	// два условия отображения фидбэка: закончился таймер и пришёл ответ от сервера
    	var timer_reached = false,
    		response_received = false;
    
    	function checkStatus() {
    		if (timer_reached && response_received) {
    			// останавливаем индикатор
    			preloader.start();
    			coupon.removeClass('coupon-preloader');
    
    			// прячем форму с номером телефона
    			switchSMSMode(coupon);
    		}
    	}
    
    	// устраиваем гонку между таймером и запросом
    	setTimeout(function() {
    		timer_reached = true;
    		checkStatus();
    	}, 1000);
    
    	$.get(sms_url, {
    		id: coupon.attr('id'),
    		action: 'send_sms',
    		phone: phone_code + phone_num
    	}, function(data) {
    		response_received = true;
    		checkStatus();
    	}, 'json');
    }
    

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

    code-example

  • Как создавалась Айчиталка. Часть 1: движок

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

    Пока пишу с корпоративного аккаунта, а если суппорт Хабра таки отдуплится и поправит баги, то буду писать со своего.

  • Спрайты в вебе. Часть 1

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

    Что такое спрайты?

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


    Спрайт с сайта «Житлобуд»

    Такой подход дает сразу несколько преимуществ. Во-первых, объем одного файла как правило меньше, чем сумма объемов отдельных файлов. Это можно объяснить снижением издержек на сохранение одного изображения: всякие там заголовки, указывающие на формат изображения, или общая палитра цветов, если речь идет об индексированном PNG или GIF. Бородатые кодеры могут вспомнить рекомендации 90-х годов по разбивке одного большого файла на несколько мелких, чтобы создать иллюзию у пользователя, что сайт грузится быстрее 🙂 Такой подход создавал именно иллюзию, так как суммарный объем нескольких файлов был больше, чем объем одного файла, а сама картинка грузилась дольше.

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

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

    Назад в прошлое

    Будучи неопытным юнцом, я очень любил играть в игры. Больше всего меня интересовало то, как же эти игры делаются. Тогда еще не было повсеместного использования 3D-графики, все игры, в которые я играл, в основном были 2D-аркадами (Another World, Commander Keen, Moles, Prince of Persia, эх…), активно использующими спрайты. Вообще, слово «спрайт» получило широкое распространение именно благодаря видеоиграм, и именно видеоигры больше всего повлияли на меня как на веб-разработчика.

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

    Раскадровка


    Слон с сайта «Паритет98»

    Раскадровка, по сути, представляет собой набор кадров для анимации, сохраненных в одном файле. Для такого спрайта пишется контроллер на JavaScript, который управляет скоростью и направлением смещения спрайта. По сравнению с обычной GIF-анимацией это дает нам следующие преимущества:

    1. Можно использовать JPEG или полупрозрачный PNG.
    2. Более широкие возможности по оптимизации изображения.
    3. Можно самостоятельно управлять скоростью и направлением анимации, имея один и тот же набор кадров (то есть используя всего одно изображение).

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

    Затем нужно точно совместить изображения в кадрах, чтобы при анимации не были заметны рывки. Если все сделаете правильно, то на создание JS-контроллера уйдет минимум сил и времени.

    Циклический спрайт

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

    Тут, конечно, руки так и чешутся использовать GIF-анимацию, однако перфекционистов вроде меня может не устроить плавность движения. Сравните:

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

    К минусам данного подхода можно отнести более высокие требования системным ресурсам, поэтому если у вас много прелоудеров на странице нужно десять раз подумать, что лучше использовать, GIF или JS.

    Слои

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

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

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

    Естественно, этот пример довольно простой и примитивный: уверен, что многие так и поступили бы с этой картинкой. Наибольший эффект от такого типа спрайтов достигается в том случае, когда сердцевина имеет не прямоугольную форму. Хороший пример: раздел «Отделение Imperia» сайта Imperia Private Banking:

    imperia-branch

    Огромный узор на двери был разделен на 2 слоя: сердцевину и окантовку, каждый слой был по-своему оптимизирован, что в итоге позволило мне сэкономить около 200 КБ. Как быстро разделить изображение на два таких слоя я писал в статье «Про PNG. Часть 2».

    Векторные маски

    К сожалению, не всегда подобные картинки можно эффективно разделить по слоям. Например, когда сердцевина не прямоугольной формы содержит фотографическое изображение. В этом случае на помощь приходят современные браузерные технологии, а именно SVG, Canvas и VML. С помощью них можно наложить векторные маски на прямоугольную JPEG-картинку, добившись необходимого внешнего вида. Для этих целей я написал специальную библиотеку под названием ictinus, которая занимается решением таких задач. Хороший пример использования использования спрайтовых масок (и моей библиотеки :)) — главная страница сайта «Аймобилко»:

    imobilco

    В середине страницы находится красивый тизер продукта, у которого изображение должно принимать «неправильную» форму (в данном случае — коробка компакт-диска с загнутым углом). Так как фон не однородный (и тоже, кстати, меняется), единственным очевидным способом решения этой задачи было создание отдельного PNG-изображения весом более 100 КБ для каждого такого продукта. Меня такой расклад не устраивал, поэтому я решил в качестве эксперимента применить ictinus (на тот момент был в состоянии очень сырой альфа-версии). На прямоугольное JPEG-изображение накладывается векторная маска (в зависимости от типа продукта), затем под эту картинку ставится PNG-тень. Результат оказался более чем убедительным. Помимо сильной экономии на траффике пользователя я сэкономил нервы заказчику: теперь ему достаточно любому продукту в админке поставить галочку «Показывать на главной», а не готовить в фотошопе новые картинки.

    ***

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

    Скачать JS-котроллер для спрайтовой анимации



    Метки: , , , ,
  • Фотошоп для веб-разработчика. Каналы и маски: теория

    В скринкасте к статье «Про PNG, часть 4» я продемонстрировал, так сказать, пять минут своей работы по оптимизации изображения, вогнав в ступор даже видавших виды студийных арт-директоров🙂 Тогда я написал, что не планирую объяснять, что и зачем я там делал. Сейчас я собираюсь исправиться и подробно описать все показанные способы оптимизации графики.

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

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

    about-masks

    Маски бывают двух типов — векторные и растровые. Первый тип нас пока не интересует, рассматрим второй тип. Растровая маска представляет из себя черно-белое изображение. Черный цвет означает, что соответствующий пиксель изображения будет абсолютно прозрачным, белый — абсолютно непрозрачным. Все промежуточные цвета соответствуют разным степеням прозрачности. Таким образом, всего у изображения может быть 256 градаций прозрачности, а не 100, как привыкли думать веб-разработчики.

    Хозяйке на заметку

    Утверждение про 256 градаций справедливо для изображений с 8 битами на канал (28 = 256), для изображения с 16 битами на канал будет 65 536 градаций.

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

    Каналы — это тоже черно-белые изображения, но отвечают они за интенсивность цветового компонента в текущей цветовой модели. Например, в цветовой модели RGB есть три канала: красный, зеленый и синий. Соответственно, пиксели в этих каналах отвечают за интенсивность того или иного цвета. Посмотреть на них можно в палитре Channels.

    about-channels

    Казалось бы, что общего между каналами и масками, помимо того, что они черно-белые? Ну, во-первых, они все отображаются в палитре Channels:) Второе, и самое важное, Ctrl+клик или ⌘-клик по слою с каналом/маской создаст выделение. И наоборот — из выделения можно создать канал с маской и подкорректировать её с помощью инструментов рисования.

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

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

    Логотип отеля «Феликс Завойский»

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

    Переходим в палитру Channels и смотрим внимательно на каналы.

    demo2

    Обратите внимание на зеленый канал (просто кликните на него): мы видим практически ровную черно-белую маску.

    demo3

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

    Выделяем все изображение (Ctrl+A или ⌘+A), копируем, создаем новый документ и вставляем туда нашу картинку. Помним, что черный цвет — абсолютно прозрачные пиксели, белый — не прозрачные. Но для начала нам нужно убедиться, что на фоне сплошной черный цвет, без каких-либо незаметных глазу переходов. Для этого в палитре Layers жмем на иконку Create new fill or adjustment layer и выбираем Threshold. Дизайнеры почему-то незаслуженно обходят стороной этот настроечный слой, хотя лично для меня это архиважный инструмент для анализа изображения. Смысл его довольно прост: с помощью ползунка мы устанавливаем пороговое значение яркости изображения (threshold по-английски «пороговое значение»). Все, что выше (правее) этого значения будет отображаться абсолютно белым, что ниже (левее) — абсолютно черным. Передвигаем ползунок в крайнее левое положение и начинаем потихонечку двигать его вправо (а вы знаете, что в фотошопе в полях ввода цифр можно увеличивать и уменьшать значения с помощью стрелок вверх и вниз на клавиатуре?). Так и есть — до третьего уровня мы видим, что фон меняется:

    demo4

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

    Сам слой Threshold нам больше не нужен — удаляем его. Теперь необходимо сделать так, чтобы серые пиксели логотипа стали белыми, а фон — абсолютно черным. Для этого очень хорошо подходит инструмент Levels. Вызываем его через Image → Adjustments → Levels.

    demo5

    Этот инструмент чем-то похож на Threshold с той разницей, что сохраняет полутона. У него уже три ползунка: тени (shadows), средние тона (midtones) и светлые тона (highlights). Относительно черно-белого изображения: все пиксели, яркость которых ниже (левее) точки теней, будут черными, выше (правее) светлых тонов станут белыми. Точка средних тонов управляет, как распределить яркость между белым и черным цветом (больше темных или светлых пикселей). Нам необходимо сделать так, чтобы серые пиксели логотипа стали точкой ярких тонов, а черный фон, располагающийся ближе к низу изображения (помните, что показал нам Threshold?) — точной теней. Выбираем черную пипетку dark и тыкаем в черный фон ближе к низу изображения, затем выбираем светлую пипетку light и тыкаем в серый цвет логотипа. В итоге гистограмма уровней должна выглядеть вот так:

    demo6

    Наша маска готова. Переводим изображение в модель RGB (Image → Mode → RGB Color) и в палитре Channels делаем Ctrl+клик или ⌘-клик на любом канале. В палитре Layers создаем новый слой и заливаем выделение цветом логотипа:

    demo7

    А теперь коротко: что же произошло и почему это сработало. Проанализировав каналы, мы выяснили, что именно на зеленом компоненте содержится меньше всего информации о градиентном фоне (что вполне логично, фон ведь красный), при этом сам логотип смотрится довольно контрастно. Этот канал послужил нам основой для создания маски. Мы увеличили яркость серых пикселей, сделав их белыми, с помощью инструмента Levels. А инструмент Threshold помог нам найти незаметные цветовые переходы, от которых также избавил нас инструмент Levels (в качестве нижней границы теней мы выбрали «самый яркий» черный цвет, сделав, тем самым, менее яркие оттенки тоже черными).

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

    Что следует запомнить из этого урока:

    • Каналы и маски — инструмент не только для дизайнеров. Экспериментируйте с разными цветовыми моделями (CMYK, Lab) для достижения нужного результата в сложных случаях.
    • Обязательно освойтесь с Levels и Threshold. Вы должны четко понимать, как они работают, когда и ради чего их нужно вызывать. Поэкспериментируйте с Curves.

    В следующей статье на практике рассмотрим использование масок для оптимизации изображений.

  • Фотошоп для веб-разработчика. Кнопочки

    В этой статье я полностью продемонстрирую процесс создания красивых кнопочек — от получения макета до готового решения.

    button-example

    Дизайнер хочет, чтобы на сайте было 10 разных цветов таких кнопок, причем при наведении курсора цвет должен немного бледнеть. Вариант «в лоб» — нарезать кучу картинок под каждый цвет — нам, как правильным разработчикам, не подходит, поэтому мы создадим специальную маску, под которую будем класть разные градиенты. В итоге можно будет обойтись всего двумя небольшими картинками. А если постараться — то и одной.

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

    ss11

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

    Давайте рассмотрим подробнее слой shape. Это специальный векторный слой, состоящий из двух частей: цвет и маска.

    ss3

    Дважды кликнув на первый квадратик мы можем поменять цвет шэйпа, кликнув на второй — отредактировать его внешний вид.

    Кликаем на маску и выбираем Direct Selection Tool:

    ss2

    Выделяем все верхние узлы:

    ss4

    Теперь с помощью стрелок вверх/вниз на клавиатуре перемещаем узлы в нужное положение, чтобы верхняя граница стала четкой. Помните, что шаг смещения зависит от текущего масштаба. Я рекомендую увеличить масштаб до 500%. Вы можете временно включать/отключать отображение векторной рамки с помощью Ctrl+H/⌘+H, это поможет вам точнее разместить узлы.

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

    ss5

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

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

    Делаем правый клик на иконке fx у дубликата и в меню выбираем Create Layers. Мы превратили все эффекты в полноценные слои, с которыми можно работать. 

    Сразу же обращаем внимание на то, что обводка стала чуть темнее:

    ss7

    Это объясняется особенностями работы фильтров в фотошопе (вам еще должны были показать сообщение о том, что можете получить не то, чего ожидали). Для того, чтобы добиться правильных цветов, дизайнеру пришлось в качестве цвета обводки указать градиент от светло-зеленого до темно-зеленого.  Нас это не устраивает, потому что зеленый градиент будет странно смотреться на красной кнопке 🙂

    Исправим эту проблему. Выделяем слой с обводкой (у меня он называется shape copy’s Inner Stroke) и обесцвечиваем его: Image → Adjustments → Desaturate. Обводка стала черно-белой — как раз то, что нам нужно — но все равно слишком темной. Снизим прозрачность у обводки: выставим параметр Opacity в 60%. Теперь дубликат практически не отличить от оригинала (учитывайте, что на скриншоте масштаб 400%):

    ss8

    Тень (shape copy’s Drop Shadow) тоже имеет зеленоватый оттенок, обесцветим и ее с помощью команды Desaturate.

    Осталось вырезать внутренности кнопки, чтобы тень была поверх белого фона, а обводка — поверх прозрачного. Сейчас все слои эффектов, кроме Drop Shadow, находятся в режиме маски отсечения (clipping mask): слои накладываются поверх пикселей основного слоя. Нам нужно перевести их в нормальный режим, для этого делаем Alt-click на линии между слоями shape copy и shape copy’s Gradient Fill. Получим вот такой результат:

    ss9

    Отключаем слои с Color Fill и Gradient Fill — они нам больше не нужны. Теперь нам нужно наложить на оставшиеся слои маски. Делаем Ctrl-click/⌘-click по маске слоя shape copy, выделяем слой Layer 1 и нажимаем на иконку mask-icon в палитре слоев. Проделываем такую же операцию для слоев shape copy’s Inner Stroke и shape copy’s Drop Shadow. Получим вот такой результат:

    ss10

    Конечно же, это не то, что мы хотели получить: прозрачной должна быть внутренняя часть кнопки, а не внешняя. Чтобы исправить это, нужно инвертировать маску в слоях shape copy’s Drop Shadow и Layer 1. Выделяем маску каждого слоя и выполняем Ctrl+I/⌘+I, отключаем слой shape copy. Получилось то, что нам нужно:

    ss111

    Теперь необходимо обрезать ненужные белые пиксели. Не торопитесь выбирать инструмент Crop и прицельно выделять блок — есть более простой и красивый способ. Выбираем Image → Trim, в окне выбираем Based On: Top Left Pixel Color .

    Осталось подготовить картинку для использования на странице. Для создания кнопки хорошо подойдет способ «звездочка», нужно только правильно спозиционировать уголки. Объединяем все слои (Layer → Merge Visible) и вызываем Filter → Other → Offset. В диалоговом окне указываем смещение по горизонтали и вертикали в 9 пикселей, в качестве значения Undefined Areas выбираем Wrap Around:

    ss12

    Так как правильные мальчики и девочки верстают сайты с учетом изменения размера шрифта, нужно добавить нашей картинке запас прочности. Для этого увеличиваем размер холста до 300×80 пикселей и растягиваем последний горизонтальный и вертикальный пиксели (тут очень удобно воспользоваться инструментами Single Row Marquee Tool и Single Column Marquee Tool).

    Все, работу над изображением мы закончили. Сохраняем картинку в формате PNG-24 под названием, например, button-mask.png.

    Теперь переходим к разметке. Тут все довольно просто: создаем контейнер, в котором размещается 4 уголка. Для того, чтобы надпись была по середине кнопки, мы не будем использовать CSS-свойство height, вместо него воспользуемся свойством line-height.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    	<head>
    		<title>Styled button example</title>
    		<style type="text/css">
    			.styled-button {
    				text-align:center;
    				position:relative;
    				width:6em;
    
    				/* Вместо высоты */
    				line-height:2.3em;
    				color:#428135;
    				background:url(green-gradient.png) #8fbd81 repeat-x;
    				overflow:hidden;
    			}
    
    			.styled-button .cn {
    				display:block;
    				background:url(button-mask.png) repeat-x;
    				width:100%;
    				height:80px;
    				position:absolute;
    
    				/* Радиус скругления */
    				left:-9px;
    				top:-9px;
    			}
    
    			.styled-button .cn.tr,
    			.styled-button .cn.br {
    				/* Эмуляция свойства right для IE6 */
    				margin-left:100%;
    			}
    
    			.styled-button .cn.bl,
    			.styled-button .cn.br {
    				/* Такое же значение, как у line-height */
    				margin-top:2.3em;
    			}
    		</style>
    	</head>
    	<body>
    		<div class="styled-button">
    			Кнопка
    			<span class="cn tl"></span>
    			<span class="cn tr"></span>
    			<span class="cn bl"></span>
    			<span class="cn br"></span>
    		</div>
    	</body>
    </html>
    

    Осталось только для IE6 прописать фильтр, чтобы отображался полупрозрачный PNG.

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

    На что нужно обратить внимание в этом уроке:

    • Базовое сведения о работе векторных и растровых масок.
    • Некоторые не очевидные способы решения стандартных задач, типа Image → Trim или Single Row Marquee Tool.
    • Работа с эффектами слоев.
    • Фильтры нужны не только дизайнерам.

    Если сама идея описания фотошопа для веб-разработчиков нравится — буду писать дальше.

← cтарое