Вторая часть официальной документации по Fabric. Речь идет об анимации объектов на Canvas, применении стандартных фильтров к изображениям или создании пользовательских фильтров.

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


Это вторая часть серии статей об открытой Javascript Canvas библиотеке Fabric.js, которую мы используем на printio.ru для редактора дизайнов.

В первой части этой серии мы ознакомились с самыми базовыми аспектами canvas библиотеки Fabric.js. Мы узнали, чем может быть полезна Fabric, рассмотрели ее объектную модель и иерархию объектов; увидели, что существуют как простые фигуры (прямоугольник, треугольник, круг), так и сложные (SVG). Научились выполнять простые операции над этими объектами.

Ну вот, разобрались с азами, давайте приступать к более интересным вещам!

Анимация

Любая уважающая себя canvas-библиотека в наше время включает в себя средства работы с анимацией. Fabric — не исключение. Ведь мы имеем мощную объектную модель и гибкие графические возможности. Было бы грех не уметь это приводить в движение.

Вы наверное помните, как менять атрибут у объекта. Просто вызываем метод set, передавая соответствующее значение:

rect.set('angle', 45);

Анимировать объект можно по такому же принципу и с такой же легкостью. Каждый объект в Fabric имеет метод animate (наследуя от fabric.Object) который … анимирует этот объект:

rect.animate( 'angle', 45, {
  onChange: canvas.renderAll.bind(canvas)
});

Первый аргумент - это атрибут, который хотим менять. Второй аргумент — финальное значение этого атрибута.

Например, если прямоугольник находится под углом -15° и мы указываем 45°, то угол постепенно изменится с -15° до 45°.

Ну, а последний аргумент — опциональный объект для более детальных настроек (длительность, вызовы, easing и т. д. )

animate кстати имеет очень полезную функциональность — поддержку относительных значений. Например, если нужно подвинуть объект на 100px вправо, то сделать это очень просто:

rect.animate('left', '+=100', { onChange: canvas.renderAll.bind(canvas) });

По такому же принципу, для поворота объекта на 5 градусов против часовой стрелки:

rect.animate('angle', '-=5', { onChange: canvas.renderAll.bind(canvas) });

Вы наверняка заметили, что мы постоянно указываем вызов 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).

Например, вот так можно подвинуть объект вправо, при этом “отпружинивая”” в конце:

rect.animate('left', 500, {
  onChange: canvas.renderAll.bind(canvas),
  duration: 1000,
  easing: fabric.util.ease.easeOutBounce
});

Заметьте, что мы используем 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 или собственный фильтр.

Ну вот, к примеру, сделаем картинку черно-белой:

fabric.Image.fromURL( 'pug.jpg', function ( img ) {
  
  // добавляем фильтр
  img.filters.push( new fabric.Image.filters.Grayscale() );
  
  // применяем фильтры и перерисовываем canvas после применения
  img.applyFilters( canvas.renderAll.bind( canvas ) );
  
  // добавляем изображения на холст
  canvas.add( img );

});

Fabric Image

А вот так можно сделать сепию:

fabric.Image.fromURL( 'pug.jpg', function ( img ) {
  
  img.filters.push( new fabric.Image.filters.Sepia() );
  
  img.applyFilters( canvas.renderAll.bind( canvas ) );
  
  canvas.add( img );

});

Fabric Image

С атрибутом filters можно делать все тоже что и с обычным массивом — удалить фильтр (с помощью pop, splice, или shift), добавить фильтр (с помощью push, splice, unshift) или даже соединить несколько фильтров.

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

fabric.Image.fromURL( 'pug.jpg', function ( img ) {

  img.filters.push(
    new fabric.Image.filters.Sepia(),
    new fabric.Image.filters.Brightness({ brightness: 100 }));

  img.applyFilters(canvas.renderAll.bind( canvas ));
  
  canvas.add( img );

});

Fabric Image

Заметьте, что мы передали { brightness: 100 }-объект в Brightness-фильтр. Это потому, что некоторым фильтрам ничего дополнительного не нужно; а некоторым (например - grayscale, invert, sepia) надо указать определенные параметры.

Для фильтра яркости это собственно само значение яркости (0-255). У фильтра шума, это значение шума (0-1000). А у фильтра удаления белого фона (‘remove white’) есть порог (‘threshold’) и расстояние (‘distance’).

Ну вот разобрались с фильтрами; пора создать свой!

