Вот набрел на интересную библиотеку для удобной работы с Canvas - Fabric.js. Не мог не пройти мимо нее, так как люблю Canvas и стараюсь узнать все о нем, по мере сил.
Ниже приведена полная версия первой части официальной документации по Fabric.js - Знакомимся с Fabric.js. Часть 1-я. Здесь я сделал ее вычитку и исправил кое-какие орфографические ошибки.
Статья понравилась еще тем, как автор объяснил чтение SVG-команд в файле.
Введение
Сегодня я хочу познакомить вас с Fabric.js — мощной Javascript-библиотекой для работы с HTML5 canvas.
Fabric включает в себя объектную модель, которой так не хватает при работе с canvas, а так же SVG-парсер, интерактивный слой и множество других незаменимых инструментов.
Это полностью открытая библиотека с MIT-лицензией и многими взносами разработчиков за последние несколько лет.
Работу над Fabric я начал 3 года назад, когда понял, насколько тяжело работать с обычным API Canvas.
В тот момент я создавал интерактивный редактор на printio.ru — мой стартап, где мы даем возможность создать дизайн и напечатать его на одежде или других товарах. Редактор хотелось сделать удобным и суперинтерактивным.
В то время такой функционал можно было создать только во Flash. Но Flash использовать я не хотел. Я предпочитаю JavaScript и был уверен, что с ним можно добиться многого. Получилось довольно неплохо. :)
Даже сейчас очень немногие визуальные редакторы могут делать то, что можно достичь с помощью Fabric.
Зачем это нужно?
В последнее время популярность Canvas растет и люди на нем делают довольно поразительные вещи. Проблема в том, что родной API Canvas ужасно низко-уровневый.
Одно дело, если нужно нарисовать несколько простых фигур или графиков и забыть о них. Другое — интерактивность, изменение картинки в какой-то момент или рисование более сложных фигур.
Вот именно для этого и нужна Fabric.js
Дело в том, что обычные canvas-методы позволяют нам вызывать только очень простые графические команды, вслепую меняя целый bitmap холста (canvas).
Нужно нарисовать прямоугольник? Используем fillRect(left, top, width, height). Нарисовать линию? Используем комбинацию moveTo(left, top) и lineTo(x, y). Как будто рисуем кисточкой по холсту, накладывая все больше и больше краски, почти без какого-либо контроля.
Fabric дает нам объектную модель поверх низко-уровневых методов canvas, хранит состояние холста и позволяет работать с обьектами напрямую.
Давайте посмотрим на разницу между canvas и Fabric. Допустим, нужно нарисовать красный прямоугольник. Используя API Canvas, это делается приблизительно так:
А вот тоже самое с Fabric:
Разницы в размере кода пока не видно. Однако, видно что, способ работы с canvas кардинально отличается.
В обычном API Canvas мы работаем с контекстом. Контекст — это объект, который по сути представляет из себя bitmap холста.
С Fabric мы управляем именно объектами — создаем и меняем параметры, добавляем их на canvas. Как видите, эти объекты — полноценные жители в Fabric (объекты первого класса).
Рисовать красный прямоугольник — это конечно не серьезно. Давайте сделаем с ним что-нибудь интересное. Например, повернем на 45 градусов.
Сначала, используя обычные методы:
… и теперь с помощью Fabric:
Что здесь происходит? Используя Fabric, все что надо было сделать, это добавить значение угла с 45 градусами. А вот с обычными методами все не так-то просто.
Во-первых, мы не можем управлять объектами напрямую. Вместо этого, приходится менять позицию и угол самого bitmap (ctx.translate, ctx.rotate). Потом рисуем прямоугольник, при этом не забывая отодвинуть bitmap соответственно (-10, -10), так, чтобы прямоугольник появился на 100/100. Еще надо не забыть перевести угол из градусов в радианы при повороте bitmap.
Теперь вам, наверное, становится понятно, зачем существует Fabric.
Давайте посмотрим на еще один пример — хранение состояния canvas.
Представим, что в какой-то момент нам нужно подвинуть этот красный прямоугольник в другое место. Как это сделать, не имея возможности управлять объектами? Вызывать fillRect еще раз?
Не совсем. Вызывая еще одну команду fillRect, прямоугольник рисуется прямо поверх всего bitmap. Именно поэтому я привел аналог кисти с краской.
Чтобы подвинуть фигуру, нам нужно сначала стереть предыдущий результат, а потом уже рисовать на новом месте:
А теперь с Fabric:
Заметьте очень важную разницу. Нам не пришлось абсолютно ничего стирать перед рисованием.
Просто продолжаем работать с объектами, меняя их атрибуты, а потом перерисовываем canvas, чтобы увидеть изменения.
Таким образом можно изменить десятки объектов и в конце одной командой обновить экран.
Объекты
Мы уже видели, как работать с прямоугольниками, используя fabric.Rect конструктор. Но, конечно же, Fabric предоставляет многие другие простые фигуры: круги, треугольники, эллипсы и т. д.
Все они доступны из fabric объектов, соответственно: fabric.Circle, fabric.Triangle, fabric.Ellipse и т. д.
7 базовых фигур, доступных в Fabric.js:
Нужно нарисовать круг? Просто создаем соответствующий объект и добавляем его на холст. Тоже самое с другими формами:
… и вот уже на холсте красуется зеленый круг в точке 100/100 и синий треугольник в точке 50/50.
Управляем объектами
Создание визуальных фигур — это только цветочки. В какой-то момент наверняка понадобится их менять.
Возможно, какие-то действия пользователя должны сказываться на состоянии картинки (холста) или должна быть запущена анимация.
Или же нужно поменять атрибуты объектов (цвет, прозрачность, размер, позицию) в зависимости от движений мышки.
Fabric берет на себя заботу о состоянии холста и перерисовке. От нас требуется менять только сами объекты.
В предыдущем примере было видно, как метод set подвинул объект на новую позицию set({ left: 20, top: 50 }). Точно также можно менять любые другие атрибуты, которых доступно несколько.
Во-первых, есть атрибуты, меняющие:
- позицию — left, top
- размер — width, height
- рендеринг (отображение объекта) — fill, opacity, stroke, strokeWidth
- масштаб и поворот — scaleX, scaleY, angle
- переворот (flip - 180 градусов) — flipX, flipY.
Да, отобразить зеркально повернутую картинку в Fabric на удивление легко — просто присваиваем true в атрибут *flip**.
Чтение атрибутов происходит с помощью метода get, присваивание — с помощью метода set.
Давайте как-нибудь поменяем наш прямоугольник:
Мы выставили ‘fill’ значением ‘red’, меняя цвет объекта на красный. Затем поменяли ‘strokeWidth’ и ‘stroke’, что добавляет прямоугольнику 5-пиксельную рамку светло-зеленого цвета. И наконец, меняем атрибуты ‘angle’ и ‘flipY’. Заметьте, как три выражения используют слегка разный синтаксис.
Отсюда видно, что set() — довольно универсальный метод. Он предназначен для частого использования, поэтому заточен под удобство.
Ну, а как насчет чтения? Я уже упомянул, что есть общий get(), а также набор конкретных get()* методов.
Например, для получения ‘width’ объекта можно использовать get(‘width’) или getWidth(). Для ‘scaleX’ — get(‘scaleX’) или getScaleX(), и т. д.
Такие специальные методы, как getWidth() и getScaleX() существуют для всех ‘публичных’ атрибутов объекта (‘stroke’, ‘strokeWidth’, ‘angle’, и т. д.).
Вы наверное заметили, что в предыдущих примерах были использованы конфигурационные хэши, которые выглядели точно также, как и те, которые мы только что использовали в методе set. Это потому, что они действительно одинаковые. Объект может быть “сконфигурирован” в момент создания или позже, с помощью метода set.
Синтаксис при этом абсолютно одинаковый:
Атрибуты по умолчанию
У всех объектов в Fabric есть набор значений по умолчанию. Они используются, когда во время создания мы не задаем другие значения. Привожу пример.
Прямоугольник получил значения по умолчанию. Он находится в позиции 0/0, черного цвета, непрозрачный, не имеет ни рамок, ни габаритов (ширина и высота равны нулю).
Из-за этого мы его и не видим. Как только устанавливаем позитивные width/height, черный прямоугольник появляется в левом верхнем углу.
Иерархия и Наследование
Объекты Fabric не существуют сами по себе. Они формируют четкую иерархию. Большинство объектов наследуют от fabric.Object.
fabric.Object — это абстрактная 2-мерная фигура на плоскости. Она имеет left/top и width/height атрибуты, а также набор других визуальных параметров.
Те атрибуты, которые мы видели ранее (fill, stroke, angle, opacity, flip** и т. д.) принадлежат всем Fabric-объектам, которые наследуют от *fabric.Object.
Такое наследование очень удобно. Оно позволяет нам определить методы на fabric.Object, таким образом делая его доступным во всех “классах”-потомках.
Например, если нужен метод getAngleInRadians на всех объектах, просто создаем его на fabric.Object.prototype:
Как видите, метод теперь доступен всем объектам.
Разумеется, классы-потомки могут не только наследовать от fabric.Object, но и определять свои собственные методы и параметры.
Например, в fabric.Circle существует дополнительный атрибут ‘radius’. Или возьмем к примеру fabric.Image, с которым мы познакомимся подробнее чуть позже.
В нем имеются методы getElement/setElement, предназначенные для чтения\записи HTML элемента , на котором основан объект типа fabric.Image.
Canvas (холст)
Мы подробно рассмотрели объекты; давайте опять вернемся к canvas.
Как видно из примеров, первый шаг - это создание самого “холста” для рисования — new fabric.Canvas(‘…’).
fabric.Canvas - это, по сути, оболочка вокруг canvas-элемента, ответственная за управление всеми содержащимися на нем объектами. Конструктор берет id элемента и возвращает объект типа fabric.Canvas.
Теперь в него можно добавлять объекты ( add() ), а также их читать ( item(), getObjects() ) или удалять ( remove() ):
Как мы уже выяснили, главная задача объекта fabric.Canvas - это управление объектами, которые находятся на canvas.
Также его можно сконфигурировать через набор параметров. Такие настройки, как изменение фона холста, скрывание объектов по маске, изменение общей длины/ширины, включение/выключение интерактивности - эти и другие опции можно выставить прямо на fabric.Canvas как во время создания, так и позже:
.. или
Интерактивность
Одна из самых уникальных возможностей Fabric, встроенная прямо в ядро - это слой интерактивности. Он позволяет пользователю манипулировать объектной моделью, с которой мы только что ознакомились.
Объектная модель существует для програмного доступа. А что нужно, чтобы управлять объектами мышкой (или touchpad на мобильных устройствах)?
Для этого в Fabric заложен функционал пользовательского доступа. Как только мы создаем холст через new fabric.Canvas(‘…’), объекты, расположенные на нем, сразу же можно выделять, двигать, масштабировать, вращать и даже группировать вместе, управляя ими как одним целым!
Если мы хотим дать возможность пользователю управлять объектами на холсте - допустим, картинкой - нужно всего лишь создать холст и добавить на него объект. Больше не нужно никаких дополнительных настроек.
Управлять этой интерактивностью легко. Для этого есть ‘selection’ флаг на холсте, а также ‘selectable’ флаг на индивидуальных объектах.
А что делать, если интерактивность вовсе не нужна? Тогда просто меняем fabric.Canvas на fabric.StaticCanvas.
Синтаксис (конфигурация, методы) абсолютно идентичный, просто используем слово StaticCanvas вместо Canvas.
Это создает облегченную версию холста, без лишней логики для интерактивности и управления event-ами. Все остальное остается таким же. Мы получаем полную объектную модель, можем добавлять, удалять и менять объекты, ну и конечно же менять опции самого холста. Исчезает только управление внешними event-ами.
В дальнейшем, когда мы ознакомимся с возможностью кастомной сборки Fabric (custom build), вы увидете, что можно создать более легкую версию библиотеки под ваши нужды.
Это может быть полезно если, например, нужно просто отобразить статичный график, SVG-фигуру или картинки с фильтрами.
Картинки
Кстати, насчет картинок …
Все-таки работа с простыми фигурами не так интересна, как с более графически насыщенными картинками. Как вы наверное уже догадываетесь, в Fabric это очень просто.
Создаем fabric.Image объект, добавляем его на холст:
Заметьте, как мы передаем *
Мы также выставляем left/top значения на 100/100, угол на 30 и прозрачность на 0.85. После добавления на холст картинка рендерится в позиции 100/100, повернутая на 30 градусов и слегка прозрачная! Неплохо …
А что же делать, если элемента картинки в документе не существует, если есть только ее адрес? Это не страшно. В таком случае можно использовать fabric.Image.fromURL:
Здесь никаких сюрпризов. Вызываем fabric.Image.fromURL передавая адрес картинки, а также функцию (callback), которую надо вызвать когда картинка загрузится.
Callback получает первым аргументом объект fabric.Image. В момент вызова, с ней можно делать что угодно - изменить или сразу добавить на холст для показа:
Path и PathGroup
Мы ознакомились с простыми фигурами и картинками. Теперь перейдем к более сложному контенту.
Встречайте мощную и незаменимую пару: Path и PathGroup.
Path (дословно переводится “путь”) в Fabric представляет из себя кривую фигуру, которая может быть залита цветом, иметь контур, быть измененной любым способом.
Она изображается набором команд, которые можно сравнить с рисованием ручкой от одной точки до другой. При помощи таких команд как ‘move’ (двинуть), ‘line’ (линия), ‘curve’ (кривая), или ‘arc’ (арка), Path могут воспроизводить удивительно сложные фигуры. А с помощью Path-групп (PathGroup), все становится возможным.
Paths в Fabric имеют сходство с SVG *
О сериализации и SVG-парсинге мы поговорим позже. Сейчас стоит сказать, что работать с Path-объектами вы вряд-ли будете вручную. Вместо этого имеет смысл использовать SVG-parser, встроенный в Fabric. Чтобы понять, что же из себя представляют эти Path-объекты, давайте создадим один из них.
При создании объекта fabric.Path мы передаем строку с инструкциями “черчения” кривой. Выглядит эта инструкция, конечно, очень загадочно, но понять ее на самом деле довольно легко.
‘M’ означает ‘move’ (двинуть) и говорит невидимой ручке подвинуться в точку 0, 0. ‘L’ означает ‘line’ (линия) и рисует линию до точки 200/100. Затем команда ‘L’ рисует линию до 170/200. И наконец, ‘z’ заставляет невидимую ручку замкнуть текущий контур и завершить фигуру. Как результат, получается вот такая треугольная форма.
Объект fabric.Path такой же, как и остальные объекты в Fabric, поэтому мы легко изменили его параметры (left/top). Но можно изменить и большее:
Ради интереса, давайте посмотрим на еще один контур, на этот раз более сложный. Вы поймете, почему создание контуров вручную — не самое веселое занятие:
Ого-го, что же здесь происходит?! Давайте разбираться.
‘M’ все еще означает ‘move’ (двинуть) команду и вот невидимая ручка начинает свое путешествие от точки ‘121.32, 0’. Затем идет команда ‘L’, которая приводит ее к точке ‘44.58, 0’. Пока все просто.
А что следующее? Команда ‘C’ означает cubic bezier (кривая безье). Она принуждает ручку рисовать кривую в точку ‘36.67, 0’. Кривая использует ‘29.5, 3.22’ как точку контроля в начале линии и ‘24.31, 8.41’ как точку контроля в конце линии.
Далее следует целая мириада остальных кривых безье, что в итоге и создает финальную фигуру:
С такими “монстрами” вручную работать вы наверняка не будете. Вместо этого, можно использовать очень удобный метод fabric.loadSVGFromString или fabric.loadSVGFromURL, загружающий целый SVG-файл. Все остальное сделает parser Fabric, пройдя по всем SVG-элементам и создавая соответствующие Path-объекты.
Кстати, что касается SVG-документов, Path в Fabric обычно представляет SVG *
PathGroup — это всего лишь группа Path-объектов. Так как fabric.PathGroup наследует от fabric.Object, такие объекты могут быть добавлены на холст как и любые другие объекты Fabric. Конечно же ими можно управлять, как и всем остальным.
Напрямую с ними работать скорее всего не придется. Если они вам попадутся во время работы с Fabric, просто имейте ввиду с чем имеете дело, и зачем они вообще нужны.
Послесловие
Мы затронули только самые базовые аспекты Fabric. Разобравшись с ними, вы с легкостью сможете создать как простые, так и сложные фигуры или картинки.
Их вы сможете показать на холсте, поменять (через атрибуты позиции, масштаба, угла, цвета, контура, прозрачности) и сделать с ними все, что душа пожелает.
В следующей части, мы поговорим о работе с группами, анимацией, текстом, SVG парсингом, рендерингом и сериализацией, управлением событиями, фильтрами картинок и остальными интересными вещами.
А пока взгляните на демки с объяснительным кодом или бенчмарки, присоединяйтесь к дискуссии в Google Group или Stackoverflow, ознакомьтесь с документацией, wiki и кодом.
Я надеюсь, вам понравится экспериментировать с Fabric!
продолжаю делать flood по javascript …