Вторая часть официальной документации по Fabric. Речь идет об анимации объектов на Canvas, применении стандартных фильтров к изображениям или создании пользовательских фильтров.
Вычитал и поправил некоторые орфографические ошибки. Продолжает радовать факт, как автор детально и внятно описывает работу каждой строки кода в сниппетах. Эту статью можно продолжать читать просто как общий легкий курс по JavaScript. )
Это вторая часть серии статей об открытой Javascript Canvas библиотеке Fabric.js, которую мы используем на printio.ru для редактора дизайнов.
В первой части этой серии мы ознакомились с самыми базовыми аспектами canvas библиотеки Fabric.js. Мы узнали, чем может быть полезна Fabric, рассмотрели ее объектную модель и иерархию объектов; увидели, что существуют как простые фигуры (прямоугольник, треугольник, круг), так и сложные (SVG). Научились выполнять простые операции над этими объектами.
Ну вот, разобрались с азами, давайте приступать к более интересным вещам!
Анимация
Любая уважающая себя canvas-библиотека в наше время включает в себя средства работы с анимацией. Fabric — не исключение. Ведь мы имеем мощную объектную модель и гибкие графические возможности. Было бы грех не уметь это приводить в движение.
Вы наверное помните, как менять атрибут у объекта. Просто вызываем метод set, передавая соответствующее значение:
Анимировать объект можно по такому же принципу и с такой же легкостью. Каждый объект в Fabric имеет метод animate (наследуя от fabric.Object) который … анимирует этот объект:
Первый аргумент - это атрибут, который хотим менять. Второй аргумент — финальное значение этого атрибута.
Например, если прямоугольник находится под углом -15° и мы указываем 45°, то угол постепенно изменится с -15° до 45°.
Ну, а последний аргумент — опциональный объект для более детальных настроек (длительность, вызовы, easing и т. д. )
animate кстати имеет очень полезную функциональность — поддержку относительных значений. Например, если нужно подвинуть объект на 100px вправо, то сделать это очень просто:
По такому же принципу, для поворота объекта на 5 градусов против часовой стрелки:
Вы наверняка заметили, что мы постоянно указываем вызов onChange. Разве 3-й аргумент не опциональный? Да, именно так.
Дело в том, что как раз это вызывание canvas.renderAll на каждый кадр анимации позволяет видеть саму анимацию!
Mетод animate всего лишь изменяет значение атрибута в течении указанного времени и по определенному алгоритму (easing).
rect.animate(‘angle’, 45) изменяет значение угла, при этом не перерисовывая экран после каждого изменения. А перерисовка экрана нужна для того, чтобы увидеть анимацию.
Ну, а почему же animate не перерисовывает экран автоматически? Из-за производительности. Ведь на холсте могут находиться сотни или даже тысячи объектов.
Было бы довольно ужасно, если каждый из объектов перерисовывал экран при изменении. В таком случае лучше использовать, например, requestAnimationFrame для постоянной отрисовки холста, не вызывая renderAll для каждого объекта.
Однако, в большинстве случаев вы скорее всего будете использовать canvas.renderAll как onChange-вызов.
Возвращаясь к опциям для анимации - что же именно мы можем менять?
- from: - позволяет менять начальное значение атрибута для анимации (если не хотим использовать текущее)
- duration: - длительность анимации; по умолчанию - 500 ms
- onComplete: - функция для вызова в конце анимации (callback)
- easing: - функция easing (смягчение)
Все эти опции более менее очевидны, кроме наверное easing. Давайте посмотрим поближе.
По умолчанию, animate используют easeInSine-функцию для смягчения анимации. Если такой вариант не подходит, в Fabric имеется большой набор популярных easing-функций (доступных через объект fabric.util.ease).
Например, вот так можно подвинуть объект вправо, при этом “отпружинивая”” в конце:
Заметьте, что мы используем fabric.util.ease.easeOutBounce как опцию смягчения. Есть и другие популярные функции — easeInCubic, easeOutCubic, easeInElastic, easeOutElastic, easeInBounce, easeOutExpo и т. д.
Вот в принципе и все, что нужно знать о анимации. Теперь можно с легкостью делать интересные вещи — менять угол объекта, чтобы сделать его вращающимся; анимировать left/top чтобы его двигать; анимировать width/height для увеличения/уменьшения; анимировать opacity для появления/исчезания и т. д.
Фильтры изображений
В первой части этой серии мы узнали, как работать с изображениями в Fabric. Как вы наверное помните, для этого используется fabric.Image-конструктор, передавая в него -элемент.
Также есть метод fabric.Image.fromURL, с помощью которого можно создать объект прямо из строки URL. И конечно же эти fabric.Image-объекты можно кинуть на холст, где они отобразятся как и все остальное.
Работать с изображениями прикольно, а с фильтрами изображений — еще веселей! Fabric уже имеет несколько фильтров, а также позволяет легко определять свои.
Некоторые фильтры из Fabric вам наверное знакомы — удаление белого фона, перевод в черно-белый, негатив или яркость. А некоторые менее популярны — градиентная прозрачность, сепия, шум.
Так как же применить фильтр к изображению? Каждый fabric.Image-объект имеет filters-атрибут, который просто является массивом фильтров. Каждый элемент в этом массиве — или один из существующих в Fabric или собственный фильтр.
Ну вот, к примеру, сделаем картинку черно-белой:
А вот так можно сделать сепию:
С атрибутом filters можно делать все тоже что и с обычным массивом — удалить фильтр (с помощью pop, splice, или shift), добавить фильтр (с помощью push, splice, unshift) или даже соединить несколько фильтров.
Когда вызывается applyFilters, все фильтры в массиве применяются к картинке по очереди. Вот, например, давайте создадим картинку с увеличенной яркостью и с эффектом сепии:
Заметьте, что мы передали { brightness: 100 }-объект в Brightness-фильтр. Это потому, что некоторым фильтрам ничего дополнительного не нужно; а некоторым (например - grayscale, invert, sepia) надо указать определенные параметры.
Для фильтра яркости это собственно само значение яркости (0-255). У фильтра шума, это значение шума (0-1000). А у фильтра удаления белого фона (‘remove white’) есть порог (‘threshold’) и расстояние (‘distance’).
Ну вот разобрались с фильтрами; пора создать свой!
Образец для создания фильтров будет довольно прост. Нам нужно создать ‘класс’’ и написать метод applyTo. Опционально мы можем дать фильтру toJSON-метод (поддержка JSON-сериализации) и/или initialize (если фильтр имеет дополнительные параметры):
Не вникая сильно в подробности кода, стоит заметить, что самое главное происходит в цикле, где мы меняем зеленую (data[i+1]) и голубую (data[i+2]) компоненты каждого пикселя на 0, по сути дела удаляя их.
Красная компонента остается нетронутой, что и делает все изображение красным.
Примечание чтеца: для желающих - более подробно аналог представленного выше кода описан в моей статье Canvas - Raw Pixel.
Как видите, applyTo-метод получает в себя canvas-элемент, который представляет собой изображение. Имея такой canvas, мы можем пройтись по всем пикселям изображения (getImageData().data) изменяя их как нам угодно.
Цвета
Независимо от того, с чем вам удобней работать — hex, RGB, или RGBA-форматами цвета — Fabric упрощает утомительные операции и переводы из одного формата в другой.
Давайте посмотрим на несколько способов определить цвет в Fabric:
Перевод формата происходит очень просто. Метод toHex() переводит цвет в hex. Метод toRgb() — в RGB, а метод toRgba() — в RGB с альфа каналом (прозрачностью):
Кстати, можно делать не только перевод. Можно “накладывать” цвета один на другой или делать из них черно-белый вариант:
Градиенты
Еще более экспрессивный способ работы с цветами — используя градиенты. Градиенты позволяет плавно смешать один цвет с другим, открывая возможность довольно изумительным эффектам.
Fabric поддерживает градиенты с помощью метода setGradient (setGradientFill до 1.1.0 версии), который присутствует на всех объектах.
Вызывание setGradient( ‘fill’, { … } ) - это почти как выставление значения ‘fill’ у объекта, только вместо цвета используется градиент:
В этом примере мы создаем круг в точке 100/100 с радиусом в 50px. Потом выставляем ему градиент, идущий по всей высоте объекта, от черного к белому.
Как видите, метод получает в себя конфигурационный объект, в котором могут присутствовать 2 пары координат (x1/y1 и x2/y2) и объект ‘colorStops’.
Координаты указывают, где градиент начинается и где он заканчивается. colorStops указывают - из каких цветов он состоит.
Вы можете определить сколько угодно цветов; главное, чтобы их позиции находились в интервале от 0 до 1 (например 0, 0.1, 0.3, 0.5, 0.75, 1). 0 представляет начало градиента, 1 — его конец.
Чтобы понять, как мы определили координаты, посмотрим на изображение ниже:
Так как координаты относительны центру объекта, то верхней точкой является -circle.height / 2, а нижней - circle.height / 2. Координаты по ширине (x1/x2) определяем точно так же.
Вот пример красно-голубого градиента, идущего слева направо:
А вот 5-ти шаговый градиент-радуга, с цветами, занимающими по 20% всей длины:
А вы можете придумать что-нибудь интересное?
Текст
Что если нужно отобразить не только картинки и векторные формы на холсте, а еще и текст? Fabric умеет и это! Встречайте fabric.Text.
Перед тем как говорить о тексте, стоит отметить, зачем мы вообще предоставляем поддержку работы с текстом.
Ведь canvas имеет встроенные методы fillText и strokeText.
Во-первых, для того чтобы можно было работать с текстом как с объектами. Выстроенные canvas-методы — как обычно — позволяют вывести текст на очень низком уровне.
А вот создав объект типа fabric.Text, мы можем работать с ним как и с любым другим объектом на холсте — двигать его, масштабировать, менять атрибуты, и т. д.
Вторая причина — чтобы иметь более богатый функционал, чем то что дает нам canvas. Некоторые вещи, которые есть в Fabric, но нет в родных методах:
- Многострочность - Родные методы позволяют написать только одну линию, игнорируя переходы строки
- Выравнивание текста - Влево, по центру, вправо. Полезно во время работы с многострочным текстом
- Фон текста - Фон выводится только под самим текстом, в зависимости от выравнивания
- Декорация текста - Подчеркивание, надчеркивание, перечеркивание
- Высота строки - Полезно во время работы с многострочным текстом
Ну что ж, давайте посмотрим на вездесущий “hello world”?
Вот и все! Для показа текста необходимо всего лишь добавить объект типа fabric.Text на холст, указывая нужную позицию.
Первый параметр необходим — это собственно сама строка текста. Второй аргумент — опционально конфигурация, как обычно; можно указать left, top, fill, opacity, и т. д.
Помимо обычных атрибутов, у текстовых объектов конечно же есть и свои, относящиеся к тексту. Вкратце, об этих атрибутах:
fontFamily
По умолчанию - “Times New Roman”. Позволяет менять семейство шрифта для текста:
fontSize
Контролирует размер текста. Заметьте, что в отличие от других объектов в Fabric, мы не можем менять размер текста с помощью width/height. Вместо этого как раз и используется fontSize и конечно же scaleX/scaleY:
fontWeight
Позволяет сделать текст жирнее или тоньше. Точно также как в CSS, можно использовать или слова (“normal”, “bold”) или номерные значения (100, 200, 400, 600, 800).
Важно понимать, что для определенной толщины нужно иметь соответствующий шрифт. Если в шрифте не присутствует “bold” (жирный) вариант, например, то жирный текст может не отобразиться:
textDecoration
Позволяет добавить тексту перечеркивание, надчеркивание или подчеркивание. Опять же, эта декларация работает также, как в CSS.
Однако Fabric умеет даже немного больше, позволяя использовать эти декорации вместе (например, подчеркивание И перечеркивание), просто перечисляя их через пробел:
shadow
До версии 1.3.0 этот атрибут назывался “textShadow”.
Тень для текста. Состоит из 4-х компонент: цвет, горизонтальный отступ, вертикальный отступ, и размер размытия.
Это все должно быть знакомо, если вы до этого работали с тенями в CSS. Меняя эти 4 опции, можно добиться многих интересных эффектов:
fontStyle
Стиль текста. Может быть только один из двух: normal или italic. Опять же, работает так же, как и в CSS:
stroke и strokeWidth
Соединяя stroke (цвет наружнего штриха) и strokeWidth (ширину наружнего штриха), можно достичь довольно интересных эффектов.
Вот пара примеров:
Стоит отметить, что stroke был назван strokeStyle до версии 1.1.6
textAlign
Выравнивание полезно при работе с многострочным текстом. В однострочном тексте, выравнивание не видно, потому как ширина самого текстового объекта такая же как и длина строки.
Возможные значения: left, center, right и justify:
lineHeight
Еще один атрибут, скорее всего знакомый из CSS — lineHeight (высота строки).
Позволяет менять расстояние между строк в многострочном тексте. Вот пример текста с ‘lineHeight 3’ и второй с ‘lineHeight 1’:
textBackgroundColor
И наконец, дать тексту фон можно с помощью textBackgroundColor. Заметьте, что фон заполняется только под самим текстом, а не на всю “коробку”.
Чтобы закрасить весь текстовый объект, можно использовать атрибут backgroundColor. Также видно, что фон зависит от выравнивания текста и lineHeight.
Если lineHeight очень большой, фон будет видно только под текстом:
События
События — незаменимый инструмент для создания сложных приложений. Для удобства пользования и более детальной настройки, Fabric имеет обширную систему событий; начиная от низкоуровневых событий мыши и вплоть до высокоуровневых событий объектов.
События позволяют нам “поймать” различные моменты, когда что-то происходит на холсте.
Хотим узнать, когда была нажата мышка? Следим за событием mouse:down.
Как насчет, когда объект был добавлен на холст? Для этого есть object:added.
Ну, а что насчет перерисовки холста? Используем after:render.
API событий очень прост и похож на то, к чему вы скорее всего привыкли в jQuery, Underscore.js или других популярных JS-библиотеках.
Есть метод on для инициализации слушателя событий и есть метод off для его удаления.
Давайте посмотрим на пример:
Мы добавили слушатель события mouse:down на canvas-объекте и указали обработчика, который будет записывать координаты, где произошло это событие.
Таким образом, мы можем видеть, где именно произошел клик на холсте. Обработчик событий получает options-объект с двумя параметрами: e — оригинальное событие и target — Fabric-объект на холсте, если он найден.
Первый параметр присутствует всегда, а вот target - только если клик произошел на объекте. Ну и конечно же, target передается только обработчикам тех событий, где это имеет смысл.
Например, для mouse:down но не для after:render (так как это событие не “имеет” никаких объектов, а просто обозначает что холст был перерисован):
Этот пример выведет ‘an object was clicked!’ если мы нажмем на объект. Также покажется тип этого объекта.
Какие еще события доступны в Fabric?
На уровне мышки, у нас есть mouse:down, mouse:move и mouse:up.
Из общих есть after:render.
Есть события, касающиеся выбора объектов: before:selection:cleared, selection:created, selection:cleared.
Ну и конечно же, события объектов: object:modified, object:selected, object:moving, object:scaling, object:rotating, object:added и object:removed.
Стоит заметить, что события типа object:moving (или object:scaling) происходят постоянно, во время движения или масштабирования объекта, даже если на один пиксель.
В то же время, события типа object:modified или selection:created происходят только в конце действия (изменение объекта, создание группы объектов, и т. д.).
В предыдущих примерах мы присоединяли слушателя на canvas объект ( canvas.on( ‘mouse:down’, … ) ).
Как вы наверное догадываетесь, это означает, что события распространяются только на тот холст, к которому мы их присоединили. Если у вас несколько холстов на странице, вы можете дать им разные слушатели. События на одном холсте не распространяются на другие холсты.
Для удобства, Fabric позволяет добавлять слушатели прямо на Fabric объекты!
В этом примере слушатели “присоединяются” прямо к прямоугольнику и кругу. Вместо object:selected мы используем событие selected.
По такому же принципу, можно использовать событие modified (object:modified когда “вешаем” на холст), rotating (аналог object:rotating) и т. д.
Вы можете ознакомиться с событиями поближе и прямо в реальном времени вот в этой демо.
На этом 2-я часть подошла к концу. Столько всего нового, но это еще не все! В 3-й части мы рассмотрим группы объектов, сериализацию/десериализацию холста и формат JSON, SVG-парсер, а также создание подклассов.