Образец для создания фильтров будет довольно прост. Нам нужно создать ‘класс’’ и написать метод applyTo. Опционально мы можем дать фильтру toJSON-метод (поддержка JSON-сериализации) и/или initialize (если фильтр имеет дополнительные параметры):

fabric.Image.filters.Redify = fabric.util.createClass({

  type: 'Redify',

  applyTo: function ( canvasEl ) {
    var context = canvasEl.getContext( '2d' ),
        imageData = context.getImageData( 0, 0, canvasEl.width, canvasEl.height ),
        data = imageData.data;

    for ( var i = 0, len = data.length; i < len; i += 4 ) {
      data[ i + 1 ] = 0;
      data[ i + 2 ] = 0;
    }

    context.putImageData( imageData, 0, 0 );
  }
});

fabric.Image.filters.Redify.fromObject = function ( object ) {
  return new fabric.Image.filters.Redify( object );
};

Fabric Image

Не вникая сильно в подробности кода, стоит заметить, что самое главное происходит в цикле, где мы меняем зеленую (data[i+1]) и голубую (data[i+2]) компоненты каждого пикселя на 0, по сути дела удаляя их.

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

Примечание чтеца: для желающих - более подробно аналог представленного выше кода описан в моей статье Canvas - Raw Pixel.

Как видите, applyTo-метод получает в себя canvas-элемент, который представляет собой изображение. Имея такой canvas, мы можем пройтись по всем пикселям изображения (getImageData().data) изменяя их как нам угодно.

Цвета

Независимо от того, с чем вам удобней работать — hex, RGB, или RGBA-форматами цвета — Fabric упрощает утомительные операции и переводы из одного формата в другой.

Давайте посмотрим на несколько способов определить цвет в Fabric:

new fabric.Color( '#f55' );
new fabric.Color( '#123123' );
new fabric.Color( '356735' );
new fabric.Color( 'rgb( 100, 0, 100 )' );
new fabric.Color( 'rgba( 10, 20, 30, 0.5 )' );

Перевод формата происходит очень просто. Метод toHex() переводит цвет в hex. Метод toRgb() — в RGB, а метод toRgba() — в RGB с альфа каналом (прозрачностью):

new fabric.Color( '#f55' ).toRgb(); // => "rgb( 255, 85, 85 )"
new fabric.Color( 'rgb( 100, 100, 100 )' ).toHex(); // => "646464"
new fabric.Color( 'fff' ).toHex(); // => "FFFFFF"

Кстати, можно делать не только перевод. Можно “накладывать” цвета один на другой или делать из них черно-белый вариант:

var redish = new fabric.Color('#f55');
var greenish = new fabric.Color('#5f5');

redish.overlayWith(greenish).toHex(); // => "AAAA55"
redish.toGrayscale().toHex(); // => "A1A1A1"

Градиенты

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

Fabric поддерживает градиенты с помощью метода setGradient (setGradientFill до 1.1.0 версии), который присутствует на всех объектах.

Вызывание setGradient( ‘fill’, { … } ) - это почти как выставление значения ‘fill’ у объекта, только вместо цвета используется градиент:

var circle = new fabric.Circle({
  left: 100,
  top: 100,
  radius: 50
});

circle.setGradient( 'fill', {
  x1: 0,
  y1: -circle.height / 2,
  x2: 0,
  y2: circle.height / 2,
  colorStops: {
    0: '#000',
    1: '#fff'
  }
});

Fabric Image

В этом примере мы создаем круг в точке 100/100 с радиусом в 50px. Потом выставляем ему градиент, идущий по всей высоте объекта, от черного к белому.

Как видите, метод получает в себя конфигурационный объект, в котором могут присутствовать 2 пары координат (x1/y1 и x2/y2) и объект ‘colorStops’.

Координаты указывают, где градиент начинается и где он заканчивается. colorStops указывают - из каких цветов он состоит.

Вы можете определить сколько угодно цветов; главное, чтобы их позиции находились в интервале от 0 до 1 (например 0, 0.1, 0.3, 0.5, 0.75, 1). 0 представляет начало градиента, 1 — его конец.

Чтобы понять, как мы определили координаты, посмотрим на изображение ниже:

Fabric Image

Так как координаты относительны центру объекта, то верхней точкой является -circle.height / 2, а нижней - circle.height / 2. Координаты по ширине (x1/x2) определяем точно так же.

Вот пример красно-голубого градиента, идущего слева направо:

circle.setGradient( 'fill', {
  x1: -circle.width / 2,
  y1: 0,
  x2: circle.width / 2,
  y2: 0,
  colorStops: {
    0: "red",
    1: "blue"
  }
});

