Вот набрел на интересную библиотеку для удобной работы с Canvas - Fabric.js. Не мог не пройти мимо нее, так как люблю Canvas и стараюсь узнать все о нем, по мере сил.

Ниже приведена полная версия первой части официальной документации по Fabric.js - Знакомимся с Fabric.js. Часть 1-я. Здесь я сделал ее вычитку и исправил кое-какие орфографические ошибки.

Статья понравилась еще тем, как автор объяснил чтение SVG-команд в файле.

Введение

Сегодня я хочу познакомить вас с Fabric.js — мощной Javascript-библиотекой для работы с HTML5

1
<canvas>
.

Fabric включает в себя объектную модель, которой так не хватает при работе с

1
<canvas>
, а так же SVG-парсер, интерактивный слой и множество других незаменимых инструментов.

Это полностью открытая библиотека с MIT-лицензией и многими взносами разработчиков за последние несколько лет.

Работу над Fabric я начал 3 года назад, когда понял, насколько тяжело работать с обычным API Canvas.

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

В то время такой функционал можно было создать только во Flash. Но Flash использовать я не хотел. Я предпочитаю JavaScript и был уверен, что с ним можно добиться многого. Получилось довольно неплохо. :)

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

Зачем это нужно?

В последнее время популярность Canvas растет и люди на нем делают довольно поразительные вещи. Проблема в том, что родной API Canvas ужасно низко-уровневый.

Одно дело, если нужно нарисовать несколько простых фигур или графиков и забыть о них. Другое — интерактивность, изменение картинки в какой-то момент или рисование более сложных фигур.

Вот именно для этого и нужна Fabric.js

Дело в том, что обычные canvas-методы позволяют нам вызывать только очень простые графические команды, вслепую меняя целый bitmap холста (canvas).

Нужно нарисовать прямоугольник? Используем

1
fillRect(left, top, width, height)
. Нарисовать линию? Используем комбинацию
1
moveTo(left, top)
и
1
lineTo(x, y)
. Как будто рисуем кисточкой по холсту, накладывая все больше и больше краски, почти без какого-либо контроля.

Fabric дает нам объектную модель поверх низко-уровневых методов canvas, хранит состояние холста и позволяет работать с обьектами напрямую.

Давайте посмотрим на разницу между canvas и Fabric. Допустим, нужно нарисовать красный прямоугольник. Используя API Canvas, это делается приблизительно так:

// берем canvas-элемент (id="c")
var canvasEl = document.getElementById('c');

// берем 2d-контекст, на котором рисовать ("bitmap" упомянутый ранее)
var ctx = canvasEl.getContext('2d');

// меняем fill (закраску) цвета контекста
ctx.fillStyle = 'red';

// создаем прямоугольник в точке 100/100 размером в 20x20
ctx.fillRect(100, 100, 20, 20);

А вот тоже самое с Fabric:

// создаем ‘оболочку’ вокруг canvas элемента (id=’c’)
var canvas = new fabric.Canvas('c');

// создаем прямоугольник
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20
});

// добавляем прямоугольник, чтобы он отобразился
canvas.add(rect);

Fabric Image

Разницы в размере кода пока не видно. Однако, видно что, способ работы с canvas кардинально отличается.

В обычном API Canvas мы работаем с контекстом. Контекст — это объект, который по сути представляет из себя bitmap холста.

С Fabric мы управляем именно объектами — создаем и меняем параметры, добавляем их на canvas. Как видите, эти объекты — полноценные жители в Fabric (объекты первого класса).

Рисовать красный прямоугольник — это конечно не серьезно. Давайте сделаем с ним что-нибудь интересное. Например, повернем на 45 градусов.

Сначала, используя обычные методы:

var canvasEl = document.getElementById('c');
var ctx = canvasEl.getContext('2d');
ctx.fillStyle = 'red';

ctx.translate(100, 100);
ctx.rotate(Math.PI / 180 * 45);
ctx.fillRect(-10, -10, 20, 20);

… и теперь с помощью Fabric:

var canvas = new fabric.Canvas('c');

// создаем прямоугольник с углом в 45 градусов
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20,
  angle: 45
});

canvas.add(rect);

Fabric Image

Что здесь происходит? Используя Fabric, все что надо было сделать, это добавить значение угла с 45 градусами. А вот с обычными методами все не так-то просто.

Во-первых, мы не можем управлять объектами напрямую. Вместо этого, приходится менять позицию и угол самого bitmap (

1
ctx.translate
,
1
ctx.rotate
). Потом рисуем прямоугольник, при этом не забывая отодвинуть bitmap соответственно (
1
-10, -10
), так, чтобы прямоугольник появился на
1
100/100
. Еще надо не забыть перевести угол из градусов в радианы при повороте bitmap.

Теперь вам, наверное, становится понятно, зачем существует Fabric.

Давайте посмотрим на еще один пример — хранение состояния canvas.

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

1
fillRect
еще раз?

Не совсем. Вызывая еще одну команду

1
fillRect
, прямоугольник рисуется прямо поверх всего bitmap. Именно поэтому я привел аналог кисти с краской.

Чтобы подвинуть фигуру, нам нужно сначала стереть предыдущий результат, а потом уже рисовать на новом месте:

// создается прямоугольник
var canvasEl = document.getElementById('c');
...
ctx.strokRect(100, 100, 20, 20);

// стираем весь canvas (и предыдущий прямоугольник)
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);

// рисуем новый прямоугольник
ctx.fillRect(20, 50, 20, 20);

А теперь с Fabric:

var canvas = new fabric.Canvas('c');
...
canvas.add(rect);

// вызываем уже созданный объект rect и задаем ему новые значения свойств - смешаем на новое положение x/y = 20/50
rect.set({ left: 20, top: 50 });

// выполняем рендеринг снова - перерисовываем canvas
canvas.renderAll();

Fabric Image

Заметьте очень важную разницу. Нам не пришлось абсолютно ничего стирать перед рисованием.

Просто продолжаем работать с объектами, меняя их атрибуты, а потом перерисовываем canvas, чтобы увидеть изменения.

Таким образом можно изменить десятки объектов и в конце одной командой обновить экран.

Объекты

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

1
fabric.Rect
конструктор. Но, конечно же, Fabric предоставляет многие другие простые фигуры: круги, треугольники, эллипсы и т. д.

Все они доступны из fabric объектов, соответственно:

1
fabric.Circle
,
1
fabric.Triangle
,
1
fabric.Ellipse
и т. д.

7 базовых фигур, доступных в Fabric.js:

