Статьи

Advanced Snap.svg

В предыдущем посте мы видели, как начать работу с Snap.svg. В этом посте мы подробнее рассмотрим новые функции, упомянутые в первой статье.

маскировка

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

var paper = Snap(800, 600), img = paper.image('bigImage.jpg', 10, 10, 300, 300), bigCircle = s.circle(150, 150, 100); 

На данный момент круг закрывает центр изображения.

Жаль, что вы можете иметь только прямоугольные изображения. Возможно, ваш дизайнер создал красивые круглые кнопки или изображения. Конечно, есть несколько решений, но все они оставляют вас с другой проблемой: в лучшем случае, дизайнер может дать вам изображение с внешним видом, соответствующим фону страницы, чтобы оно выглядело круглым. Однако, если у вас есть сплошной фон, если вам нужно изменить его цвет, вам придется редактировать изображение. Вы можете использовать прозрачность, но вам понадобятся более тяжелые форматы, такие как PNG, или плохое качество с GIF. Возможно, через несколько лет WebP будет полностью поддерживаться всеми браузерами, и это положит конец загадке. В любом случае, если вам нужна интерактивность для вашего изображения, вы застрянете с прямоугольной формой, реагирующей на такие события, как mouseenter mouseout click , click и т. Д.

Давным-давно занимаясь Flash, одной из самых неприятных вещей в SVG было отсутствие возможности использовать маски , представленные в SVG 1.1). В Snap применить маску к любому элементу, включая изображения, довольно просто:

 bigCircle.attr('fill', '#fff'); //This is IMPORTANT img.attr({ mask: bigCircle }); 

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

Очевидно, что вы можете комбинировать различные формы для создания сложных масок. Snap предлагает немного синтаксического сахара, чтобы помочь вам:

 var smallRect = paper.rect(180, 30, 50, 40), bigCircle = paper.circle(150, 150, 100), mask = paper.mask(bigCircle, smallRect); mask.attr('fill', 'white'); img.attr({ mask: mask }); 

Метод Paper.mask() эквивалентен Paper.g() и фактически может быть легко заменен им.

вырезка

Обтравочные контуры ограничивают область, к которой может быть применена краска, поэтому любые части чертежа за пределами области, ограниченной текущим активным обтравочным контуром, не рисуются. Обтравочный контур можно рассматривать как маску, в которой видимые области (в пределах обтравочного контура) имеют альфа-значение 1, а скрытые области имеют альфа-значение 0. Единственное отличие состоит в том, что, хотя области, скрытые масками, будут, тем не менее, реагировать на события Вырезанные области не будут.

Snap не имеет ярлыков для отсечения, но вы можете установить свойства clip , clip-path и clip-route любого элемента, используя метод attr() .

Градиенты

SVG 1.1 позволяет использовать градиенты для заливки фигур. Конечно, если мы используем эти фигуры для заполнения маски, мы можем использовать возможность указать альфа-уровень окончательного чертежа, изменив заливку маски, и создать удивительные эффекты. Snap предоставляет ярлыки для создания градиентов, которые впоследствии можно назначить свойству fill других элементов. Если мы немного изменим предыдущий код, например:

 var gradient = paper.gradient('r()#fff-#000'); mask.attr('fill', gradient); 

Если вы протестируете этот код, конечный эффект будет не таким, как вы ожидали. Это потому, что мы использовали относительный тип градиента излучения , выраженный строчной буквой «r» выше. Относительные градиенты создаются отдельно для каждого элемента группы (как составная маска). Если вы предпочитаете иметь один градиент для всей группы, вы можете использовать абсолютную версию команды. 'R()#fff-#000'абсолютный градиент излучения, начинающийся с белой заливки в центре и понижающийся до черного на границах.

Мы можем получить тот же результат, указав SVG-градиент для свойства fill любого элемента:

 mask.attr('fill', 'L(0, 0, 300, 300)#000-#f00:25-#fff'); 

В этом последнем примере мы показали более сложный градиент. Помимо другого типа ( абсолютно линейный ), этот градиент изменяется от (0, 0) до (300, 300), от черного до красного (25%, белый).

Метод gradient() принимает строку. Дальнейшие подробности объяснены в документации Snap.

Также возможно использовать существующие градиенты из любого элемента svg на странице:

 <svg id="svg-test"> <defs> <linearGradient id="MyGradient"> <stop offset="5%" stop-color="#F60" /> <stop offset="95%" stop-color="#FF6" /> </linearGradient> </defs> </svg> 
 paper.circle(50, 50, 50, 50).attr('fill', Snap('#svg-test').select('#MyGradient')); 