Fabric Image

А вот 5-ти шаговый градиент-радуга, с цветами, занимающими по 20% всей длины:

circle.setGradient( 'fill', {
  x1: -circle.width / 2,
  y1: 0,
  x2: circle.width / 2,
  y2: 0,
  colorStops: {
    0: "red",
    0.2: "orange",
    0.4: "yellow",
    0.6: "green",
    0.8: "blue",
    1: "purple"
  }
});

Fabric Image

А вы можете придумать что-нибудь интересное?

Текст

Что если нужно отобразить не только картинки и векторные формы на холсте, а еще и текст? Fabric умеет и это! Встречайте fabric.Text.

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

Ведь canvas имеет встроенные методы fillText и strokeText.

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

А вот создав объект типа fabric.Text, мы можем работать с ним как и с любым другим объектом на холсте — двигать его, масштабировать, менять атрибуты, и т. д.

Вторая причина — чтобы иметь более богатый функционал, чем то что дает нам canvas. Некоторые вещи, которые есть в Fabric, но нет в родных методах:

  • Многострочность - Родные методы позволяют написать только одну линию, игнорируя переходы строки
  • Выравнивание текста - Влево, по центру, вправо. Полезно во время работы с многострочным текстом
  • Фон текста - Фон выводится только под самим текстом, в зависимости от выравнивания
  • Декорация текста - Подчеркивание, надчеркивание, перечеркивание
  • Высота строки - Полезно во время работы с многострочным текстом

Ну что ж, давайте посмотрим на вездесущий “hello world”?

var text = new fabric.Text( 'hello world', { left: 100, top: 100 });
canvas.add( text );

Вот и все! Для показа текста необходимо всего лишь добавить объект типа fabric.Text на холст, указывая нужную позицию.

Первый параметр необходим — это собственно сама строка текста. Второй аргумент — опционально конфигурация, как обычно; можно указать left, top, fill, opacity, и т. д.

Помимо обычных атрибутов, у текстовых объектов конечно же есть и свои, относящиеся к тексту. Вкратце, об этих атрибутах:

fontFamily

По умолчанию - “Times New Roman”. Позволяет менять семейство шрифта для текста:

var comicSansText = new fabric.Text( "I'm in Comic Sans", {
  fontFamily: 'Comic Sans'
});

Fabric Image

fontSize

Контролирует размер текста. Заметьте, что в отличие от других объектов в Fabric, мы не можем менять размер текста с помощью width/height. Вместо этого как раз и используется fontSize и конечно же scaleX/scaleY:

var text40 = new fabric.Text( "I'm at fontSize 40", {
  fontSize: 40
});
var text20 = new fabric.Text( "I'm at fontSize 20", {
  fontSize: 20
});

Fabric Image

fontWeight

Позволяет сделать текст жирнее или тоньше. Точно также как в CSS, можно использовать или слова (“normal”, “bold”) или номерные значения (100, 200, 400, 600, 800).

Важно понимать, что для определенной толщины нужно иметь соответствующий шрифт. Если в шрифте не присутствует “bold” (жирный) вариант, например, то жирный текст может не отобразиться:

var normalText = new fabric.Text( "I'm a normal text", {
  fontWeight: 'normal'
});
var boldText = new fabric.Text( "I'm a bold text", {
  fontWeight: 'bold'
});

Fabric Image

textDecoration

Позволяет добавить тексту перечеркивание, надчеркивание или подчеркивание. Опять же, эта декларация работает также, как в CSS.

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

var underlineText = new fabric.Text( "I'm an underlined text", {
  textDecoration: 'underline'
});
var strokeThroughText = new fabric.Text( "I'm a stroke-through text", {
  textDecoration: 'line-through'
});
var overlineText = new fabric.Text( "I'm an overline text", {
  textDecoration: 'overline'
});

Fabric Image

shadow

До версии 1.3.0 этот атрибут назывался “textShadow”.

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

Это все должно быть знакомо, если вы до этого работали с тенями в CSS. Меняя эти 4 опции, можно добиться многих интересных эффектов:

var shadowText1 = new fabric.Text( "I'm a text with shadow", {
  shadow: 'rgba(0,0,0,0.3) 5px 5px 5px'
});

var shadowText2 = new fabric.Text( "And another shadow", {
  shadow: 'rgba(0,0,0,0.2) 0 0 5px'
});

var shadowText3 = new fabric.Text( "Lorem ipsum dolor sit", {
  shadow: 'green -5px -5px 3px'
});

Fabric Image

fontStyle

Стиль текста. Может быть только один из двух: normal или italic. Опять же, работает так же, как и в CSS:

var italicText = new fabric.Text( "A very fancy italic text", {
  fontStyle: 'italic',
  fontFamily: 'Delicious'
});

var anotherItalicText = new fabric.Text( "another italic text", {
  fontStyle: 'italic',
  fontFamily: 'Hoefler Text'
});

Fabric Image

stroke и strokeWidth

Соединяя stroke (цвет наружнего штриха) и strokeWidth (ширину наружнего штриха), можно достичь довольно интересных эффектов.

Вот пара примеров:

var textWithStroke = new fabric.Text( "Text with a stroke", {
  stroke: '#ff1318',
  strokeWidth: 1
});

var loremIpsumDolor = new fabric.Text( "Lorem ipsum dolor", {
  fontFamily: 'Impact',
  stroke: '#c3bfbf',
  strokeWidth: 3
});

Fabric Image

Стоит отметить, что stroke был назван strokeStyle до версии 1.1.6

textAlign

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

Возможные значения: left, center, right и justify:

var text = 'this is\na multiline\ntext\naligned right!';
var alignedRightText = new fabric.Text( text, {
  textAlign: 'right'
});

Fabric Image

lineHeight

Еще один атрибут, скорее всего знакомый из CSS — lineHeight (высота строки).

Позволяет менять расстояние между строк в многострочном тексте. Вот пример текста с ‘lineHeight 3’ и второй с ‘lineHeight 1’:

var lineHeight3 = new fabric.Text( 'Lorem ipsum ...', {
  lineHeight: 3
});
var lineHeight1 = new fabric.Text( 'Lorem ipsum ...', {
  lineHeight: 1
});

Fabric Image

textBackgroundColor

И наконец, дать тексту фон можно с помощью textBackgroundColor. Заметьте, что фон заполняется только под самим текстом, а не на всю “коробку”.

Чтобы закрасить весь текстовый объект, можно использовать атрибут backgroundColor. Также видно, что фон зависит от выравнивания текста и lineHeight.

Если lineHeight очень большой, фон будет видно только под текстом:

var text = 'this is\na multiline\ntext\nwith\ncustom lineheight\n&background';
var textWithBackground = new fabric.Text( text, {
  textBackgroundColor: 'rgb(0,200,0)'
});

Fabric Image

События

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

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

Хотим узнать, когда была нажата мышка? Следим за событием mouse:down.

Как насчет, когда объект был добавлен на холст? Для этого есть object:added.

Ну, а что насчет перерисовки холста? Используем after:render.

API событий очень прост и похож на то, к чему вы скорее всего привыкли в jQuery, Underscore.js или других популярных JS-библиотеках.

Есть метод on для инициализации слушателя событий и есть метод off для его удаления.

Давайте посмотрим на пример:

var canvas = new fabric.Canvas('...');
canvas.on( 'mouse:down', function( options ) {
  console.log( options.e.clientX, options.e.clientY );
});

Мы добавили слушатель события mouse:down на canvas-объекте и указали обработчика, который будет записывать координаты, где произошло это событие.

Таким образом, мы можем видеть, где именно произошел клик на холсте. Обработчик событий получает options-объект с двумя параметрами: e — оригинальное событие и target — Fabric-объект на холсте, если он найден.

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

Например, для mouse:down но не для after:render (так как это событие не “имеет” никаких объектов, а просто обозначает что холст был перерисован):

canvas.on( 'mouse:down', function( options ) {
  if (options.target) {
    console.log('an object was clicked! ', options.target.type);
  }
});

Этот пример выведет ‘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 объекты!

var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
rect.on( 'selected', function () {
  console.log('selected a rectangle');
});

var circle = new fabric.Circle({ radius: 75, fill: 'blue' });
circle.on( 'selected', function () {
  console.log('selected a circle');
});

В этом примере слушатели “присоединяются” прямо к прямоугольнику и кругу. Вместо object:selected мы используем событие selected.

По такому же принципу, можно использовать событие modified (object:modified когда “вешаем” на холст), rotating (аналог object:rotating) и т. д.

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

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


Вот набрел на интересную библиотеку для удобной работы с 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, это делается приблизительно так:

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

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

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

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

Не совсем. Вызывая еще одну команду 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, чтобы увидеть изменения.

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

Объекты

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

Все они доступны из fabric объектов, соответственно: fabric.Circle, fabric.Triangle, 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 берет на себя заботу о состоянии холста и перерисовке. От нас требуется менять только сами объекты.

В предыдущем примере было видно, как метод 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.

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

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’. Заметьте, как три выражения используют слегка разный синтаксис.

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

Ну, а как насчет чтения? Я уже упомянул, что есть общий get(), а также набор конкретных get()* методов.

Например, для получения ‘width’ объекта можно использовать get(‘width’) или getWidth(). Для ‘scaleX’ — get(‘scaleX’) или getScaleX(), и т. д.

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

Вы наверное заметили, что в предыдущих примерах были использованы конфигурационные хэши, которые выглядели точно также, как и те, которые мы только что использовали в методе set. Это потому, что они действительно одинаковые. Объект может быть “сконфигурирован” в момент создания или позже, с помощью метода 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

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

Из-за этого мы его и не видим. Как только устанавливаем позитивные width/height, черный прямоугольник появляется в левом верхнем углу.

Fabric Image

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

Объекты 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.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

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

Разумеется, классы-потомки могут не только наследовать от 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() ):

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

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

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

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

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

Также его можно сконфигурировать через набор параметров. Такие настройки, как изменение фона холста, скрывание объектов по маске, изменение общей длины/ширины, включение/выключение интерактивности - эти и другие опции можно выставить прямо на 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 заложен функционал пользовательского доступа. Как только мы создаем холст через new fabric.Canvas(‘…’), объекты, расположенные на нем, сразу же можно выделять, двигать, масштабировать, вращать и даже группировать вместе, управляя ими как одним целым!

Fabric Image Fabric Image

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

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

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

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

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

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

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

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

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

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

Картинки

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

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

Создаем 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);

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

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

Fabric Image

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

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

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

Callback получает первым аргументом объект 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 **-элементами. Они используют одинаковый набор команд, могут быть созданы из **-элементов и сериализованы в них.

О сериализации и 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

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

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

Объект fabric.Path такой же, как и остальные объекты в Fabric, поэтому мы легко изменили его параметры (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

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

Кстати, что касается SVG-документов, Path в Fabric обычно представляет SVG **-элемент, а вот наборы таких элементов, которые очень часто можно найти в SVG документах, обычно представлены через PathGroup (*fabric.PathGroup*-объекты).

PathGroup — это всего лишь группа Path-объектов. Так как fabric.PathGroup наследует от 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 и динамически задаем для него размеры 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-метода 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 );

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

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

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

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

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

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

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

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

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

Последний момент - сделаем для всего этого “дела” обертку-функцию 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();

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

Для большей красоты и информативности можно продублировать дату и время, которые можно вывести в виде текста. В этом случае нужно воспользоваться методом fillText, а также воспользоваться методом 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. Здесь это объект, у которого есть свой метод addColorStop(); создается градиент при помощи функции-конструктора createLinearGradient(x,y,x1,y1) или 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' );
...

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

В коде выше были использованы еще два метода canvas для создания тени - shadowBlur (размытие тени) и shadowColor (цвет тени). Так результат смотрится лучше.

canvas в image

Напоследок можно воспользоваться методом canvas под названием 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();

Тут стоит заметить один важный момент - метод toDataURL() относится к canvas, но не к ‘2d’-контексту canvas’а.

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

P.S.

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

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

JavaScript Canvas Clock - Image

На этом все.


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

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

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

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

Или же подключить только один метод chunk, чтобы уменьшить итоговую сборку:

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

Или вот так - нам нужны только два модуля random и 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. Это методы для работы с функциями - .delay(), .once(), .after(), .before(), .debounce().

lodash - .delay()

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

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

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

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

lodash - .once()

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

Простой пример с кнопкой - 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>

Эта часть кода - украшательство, генерирование случайного цвета для каждого из блоков 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) + ')';
});

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

iterrator “отщекнется” один раз и изменит содержимое блока 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();
        }
    });

});

Весьма полезный на практике прием. Официальная справка по методу .once() находится здесь - LoDash - Once Method.

lodash - .before()

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

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

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

Официальная документация по методу .before() находится здесь - LoDash - Before Method.

lodash - .after()

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

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

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

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

Официальная документация по методу .after() находится здесь - LoDash - After Method.

lodash - .debounce()

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

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

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

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

Заключение

Вот я в меру своих сил и освоил несколько интересных методов библиотеки LoDash. Наиболее полезными для меня показались методы .debounce() и .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()).