Нужно нарисовать круг? Просто создаем соответствующий объект и добавляем его на холст. Тоже самое с другими формами:

// создаем круг
var circle = new fabric.Circle({
  radius: 20, fill: 'green', left: 100, top: 100
});

// создаем треугольник
// попробуйте создать треугольник на API Canvas одной строкой
var triangle = new fabric.Triangle({
  width: 20, height: 30, fill: 'blue', left: 50, top: 50
});

// добавляем два объекта одной командой
canvas.add(circle, triangle);

Fabric Image

… и вот уже на холсте красуется зеленый круг в точке 100/100 и синий треугольник в точке 50/50.

Управляем объектами

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

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

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

Fabric берет на себя заботу о состоянии холста и перерисовке. От нас требуется менять только сами объекты.

В предыдущем примере было видно, как метод

1
set
подвинул объект на новую позицию
1
set({ left: 20, top: 50 })
. Точно также можно менять любые другие атрибуты, которых доступно несколько.

Во-первых, есть атрибуты, меняющие:

  • позицию —
    1
    
    left, top
  • размер —
    1
    
    width, height
  • рендеринг (отображение объекта) —
    1
    
    fill, opacity, stroke, strokeWidth
  • масштаб и поворот —
    1
    
    scaleX, scaleY, angle
  • переворот (flip - 180 градусов) —
    1
    
    flipX, flipY
    .

Да, отобразить зеркально повернутую картинку в Fabric на удивление легко — просто присваиваем

1
true
в атрибут
1
flip*
.

Чтение атрибутов происходит с помощью метода

1
get
, присваивание — с помощью метода
1
set
.

Давайте как-нибудь поменяем наш прямоугольник:

var canvas = new fabric.Canvas('c');
...
canvas.add(rect);

rect.set( 'fill', 'red' );
rect.set({ strokeWidth: 5, stroke: 'rgba(100, 200, 200, 0.5)' });
rect.set( 'angle', 15 ).set( 'flipY', true );

Fabric Image

Мы выставили ‘fill’ значением ‘red’, меняя цвет объекта на красный. Затем поменяли ‘strokeWidth’ и ‘stroke’, что добавляет прямоугольнику 5-пиксельную рамку светло-зеленого цвета. И наконец, меняем атрибуты ‘angle’ и ‘flipY’. Заметьте, как три выражения используют слегка разный синтаксис.

Отсюда видно, что

1
set()
— довольно универсальный метод. Он предназначен для частого использования, поэтому заточен под удобство.

Ну, а как насчет чтения? Я уже упомянул, что есть общий

1
get()
, а также набор конкретных
1
get*()
методов.

Например, для получения ‘width’ объекта можно использовать

1
get('width')
или
1
getWidth()
. Для ‘scaleX’ —
1
get('scaleX')
или
1
getScaleX()
, и т. д.

Такие специальные методы, как

1
getWidth()
и
1
getScaleX()
существуют для всех ‘публичных’ атрибутов объекта (‘stroke’, ‘strokeWidth’, ‘angle’, и т. д.).

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

1
set
. Это потому, что они действительно одинаковые. Объект может быть “сконфигурирован” в момент создания или позже, с помощью метода
1
set
.

Синтаксис при этом абсолютно одинаковый:

var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

// полностью идентичен
var rect = new fabric.Rect();
rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

Атрибуты по умолчанию

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

var rect = new fabric.Rect(); // не передаем никаких параметров

// атрибуты по умолчанию и их значения
rect.getWidth(); // => 0
rect.getHeight(); // => 0

rect.getLeft(); // => 0
rect.getTop(); // => 0

rect.getFill(); // => rgb(0,0,0)
rect.getStroke(); // => null

rect.getOpacity(); // => 1

Прямоугольник получил значения по умолчанию. Он находится в позиции

1
0/0
, черного цвета, непрозрачный, не имеет ни рамок, ни габаритов (ширина и высота равны нулю).

Из-за этого мы его и не видим. Как только устанавливаем позитивные

1
width/height
, черный прямоугольник появляется в левом верхнем углу.

Fabric Image

Иерархия и Наследование

Объекты Fabric не существуют сами по себе. Они формируют четкую иерархию. Большинство объектов наследуют от

1
fabric.Object
.

1
fabric.Object
— это абстрактная 2-мерная фигура на плоскости. Она имеет left/top и width/height атрибуты, а также набор других визуальных параметров.

Те атрибуты, которые мы видели ранее (

1
fill
,
1
stroke
,
1
angle
,
1
opacity
,
1
flip*
и т. д.) принадлежат всем Fabric-объектам, которые наследуют от
1
fabric.Object
.

Такое наследование очень удобно. Оно позволяет нам определить методы на

1
fabric.Object
, таким образом делая его доступным во всех “классах”-потомках.

Например, если нужен метод

1
getAngleInRadians
на всех объектах, просто создаем его на
1
fabric.Object.prototype
:

fabric.Object.prototype.getAngleInRadians = function () {
  return this.getAngle() / 180 * Math.PI; // getAngle() - стандартный метод библиотеки Fabric
};

var rect = new fabric.Rect({ angle: 45 });
rect.getAngleInRadians(); // 0.785...

var circle = new fabric.Circle({ angle: 30, radius: 10 });
circle.getAngleInRadians(); // 0.523...

circle instanceof fabric.Circle; // true
circle instanceof fabric.Object; // true

Как видите, метод теперь доступен всем объектам.

Разумеется, классы-потомки могут не только наследовать от

1
fabric.Object
, но и определять свои собственные методы и параметры.

Например, в

1
fabric.Circle
существует дополнительный атрибут ‘radius’. Или возьмем к примеру
1
fabric.Image
, с которым мы познакомимся подробнее чуть позже.

В нем имеются методы

1
getElement/setElement
, предназначенные для чтения\записи HTML элемента
1
<img>
, на котором основан объект типа
1
fabric.Image
.

Canvas (холст)

Мы подробно рассмотрели объекты; давайте опять вернемся к canvas.

Как видно из примеров, первый шаг - это создание самого “холста” для рисования —

1
new fabric.Canvas('...')
.

1
fabric.Canvas
- это, по сути, оболочка вокруг
1
<canvas>
-элемента, ответственная за управление всеми содержащимися на нем объектами. Конструктор берет id элемента и возвращает объект типа
1
fabric.Canvas
.

Теперь в него можно добавлять объекты (

1
add()
), а также их читать (
1
item()
,
1
getObjects()
) или удалять (
1
remove()
):

var canvas = new fabric.Canvas( 'c' );
var rect = new fabric.Rect();

canvas.add( rect ); // добавляем

canvas.item( 0 ); // получаем fabric.Rect, добавленный ранее (первый объект)
canvas.getObjects(); // получаем все объекты (прямоугольник будет первым и единственным)

canvas.remove(rect); // удаляем прямоугольник

Как мы уже выяснили, главная задача объекта

1
fabric.Canvas
- это управление объектами, которые находятся на canvas.

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

1
fabric.Canvas
как во время создания, так и позже:

var canvas = new fabric.Canvas('c', {
  backgroundColor: 'rgb(100,100,200)',
  selectionColor: 'blue',
  selectionLineWidth: 2
  // ...
});

.. или

var canvas = new fabric.Canvas( 'c' );
canvas.setBackgroundImage( http://...' );
canvas.onFpsUpdate = function(){ /* ... */ };
// ...

Интерактивность

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

Объектная модель существует для програмного доступа. А что нужно, чтобы управлять объектами мышкой (или touchpad на мобильных устройствах)?

Для этого в Fabric заложен функционал пользовательского доступа. Как только мы создаем холст через

1
new fabric.Canvas('...')
, объекты, расположенные на нем, сразу же можно выделять, двигать, масштабировать, вращать и даже группировать вместе, управляя ими как одним целым!

Fabric Image Fabric Image

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

Управлять этой интерактивностью легко. Для этого есть ‘selection’ флаг на холсте, а также ‘selectable’ флаг на индивидуальных объектах.

var canvas = new fabric.Canvas('c');
...
canvas.selection = false; // выключаем выделение
rect.set('selectable', false); // делаем объект невыделяемым

А что делать, если интерактивность вовсе не нужна? Тогда просто меняем

1
fabric.Canvas
на
1
fabric.StaticCanvas
.

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

1
StaticCanvas
вместо
1
Canvas
.

var staticCanvas = new fabric.StaticCanvas('c');

staticCanvas.add(
  new fabric.Rect({
    width: 10, height: 20,
    left: 100, top: 100,
    fill: 'yellow',
    angle: 30
  }));

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

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

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

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

Картинки

Кстати, насчет картинок …

Все-таки работа с простыми фигурами не так интересна, как с более графически насыщенными картинками. Как вы наверное уже догадываетесь, в Fabric это очень просто.

Создаем

1
fabric.Image
объект, добавляем его на холст:

(html)
<canvas id="c"></canvas>
<img src="my_image.png" id="my-img">
(js)
var canvas = new fabric.Canvas('c');
var imgElement = document.getElementById('my-img');
var imgInstance = new fabric.Image(imgElement, {
  left: 100,
  top: 100,
  angle: 30,
  opacity: 0.85
});
canvas.add(imgInstance);

Заметьте, как мы передаем

1
<image>
-элемент в конструктор
1
fabric.Image
. Таким образом мы создаем объект типа
1
fabric.Image
, который представляет собой картинку из данного элемента.

Мы также выставляем

1
left/top
значения на 100/100, угол на 30 и прозрачность на 0.85. После добавления на холст картинка рендерится в позиции 100/100, повернутая на 30 градусов и слегка прозрачная! Неплохо …

Fabric Image

А что же делать, если элемента картинки в документе не существует, если есть только ее адрес? Это не страшно. В таком случае можно использовать

1
fabric.Image.fromURL
:

fabric.Image.fromURL('my_image.png', function (oImg) {
  canvas.add(oImg);
});

Здесь никаких сюрпризов. Вызываем

1
fabric.Image.fromURL
передавая адрес картинки, а также функцию (callback), которую надо вызвать когда картинка загрузится.

Callback получает первым аргументом объект

1
fabric.Image
. В момент вызова, с ней можно делать что угодно - изменить или сразу добавить на холст для показа:

fabric.Image.fromURL('my_image.png', function (oImg) {
  // уменьшаем картинку и переворачиваем перед добавлением
  oImg.scale(0.5).setFlipX(true);
  canvas.add(oImg);
});

Path и PathGroup

Мы ознакомились с простыми фигурами и картинками. Теперь перейдем к более сложному контенту.

Встречайте мощную и незаменимую пару: Path и PathGroup.

Path (дословно переводится “путь”) в Fabric представляет из себя кривую фигуру, которая может быть залита цветом, иметь контур, быть измененной любым способом.

Она изображается набором команд, которые можно сравнить с рисованием ручкой от одной точки до другой. При помощи таких команд как ‘move’ (двинуть), ‘line’ (линия), ‘curve’ (кривая), или ‘arc’ (арка), Path могут воспроизводить удивительно сложные фигуры. А с помощью Path-групп (PathGroup), все становится возможным.

Paths в Fabric имеют сходство с SVG

1
<path>
-элементами. Они используют одинаковый набор команд, могут быть созданы из
1
<path>
-элементов и сериализованы в них.

О сериализации и SVG-парсинге мы поговорим позже. Сейчас стоит сказать, что работать с Path-объектами вы вряд-ли будете вручную. Вместо этого имеет смысл использовать SVG-parser, встроенный в Fabric. Чтобы понять, что же из себя представляют эти Path-объекты, давайте создадим один из них.

var canvas = new fabric.Canvas('c');
var path = new fabric.Path( 'M 0 0 L 200 100 L 170 200 z' );
path.set({ left: 120, top: 120 });
canvas.add(path);

Fabric Image

При создании объекта

1
fabric.Path
мы передаем строку с инструкциями “черчения” кривой. Выглядит эта инструкция, конечно, очень загадочно, но понять ее на самом деле довольно легко.

‘M’ означает ‘move’ (двинуть) и говорит невидимой ручке подвинуться в точку 0, 0. ‘L’ означает ‘line’ (линия) и рисует линию до точки 200/100. Затем команда ‘L’ рисует линию до 170/200. И наконец, ‘z’ заставляет невидимую ручку замкнуть текущий контур и завершить фигуру. Как результат, получается вот такая треугольная форма.

Объект

1
fabric.Path
такой же, как и остальные объекты в Fabric, поэтому мы легко изменили его параметры (
1
left/top
). Но можно изменить и большее:

...
var path = new fabric.Path('M 0 0 L 300 100 L 200 300 z');
...
path.set({ fill: 'red', stroke: 'green', opacity: 0.5 });
canvas.add(path);

Fabric Image

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

...
var path = new fabric.Path(' M 121.32, 0 L 44.58,0 C36.67,0,29.5,3.22,24.31,8.41\
c-5.19,5.19-8.41,12.37-8.41,20.28c0,15.82,12.87,28.69,28.69,28.69c0,0,4.4,\
0,7.48,0C36.66,72.78,8.4,101.04,8.4,101.04C2.98,106.45,0,113.66,0,121.32\
c0,7.66,2.98,14.87,8.4,20.29l0,0c5.42,5.42,12.62,8.4,20.28,8.4c7.66,0,14.87\
-2.98,20.29-8.4c0,0,28.26-28.25,43.66-43.66c0,3.08,0,7.48,0,7.48c0,15.82,\
12.87,28.69,28.69,28.69c7.66,0,14.87-2.99,20.29-8.4c5.42-5.42,8.4-12.62,8.4\
-20.28l0-76.74c0-7.66-2.98-14.87-8.4-20.29C136.19,2.98,128.98,0,121.32,0z');

canvas.add(path.set({ left: 100, top: 200 }));

Ого-го, что же здесь происходит?! Давайте разбираться.

‘M’ все еще означает ‘move’ (двинуть) команду и вот невидимая ручка начинает свое путешествие от точки ‘121.32, 0’. Затем идет команда ‘L’, которая приводит ее к точке ‘44.58, 0’. Пока все просто.

А что следующее? Команда ‘C’ означает cubic bezier (кривая безье). Она принуждает ручку рисовать кривую в точку ‘36.67, 0’. Кривая использует ‘29.5, 3.22’ как точку контроля в начале линии и ‘24.31, 8.41’ как точку контроля в конце линии.

Далее следует целая мириада остальных кривых безье, что в итоге и создает финальную фигуру:

Fabric Image

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

1
fabric.loadSVGFromString
или
1
fabric.loadSVGFromURL
, загружающий целый SVG-файл. Все остальное сделает parser Fabric, пройдя по всем SVG-элементам и создавая соответствующие Path-объекты.

Кстати, что касается SVG-документов, Path в Fabric обычно представляет SVG

1
<path>
-элемент, а вот наборы таких элементов, которые очень часто можно найти в SVG документах, обычно представлены через PathGroup (
1
fabric.PathGroup
-объекты).

PathGroup — это всего лишь группа Path-объектов. Так как

1
fabric.PathGroup
наследует от
1
fabric.Object
, такие объекты могут быть добавлены на холст как и любые другие объекты Fabric. Конечно же ими можно управлять, как и всем остальным.

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

Послесловие

Мы затронули только самые базовые аспекты Fabric. Разобравшись с ними, вы с легкостью сможете создать как простые, так и сложные фигуры или картинки.

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

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

А пока взгляните на демки с объяснительным кодом или бенчмарки, присоединяйтесь к дискуссии в Google Group или Stackoverflow, ознакомьтесь с документацией, wiki и кодом.

Я надеюсь, вам понравится экспериментировать с Fabric!


продолжаю делать flood по javascript …


Пример создания часов на Canvas. Снова я “подсмотрел” пример из зарубежного источника.

И снова мне захотелось его разобрать “по косточкам”. Поэтому на оригинальность не претендую (к моему сожалению).

HTML-разметка

Для HTML-разметки создаем элемент canvas:

<canvas id="clock" class="clock"></canvas>

Затем в JavaScript-скрипте “достаем” элемент canvas и динамически задаем для него размеры

1
500 x 500
(мне нравится задавать их динамически):

window.addEventListener( 'DOMContentLoaded', function () {

    var clock = document.querySelector('#clock').getContext('2d');
    clock.canvas.width = 500;
    clock.canvas.height = 500;

}, false);

Время - работа с ним

Создаем новый экземпляр объекта Date для того, чтобы получить текущее время:

var now = new Date();

Затем из полученного объекта получаем значения времени - часов, минут, секунд и миллисекунд:

var hours = now.getHours();
var minutes = now.getMinutes();
var seconds = now.getSeconds();
var miliSeconds = now.getMilliseconds();

В дополнение к этому, получим также текущую дату с учетом local time и locale, в удобном для чтения человеком формате - метод toDateString:

var today = now.toDateString();
// пример вывода
"Fri Jun 17 2016"

… и точно также - локальное время (метод toLocaleTimeString):

var time = now.toLocaleTimeString();
// пример вывода
"3:34:31 PM"

Часы, минуты, секунды

В canvas создаем круговую индикацию часов, минут и секунд.

Делает это будем при помощи canvas-метода

1
arc()
:

function degreeToRadian (degree) {
    return degree * Math.PI / 180;
}

function renderTime () {

    // HOURS
    clock.beginPath();
    clock.arc( 250, 250, 200, degreeToRadian(270), degreeToRadian( hours * 15 - 90 ) );
    clock.stroke();

    // MINUTES
    clock.beginPath();
    clock.arc( 250, 250, 180, degreeToRadian(270), degreeToRadian( minutes * 6 - 90 ) );
    clock.stroke();

    // SECONDS
    clock.beginPath();
    clock.arc( 250, 250, 160, degreeToRadian(270), degreeToRadian( newSeconds * 6 - 90 ) );
    clock.stroke();

}

setInterval( renderTime, 40 );

В приведенном выше коде остановимся на некоторых моментах.

Первый момент - метод

1
arc()
принимает аргументы для угловых значений в радианах. Для человека этот формат непривычен, так как мы пользуемся градусами.

Поэтому для удобства создадим функцию degreeToRadian, которая преобразовывает градусы в радианы.

Второй момент - начальный угол (точка отсчета) по стандарту canvas (я не ошибся?) - это 0. Это соответствует трем часам (15:00) на часовом циферблате.

Конечно, нас это не устраивает, поэтому смешаем начальную точку на 270 градусов (по часовой стрелке) так, чтобы эта точка находилась в положении 12 часов (12:00) -

1
degreeToRadian(270)
.

Второй аргумент - конечный угол - будет принимать значение угла динамически, из объекта

1
now
:

var hours = now.getHours();
var minutes = now.getMinutes();
var seconds = now.getSeconds();

Из полученного значения нам необходимо вычесть 90 градусов потому, что хоть начальную точку мы и сместили на 270 градусов, отсчет продолжает вестись из начальной точки 0 (которая на 15:00). Почему так происходит, я так и не понял - но код работает.

Результирующее значение умножаем на 15 или 6 для кратности - эти величины взяты чисто эмпирически. В итоге получится красивые и “смотрибельные” окружности\стрелки.

Последний момент - сделаем для всего этого “дела” обертку-функцию

1
renderTime
и “запихнем” ее в тайминговую функцию setInterval, с частотой выполнения 40 миллисекунд.

Таким образом мы оформим анимацию отрисовки окружностей\стрелок и наши часы станут настоящими (почти) часами.

Пора взглянуть по получившийся результат, ибо иначе теряется “связь с реальностью”. Готовый код можно посмотреть здесь - JavaScript Canvas Clock - Start.

Небольшое примечание - для большей наглядности примера в код были добавлены толщина линий (lineWidth), цвет линий (strokeStyle), скругление концов линий (lineCap), а также фоновая заливка (fillStyle, fillRect) всего canvas:

clock.strokeStyle = '#28d1fa';
clock.lineWidth = 14;
clock.lineCap = 'round';
...
// BACKGROUND
clock.fillStyle = '#000';
clock.fillRect( 0, 0, 500, 500 );

И еще один момент - нужно обратить внимание на новую строку:

var newSeconds = seconds + ( miliSeconds / 1000 );

Что она делает? Она просто получает текущее значение миллисекунд, дробит это значение на еще меньшее значение ( miliSeconds / 1000 ) и прибавляет полученный результат к значению секунд (now.getSeconds).

Зачем это делается? С одной лишь целью - сделать отрисовку секундной окружности более плавной; иначе шаг прибавления будет слишком большим и резким - получится некрасиво.

В результате код для отрисовки секундной окружности будет таким:

// SECONDS
clock.beginPath();
clock.arc( 250, 250, 160, degreeToRadian(270), degreeToRadian( newSeconds * 6 - 90 ) );
clock.stroke();

Часы - дата и время

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

1
fillText
, а также воспользоваться методом
1
font
для настройки отображения шрифта:

// DATE
clock.font = '700 24px Arial, sans-serif';
clock.fillStyle = '#28d1fa';
clock.fillText(today, 175, 250);

// TIME
clock.font = '14px Arial, sans-serif';
clock.fillText(time, 185, 270);

Не забудем спозиционировать текст так, чтобы он нормально смотрелся и посмотрим на полученный результат - JavaScript Canvas Clock - Date and Time.

Добавляем градиент

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

В canvas существует два типа градиента - линейный и радиальный. В данном случае будет применяться радиальный градиент.

Градиенты в canvas добавляются совсем не так, как в CSS. Здесь это объект, у которого есть свой метод

1
addColorStop()
; создается градиент при помощи функции-конструктора
1
createLinearGradient(x,y,x1,y1)
или
1
createRadialGradient(x,y,r,x1,y1,r1)
.

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

// BACKGROUND
var gradient = clock.createRadialGradient( 250, 250, 5, 250, 250, 300 );
gradient.addColorStop( 0, '#09303a' );
gradient.addColorStop( 1, '#000' );
clock.fillStyle = gradient;
clock.fillRect( 0, 0, 500, 500 );

Здесь цвет, идущий (позиция 0) из центра радиального градиента - это:

// BACKGROUND
...
gradient.addColorStop( 0, '#09303a' );

… а цвет, на котором радиальный градиент останавливается (позиция 1), это:

// BACKGROUND
...
gradient.addColorStop( 1, '#000' );
...

Не забываем изменить строку

1
clock.fillStyle = '#000';
на строку
1
clock.fillStyle = gradient;
и посмотрим на готовый результат - JavaScript Canvas Clock - Radial Gradient.

В коде выше были использованы еще два метода canvas для создания тени -

1
shadowBlur
(размытие тени) и
1
shadowColor
(цвет тени). Так результат смотрится лучше.

canvas в image

Напоследок можно воспользоваться методом canvas под названием

1
toDataURL
. Этот метод может брать текущий отрисованный canvas и конвертировать его в картинку (в формате data URI).

Эту картинку можно использовать как угодно, но в том числе - вставлять на HTML-страницу. Этот подход можно применить и заменить картинкой canvas.

В результате canvas будет генерировать картинку с частотой 40 миллисекунд и с такой же частотой вставлять ее на страницу. А canvas убрать. Визуально подмены не будет заметно.

Можно проверить, правда ли на странице картинка, а не canvas. Элементарно - правый клик мыши на изображении и смотрим - в контекстном меню есть пункт “Save image as…”. Будь на этом месте canvas, такого “фокуса” бы не получилось.

Делается это в угоду браузеров, которые не понимают canvas. Правда, такой способ “подвешивает” браузер со страшной силой (по крайней мере, так дело обстоит у меня). Оно и понятно (если я не ошибаюсь) - попробуйте рисовать каждые 40 миллисекунд новую картинку )

Ниже - код:

<canvas id="clock" class="clock"></canvas>
<img src="" id="image" alt="Clock"/>
.clock {
    display: none;
}
document.querySelector('#image').src = clock.canvas.toDataURL();

Тут стоит заметить один важный момент - метод

1
toDataURL()
относится к canvas, но не к ‘2d’-контексту canvas’а.

Смотрим окончательный готовый результат (слегка измененный) - JavaScript Canvas Clock - Image.

P.S.

Тут вышла заминка - даже CodePen с трудом справляется с такой задачей (но если подождать, то можно увидеть).

Поэтому продублировал результат простой картинкой на странице, чтобы можно было видеть, как должно получиться:

JavaScript Canvas Clock - Image

На этом все.


Что такое модульная система в Node.js и как ею пользоваться. Статья мне очень понравилась - в ней автор подробно и внятно доносит тему.

Кроме того, это тот самый случай, когда я наконец нашел ответ на свой вопрос.

Итак - первоисточник находится здесь - Руководство для начинающих по Node.js от Felix’a.


Для организации программ в виде отдельных файлов Node.js предлагает к использованию модульную систему.

Для демонстрации подхода создадим файл

1
main.js
со следующим содержимым:

var hello = require('./hello');
hello.world();

Нетрудно догадаться, что функция

1
require('./hello')
используется для импорта данных из отдельного JavaScript-файла.

1
./
- означает, что файл находится в той же директории, что и сам файл
1
main.js
. Также следует обратить внимание на то, что не требуется указывать расширение файла, так как
1
.js
подставляется по умолчанию.

Далее необходимо создать файл (модуль)

1
hello.js
со следующим содержимым:

exports.world = function () {
  console.log('Hello World');
}

Тут следует обратить внимание на то, что происходит присваивание свойству

1
world
объекта
1
exports
.

Таким образом объявляется, что модуль

1
hello.js
экспортирует во вне функцию
1
world
.

Объект

1
exports
доступен в любом модуле и возвращается при каждом вызове функции
1
require
при подключении модуля.

При запуске

1
main.js
вывод будет следующим:

$ node main.js
Hello World

Будет не лишним упомянуть, что зачастую объект

1
exports
переопределяется следующим образом:

module.exports = function () {
  // ...
}

Как и ожидается, такое переопределение будет причиной того, что

1
require
будет возвращать функцию.

Такой подход полезен при объекто-ориентированном программировании (ООП), где каждый файл экспортирует конструктор одного класса.

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

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

Например:

var http = require('http');

Первое, что сделает Node.js - это определит, есть ли модуль ядра с именем

1
http
. Если есть, то вернет соответствующую директорию.

Но что будет при работе с модулями, не относящимися к ядру, как, например,

1
mysql
?

var mysql = require('mysql');

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

1
node_modules
. Если такая директория найдена, Node.js начнет поиск в ней файла с именем
1
mysql.js
.

Если подходящих файлов не найдено и достигнут корень файловой системы (

1
/
), Node.js сдается и выбрасывает исключение.

В настоящий момент Node.js учитывает дополнительный изменяемый список альтернативных директорий для импорта. Список доступен через массив

1
require.paths
. Однако идет активное обсуждение на тему удаления этой возможности, так что лучше пока не использовать ее.

И наконец, Node.js учитывает файл

1
index.js
, в котором описывается главный файл импорта для директории.

Таким образом, если встретится вызов

1
require('./foo')
, то Node.js будет пробовать искать файлы и
1
foo.js
и
1
foo/index.js
.

JavaScript-flood продолжается … )


Библиотека LoDash - это развитие и продолжение библиотеки Underscore.js. Если выбирать, какой библиотекой пользоваться, то выбор будет однозначно за LoDash. Достаточно сравнить колличество методов Underscore.js vs LoDash - LoDash Documentation.

Еще один большой плюс LoDash - это ее модульность. Каждый ее метод доступен в виде отдельного модуля \ модулей, который \ которые можно подключить и использовать; а не “тянуть” всю библиотеку (хотя она и небольшая - 4\22 kB).

Например, подключить только методы для работы с массивами:

var array = require('lodash/array');

Или же подключить только один метод

1
chunk
, чтобы уменьшить итоговую сборку:

var chunk = require('lodash/chunk');

Или вот так - нам нужны только два модуля

1
random
и
1
foreach
для генерации случайный фоновых заливок у коллекции блоков.

Тогда можно поступить так - устанавливаем всего два модуля вместо всей библиотеки lodash:

$ npm i --save lodash.random
$ npm i --save lodash.foreach

… и используем их, ибо только они нам нужны сейчас:

var random = require('lodash.random');
var forEach = require('lodash.foreach');

var items = document.querySelectorAll('.gallery\_\_item');
forEach( items, function (el) {
el.style.backgroundColor = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
});

В этой статье я познакомлюсь с пятью методами LoDash, которые я отложил “на потом” при знакомстве с Underscore.js. Это методы для работы с функциями -

1
.delay()
,
1
.once()
,
1
.after()
,
1
.before()
,
1
.debounce()
.

lodash - .delay()

Метод

1
.delay()
очень прост, но тем не менее - весьма полезен на практике. Это возможность запускать функцию с временной задержкой.

Самый простой пример - через 2 секунды в консоли браузера появится сообщение

1
Hello World
:

delay(function (text) {
console.log(text);
}, 2000, 'Hello World' );

Справка по этому методу в официальной документации - Lodash - Delay.

lodash - .once()

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

Простой пример с кнопкой -

1
alert
выскочит только один раз при нажатии кнопки:

function iAmOnce() {
alert('Hello!');
}
var callMeOnce = once(iAmOnce);
document.querySelector('#once').addEventListener('click', callMeOnce, false);

Второй пример интереснее. Посмотрим на разметку. Затем на javascript-код.

<div class="block"></div>
<div id="secondo" class="block">
    <div id="count"></div>
</div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>
<div class="block"></div>

Эта часть кода - украшательство, генерирование случайного цвета для каждого из блоков

1
class="block"
при помощи метода .random():

\$(document).ready( function () {
var blocks = document.querySelectorAll('.block');
forEach( blocks, function (el) {
el.style.backgroundColor = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
});

Здесь функция

1
callOnce()
сработает только один раз и при условии, когда блок
1
secondo
“поднимется” от нижней границы окна браузера на высоту 85% от общей высоты окна.

1
iterrator
“отщекнется” один раз и изменит содержимое блока
1
id="count"
с 0 на 1:

$(document).ready( function () {
    var second = $('#secondo');
var iterrator = 0;
var counter = \$('#count').text(iterrator);

    var callOnce = once( function () {
        ++iterrator;
        counter.text(iterrator);
    });

    $(window).on('scroll', function () {
        if ( $(window).scrollTop() > second.offset().top - $(window).height() * 0.85 ) {
            callOnce();
        }
    });

});

Весьма полезный на практике прием. Официальная справка по методу

1
.once()
находится здесь - LoDash - Once Method.

lodash - .before()

Метод

1
.before()
- счетчик выполнения функции; метод запустит функцию менее n-раз (less than n times). В приведенном ниже примере клик по элементу
1
#before
сработает 9 раз и запустит функцию 9 раз (и не более того):

var sayHello = function () {
console.log('Hello');
};
document.querySelector('#before').addEventListener('click', before(10, sayHello), false);

Можно сказать и так - метод

1
.before()
ограничивает максимальное количество запусков функции - не более n-раз.

Официальная документация по методу

1
.before()
находится здесь - LoDash - Before Method.

lodash - .after()

Метод

1
.after()
- еще один счетчик выполнения функции. Прямая противоположность методу
1
.before()
.

Этот метод ограничивает количество попыток запуска функции - после n-попыток функцию можно будет запускать неограниченное количество раз:

var sayAfter = function () {
console.log('After');
};
document.querySelector('#after').addEventListener('click', after(5, sayAfter), false);

Код выше выведет в консоль браузера текст

1
After
только после пятой попытки и далее сколько угодно раз. Где на практике можно применить такой подход - не приходит на ум, если честно )

Официальная документация по методу

1
.after()
находится здесь - LoDash - After Method.

lodash - .debounce()

Интересный и полезный на практике метод - запустить функцию

1
function
с временной задержкой; отсчет временной задержки идет с момента последнего запуска функции
1
function
.

Метод имеет большое количество аргументов - ссылка на официальную документацию - Lodash - Debounce Method.

Пример ниже будет выводить окно

1
Call me after 2 second!
с задержкой в 2 секунды после события
1
resize
- то есть, когда будут изменены размеры окна браузера:

var callMe = function () {
alert('Call me after 2 second!')
};
\$(window).on( 'resize', debounce( callMe, 2000 ) );

Заключение

Вот я в меру своих сил и освоил несколько интересных методов библиотеки LoDash. Наиболее полезными для меня показались методы

1
.debounce()
и
1
.once()
.

Что сказать - библиотека LoDash просто замечательная и чрезвычайно полезная. К тому же она постоянно развивается - ее автор John-David Dalton не устает ее совершенствовать.

Немного пофлудил по JavaScript … останавливаться пока не собираюсь )


Статья посвящена моему знакомству с underscore.js. Я рад, что познакомился с этой библиотекой. Кто плохо владеет english, то здесь находится переведенная на русский язык документация - underscore.js russian;

Что такое underscore.js? Это отличная библиотека с набором javascript-методов для удобной работы с массивами, коллекциями, функциями, объектами, а также просто утилиты.

Чтобы оценить полезность и удобство работы с этой библиотекой, нужно на практике рассмотреть примеры работы методов underscore.js. Поэтому ниже будут представлены именно примеры.

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

Введение

Библиотека underscore.js живет по этому адресу - underscore.js. Подключается как любая другая javascript-библиотека или javascript-плагин - через тег script. Есть вариант установки под Nodejs:

npm install underscore

В документе вызывается через символ подчеркивания - _ (отсюда и название библиотеки). Кстати, расширенный вариант underscore - lodash также обыгрывает этот вариант самоназвания ( lodash == low dash ).

underscore.js - работа с коллекциями

Список методов для работы с коллекциями достаточно обширен - Collection Functions. Рассмотрим некоторые из них.

Метод each()

Служит для перебора всех элементов коллекции и применения функции к каждому из элементов. Код ниже пробежится по всем элементам коллекции a1 и выведет каждый из этих элементов в консоль браузера:

var a1 = [ 1, 2, 3 ];
each( a1, function (el) { console.log(el) });

Более сложный пример применения метода each(). Уже здесь начинается демонстрация привлекательности underscore.js.

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

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
each( a2, function (el) { each( el, function (element) { console.log(element) } ) });

Вкратце - имеется массив a2, элементы которого - объекты со свойствами. Внешний цикл пробегается по элементам массива.

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

Метод map()

Метод map() получает на вход массив и возвращает новый массив, созданный путем преобразования элементов оригинального массива \ коллекции.

Например, код ниже берет массив a3, вызывает каждый из элементов этого массива, умножает этот элемент на 3 и помещает в новый массив a4, как элемент этого массива:

var a3 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var a4 = map( a3, function (el) { return el\*3 } );

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

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var a5 = map( a2, function (el) { el.sal += 500 } );

Функция для обработки элементов коллекции может быть любой. К примеру, можно создать новый массив, состоящий только из значений ключей spec, name или sal:

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var specs = map( a2, function (el) { return el.spec } );
var names = map( a2, function (el) { return el.name } );
var salaries = map( a2, function (el) { return el.sal } );

Метод partition()

Метод partition() создает из одного массива новый массив, который состоит из двух подмассивов.

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

var a3 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var splitArr = partition( a3, function (el) { if ( el % 2 === 0 ) { return el } } );

Метод partition() также хорошо умеет работать и с коллекциями. К примеру, код ниже отсортирует массив из объектов на основе условия - значения ключа sal:

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var splitSalary = partition( a2, function (el) { if ( el.sal < 4000 ) { return el } } );

Метод shuffle()

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

var a2 = [
{ name: 'John', spec: 'Ruby', sal: 2000 },
{ name: 'Mary', spec: 'Python', sal: 3000 },
{ name: 'Peter', spec: 'JavaScript', sal: 4000 },
{ name: 'Jefferson', spec: 'HTML', sal: 1000 },
{ name: 'Abdul', spec: 'C++', sal: 10000 }
];
var a3 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var shuffledArrObj = shuffle(a2);
var shuffledArr = shuffle(a3);

Метод find()

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

var findExample = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var findResult = find( findExample, function (el) {
return el % 2 == 0;
});

Метод reduce()

Этот метод выполняет “склеивание” элементов массива \ коллекции - все значения элементов будут объединены в одно значение. Функция-обработчик имеет вид function ( memo, value ), где memo - это начальное значение редукции:

var reduceExample = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var reduceResult = reduce( reduceExample, function (memo, el) {
return memo + el;
});

Метод reduceRight() будет делать тоже самое, что и метод reduce(), но только справа-налево.

Метод filter()

Метод для поиска элементов массива по какому-то условию. Возвращает новый массив:

var filterExample = [ 1,2,3,4,5,6,7,8,9 ];
var filterResult = filter( filterExample, function (el) {
return el % 3 === 0
});

Замечание по поводу метода filter() и его различия с методом map().

“… underscore\lodash - методы map() и filter() - у них есть различие между собой? в оф. документации говорится, что метод map() возвращает массив преобразованных элементов; метод filter() возвращает массив элементов, удовлетворяющих условию. но ведь я могу (?) подставить в оба метода любую (?) фунцию?
и метод filter() будет возвращать массив преобразованных элементов? или я что-то не понимаю? …”

“… Теоретически - да, они идентичны. Но у них разное назначение - если хочется фильтровать, то используй filter, хочешь преобразовывать - map. Могу предположить (я не знаю точно), что из соображений быстродействия filter передает в функцию объект по ссылке, а не его копию, поэтому изменения объекта будут работать, но это значит, что ты отдаешься во власть реализации метода. И никто не гарантирует, что в один день underscore/lodash не начнет передавать копию объекта. В этом случае твой код может поломаться. Поэтому я бы использовал функции по назначению. …”

Метод pluck()

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

Синтаксис метода до чрезвычайности прост - указываем имя обрабатываемой коллекции и названия ключа, значение которого хотим получить:

var school = [ { name: 'Mary', age: 12 }, { name: 'John', age: 10 }, { name: 'Peter', age: 13 }, { name: 'David', age: 11 }, { name: 'George', age: 15 } ];
var schoolNames = pluck( school, 'name');

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

underscore.js - работа с массивами

Рассмотренные выше несколько методов underscore.js могут одинаково хорошо работать как с массивами, так и с коллекциями.

Однако, помимо этих методов, у underscore.js есть методы исключительно для работы с массивами. По этой ссылке можно ознакомиться со списком таких методов - Underscore.js - Arrays.

Метод first()

Этот метод возвращает первый элемент массива - все просто и понятно:

var a1 = [ 1, 2, 3 ];
var firstEl = first(a1);

Есть метод last(), который аналогичен методу firts(), но возвращает последний элемент коллекции\массива.

Метод flatten()

Этот метод преобразует массив с вложенными подмассивами (многоуровневый массив) в один, одноуровневый массив:

var fltArr = flatten( [ 11, 12, 13, [ 21, 22, 23, [ 31, 32, 33, [ 41, 42, 43, [ 51, 52, 53, [ 61 ] ] ] ] ] ] );

Метод zip()

Этот метод интересен и является примером того, насколько библиотека underscore.js удобная и изящная. Допустим, имеются три массива:

var r1 = [ '1-1', '1-2', '1-3' ];
var r2 = [ '2-1', '2-2', '2-3' ];
var r3 = [ '3-1', '3-2', '3-3' ];

Если передать эти три массива на вход методу zip(), то он преобразует их в один массив, состоящий из трех подмассивов. При этом элементы этих подмассивов будут выглядеть таким образом:

var r4 = zip( r1 , r2 , r3 );
r4 = [ [ '1-1', '2-1', '3-1' ], [ '1-2', '2-2', '3-2' ], [ '1-3', '2-3', '3-3' ] ];

Теперь представьте, что вам пришлось бы затратить достаточно времени для написания кода на нативном JavaScript, чтобы обработать такой пример.

Метод object()

Этот метод преобразует несколько массивов в один объект:

var names = [ 'Peter', 'Mary', 'John' ];
var ages = [ 30, 20, 40 ];
var peoples = object( names, ages );
peoples = {Peter: 30, Mary: 20, John: 40};

underscore.js - работа с функциями

Методы работы с функциями с underscore.js “расположены” здесь - Underscore.js - Functions. Их не так много. Они немного сложнее для понимания. Хотя ничего сложного нет.

Метод bind()

Этот метод “привязывает” исполнение функции только к определенному объекту. Чтобы было понятнее, начнем из далека, из классики.

Создадим три объекта, у каждого из которых будет ключ name:

var someObj1 = {};
var someObj2 = {};
var someObj3 = {};
someObj1.name = 'Peter';
someObj2.name = 'Mary';
someObj3.name = 'John';

Создадим функцию greeting такого вида:

var greeting = function (el) {
console.log( el + ', ' + this.name );
};

А теперь вызовем функцию greeting на каждом из двух первых объектов:

greeting.apply( someObj1, ['Hello'] );
greeting.apply( someObj2, ['Holla'] );

Результатом будет такой текст:

Hello, Peter
Holla, Mary

… потому что this.name каждый раз будет ссылаться на разный объект - в зависимости от того, на котором из объектов было вызвано исполнение функции.

А теперь воспользуемся методом bind() библиотеки underscore.js таким образом:

var greetingBind = bind( greeting, someObj3 );

Что мы сделали в этом коде? Мы создали новый экземпляр greetingBind функции greeting и привязали исполнение этого нового экземпляра на объект someObj3.

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

greetingBind('Welcome');
greetingBind('Chao');

Вывод кода будет таким:

Welcome, John
Chao, John

Такой способ привязки функции к объекту носит название карринг и мы только что познакомились с ним на практике.

Есть еще методы debounce(), once(), after() - с ними еще не познакомился. Но они весьма любопытны и полезны.

underscore.js - работа с объектами

Метод keys()

Преобразует ключи объекта в массив:

var fruits = { apple: 10, melon: 20, potato: 30, tomato: 50 };
var fruitSorts = keys( fruits );

Метод values()

Преобразует значения ключей объекта в массив:

var fruitsvalues = values( fruits );

Метод pairs()

Преобразует пары ключ-значение объекта в массив, состоящий из подмассивов:

var fruitsPairs = pairs( fruits );

Метод invert()

Инвертирует пару ключ:значение в объекте:

var fruitsInvert = invert( fruits );

Метод pick()

Вернет новый объект, в котором будут только указанные ключи:

var fruitsPicked = pick( fruits, [ 'melon', 'tomato' ] );

Метод clone()

Возвращает полную копию оригинального объекта:

var man = { weight: 80, age: 30, height: 180, gender: 'male' };
var manDouble = clone( man );
man.age = 32;
manDouble.gender = 'female';
console.log( man );
console.log( manDouble );

Метод extend()

Копируем свойства одного объекта в другой:

var brick = { weight: 200, width: 250, height: 150, thickness: 100 };
var color = { color: 'red' };
var brickFull = extend( brick, color );

underscore.js - утилиты

Метод random()

Возвращает случайное число из диапазона min - max ( включительно нижнюю и верхнюю границы )

var rnd = random( 0, 255 );

Метод now()

Возвращает текущее время:

var currTime = now();
console.log( currTime );

Метод times()

Запускаем функцию на исполнение три раза:

times(3, function () {
console.log( 'Holla!' );
});

underscore.js - template

Интересная возможность создания шаблонов. Кто знаком с HTML-шаблонизаторами (такими, как Pug (бывший Jade)), станет все сразу понятно с первого взгляда.

Кому не станет ясно с первого взгляда, есть хорошая статья у Ильи Катора - Шаблонизатор LoDash.

Но по коду, как мне кажется, станет ясно без объяснений:

<button id="add" type="button">add underscore template</button>

<div class="canvas"></div>
var users = [ 'Peter', 'Mary', 'John', 'Josef' ];
var messages = [ 'Hello', 'Holla', 'Welcome', 'Greeting' ];
var block;
var compiled = template( "<dl class='info'><dt><%= name %></dt><dd><%= message %></dd></dl>" );

function insertBlock () {
block = compiled({ name: users[ random( 0, users.length-1 ) ], message: messages[ random( 0, messages.length-1 ) ] });
document.querySelector('.canvas').innerHTML += block;
}

document.querySelector('#add').addEventListener('click', insertBlock, false);

Заключение

Личное впечатление от underscore.js - я очень доволен, что познакомился с этой библиотекой! Она крайне полезная, удобная и приятная в работе. Теперь буду стараться использовать ее там, где это понадобится.

Еще стоит заметить один немаловажный факт - некоторые методы underscore\lodash планируются\внесены в стандарт ES6 (например, методы map() и filter()).