Узоры

Шаблоны позволяют заполнять фигуры путем повторения вхождений другой фигуры, градиента или изображения SVG. Snap предлагает метод Element.toPattern() (ранее — pattern() , теперь не рекомендуется), который создает шаблон из любого элемента Snap.

Создать шаблон и заполнить его элементом довольно просто:

 var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ fill: "none", stroke: "#bada55", strokeWidth: 5 }).toPattern(0, 0, 10, 10), c = paper.circle(200, 200, 100).attr({ fill: p }); 

Если вместо этого мы хотим объединить градиенты и шаблоны, это другая история, и немного более сложная!
В качестве примера давайте посмотрим, как создать маску, которая сочетает в себе лучистый градиент и рисунок, аналогичный приведенному выше:

 //assuming the shapes bigCircle and smallRect have already been defined, as well as 'paper' var mask = paper.g(bigCircle, smallRect), gradient = paper.gradient("R()#fff-#000"), pattern = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ fill: "none", stroke: "#bada55", strokeWidth: 5 }).toPattern(0, 0, 10, 10); mask.attr('fill', pattern); //we need to set this before calling clone! mask.attr({ mask: mask.clone() //makes a deep copy of current mask }); img.attr({ mask: mask }); 

Мы в основном должны создать двухуровневую карту. Последняя карта, используемая на нашем изображении, которую мы заполняем градиентом, сама карта заполняется градиентом. Результат впечатляет! Оказывается, это была также хорошая возможность представить вам метод clone() , который делает то, что вы себе представляете — создает глубокую копию элемента, для которого он вызывается.

Анимации

Анимации — одна из лучших функций Snap.svg. Есть несколько способов обработки анимации с немного отличным поведением.

Element.animate()

Мы начнем с самого простого метода анимации, Element.animate() . Этот метод позволяет пользователям анимировать любое количество свойств элемента, все синхронизировано. Начальное значение свойства — это, конечно, его текущее значение, а последнее указывается в первом аргументе animate() . Помимо свойств, которые нужно изменить, можно передать продолжительность анимации, ее простоту и обратный вызов, который будет вызван после завершения анимации.

Пример прояснит все:

 bigCircle.animate({r: 10}, 2000); 

Это просто сократит большой круг в нашей маске до меньшего радиуса в течение двух секунд.

Set.animate()

Вы можете анимировать элементы в группе (установить) самостоятельно. Но что, если вы хотите анимировать весь набор синхронно? Легко! Вы можете использовать Set.animate() . Это будет применять одинаковое преобразование ко всем элементам в наборе, обеспечивая синхронность между различными анимациями и повышая производительность, собирая все изменения воедино.

 mask.animate({'opacity': 0.1}, 1000); 

Вы также можете анимировать каждый элемент в наборе независимо, но синхронно. Set.animate() принимает переменное число аргументов, так что вы можете передать массив с аргументами для каждого подэлемента, который нужно анимировать:

 var set = mask.selectAll('circle'); //Create a set containing all the circle elements in mask's subtree (1 element) paper.selectAll('rect') //Select all the rect in the drawing surface (2 elements) .forEach(function(e) {set.push(e);}); //Add each of those rectangles to the set previously defined set.animate([{r: 10}, 500], [{x: 20}, 1500, mina.easein], [{x: 20}, 1500, mina.easein]); //Animate the three elements in the set 

Предполагая, что вы до сих пор правильно следовали нашему примеру кода (попробуйте его на CodePen ), запустив приведенный выше код в консоли вашего браузера, вы увидите, как эти три элемента анимируются синхронно, но независимо. Приведенный выше код был также возможностью представить наборы (как результаты методов select() и selectAll() ) и несколько полезных методов, определенных для них.

Другой способ создать набор — передать массив элементов в метод конструктора Snap:

 var set2 = Snap([bigCircle, smallRect]); 

Snap.animate()

Вы можете анимировать любое числовое свойство, но animate() не будет работать с другими типами, например, он испортит ваши text элементы, если вы попытаетесь анимировать их text атрибут. Тем не менее, есть другой способ получить такой эффект, т. Е. Третий способ вызова animate() в Snap.

Вызывая метод animate объекта Snap, можно более подробно указать действия, которые будут выполняться на каждом этапе анимации. Это помогает как группировать сложные анимации, так и запускать их синхронно (хотя метод Set.animate() будет правильным способом решения этой проблемы), а также анимировать сложные нечисловые свойства.

Например, давайте создадим и анимируем текстовый элемент:

 var labelEl = paper.text(300, 150, "TEST"), labels = ["TEST", "TETT","TEUT","TEVT","TEXT","TES-","TE--","T---", "----", "C---", "CH--", "CHE-", "CHEC-", "CHECK"]; Snap.animate(0, 13, function (val) { labelEl.attr({ text: labels[Math.floor(val)] }); }, 1000); 

Обработка событий

Возвращаясь к первоначальному сравнению масок и изображений, вы можете получить тот же эффект, который мы показали в предыдущем разделе, с анимированным GIF (вроде). Однако, если вы хотите воспроизвести это поведение в ответ на взаимодействие с пользователем, улучшение с использованием SVG еще более актуально. Вы все еще можете найти способ заставить его работать, используя несколько картинок, но, кроме потери гибкости, вы не сможете получить то же качество с минимальными усилиями:

 img.click(function(evt) { this.minified = !this.minified; bigCircle.animate({ r: !this.minified ? 100 : 10 }, 1500); }); 

Обработчики кликов могут быть впоследствии удалены с помощью метода Element.unclick() .

Среди других событий, которые можно обрабатывать аналогичным образом, есть dblclick , mousedown и mouseup , mousemove , mousemove и mouseover , а также ряд мобильных ориентированных событий, таких как touchstart и touchend .

Для тех наших читателей, которые раньше использовали интерфейсы jQuery или D3, в Snap нет метода on() для ручной обработки других событий. Если вам нужно пользовательское поведение, которое выходит за рамки обработчиков, предлагаемых Snap, вы можете извлечь свойство node для любого элемента, который, в свою очередь, содержит ссылку на связанный элемент DOM, и (возможно, после его обтекания в jQuery) вы можете добавить обработчики и свойства к нему напрямую:

 img.node.onclick = function () { img.attr("opacity", 0.1); }; 

Перетащить

Snap позволяет особенно легко активировать перетаскивание для любого элемента, группы или набора с помощью метода Element.drag() . Если вам не нужно настраивать поведение, вы можете вызвать его без аргументов:

 labelEl.drag(); //handle drag and drop for you 

Однако, если вам нужно какое-то особое поведение, вы можете передать пользовательские обратные вызовы и контексты для onmove , ondragstart , ondragend . Имейте в onmove что вы не можете пропустить onmove вызов onmove если хотите пропустить следующие.

Добавление обработчика перетаскивания не скроет событие click , которое будет ondragend после ondragend , если явно не предотвращено.

Загрузить существующий SVG

Одна из сильных сторон этой замечательной библиотеки — она ​​поддерживает повторное использование существующего кода SVG. Вы можете «внедрить» его в виде строки, или, что еще лучше, вы можете прочитать существующий файл, а затем изменить его.

Вы можете попробовать это сами. Скачайте и сохраните в корне вашего проекта этот красивый рисунок svg . Затем загрузите его на свою страницу, измените его стиль или структуру, как нам нравится, даже до добавления его в наше дерево DOM, добавления обработчиков событий и т. Д.

 Snap.load('ringing-phone.svg', function (phone) { // Note that we traverse and change attr before SVG is even added to the page (improving performance) phone.selectAll("path[fill='#ff0000']").attr({fill: "#00ff00"}); var g = phone.select("g"); paper.append(g); //Now we add the SVG element to the page }); 

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

Улучшения производительности

Одним из способов повышения производительности при манипулировании DOM является использование DocumentFragments . Фрагменты — это минимальные контейнеры для DOM-узлов. Представленные несколько лет назад, они позволяют вам недорого манипулировать целыми поддеревьями, а затем клонировать и добавлять целое поддерево с n узлами на нашу страницу с 2 вызовами методов вместо n . Фактическая разница подробно объясняется в блоге Джона Резига .

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

  1. Snap.parse(svg) принимает один аргумент, строку с кодом SVG, анализирует его и возвращает фрагмент, который впоследствии может быть добавлен к любой поверхности рисования.

  2. Snap.fragment(varargs) принимает переменное количество элементов или строк и создает один фрагмент, содержащий все предоставленные элементы.

Особенно для больших рисунков SVG фрагменты могут привести к огромной экономии производительности при правильном использовании.

Вывод

На этом мы завершаем нашу статью о расширенном Snap.svg. Теперь читатели должны иметь четкое представление о том, что они могут сделать с этой библиотекой и как это сделать. Если вы хотите узнать немного больше, лучше начать с документации Snap.

Пара полезных ссылок: