有许多方法可以为网站创建图标。 内联SVG具有可伸缩性,易于使用CSS进行修改,甚至可以进行动画处理。 如果您想了解更多有关使用内联SVG优点的信息,建议阅读内联SVG vs图标字体 。 随着浏览器支持的不断增加,再没有比现在更好的时间开始使用SVG了。 Snap Animation States是基于Snap.svg构建JavaScript插件,可帮助创建和扩展具有可缩放,可编辑SVG图标的图标库。 捕捉动画状态使您可以使用简单的架构轻松加载和设置这些SVG动画。
入门
让我们从基本的SVG汉堡菜单开始。 这是用 Affinity Designer ,但是还有许多其他免费的( Inkscape )和付费的( Adobe Illustrator )选项可用于制作矢量图像。
<svg width="100%" height="100%" viewBox="0 0 65 60" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:square;stroke-miterlimit:1.5;" fill="none" stroke="#000" stroke-width="10">
<g>
<path class="hamburger-top" d="m 5,10 55,0" />
<path class="hamburger-middle" d="m 5,30 55,0" />
</g>
<path class="hamburger-bottom" d="m 5,50 55,0" />
</svg>
尽管这是一个非常基本的SVG,但它仍然占据HTML文档中的多行。 如果要跨多个不同的网页在多个位置使用SVG,可能会很痛苦。 而且,如果您必须修改SVG,该怎么办? 然后,您要记住所有使用SVG的地方,以便您可以对其进行更新。 它不是很干净或可重复使用。 这就是“捕捉动画状态”所要解决的全部内容。
让我们继续使用相同的SVG,但是这次我们将使用插件将其加载到DOM。 插件的模式至少需要两个属性: selector:
"
.some-css-selector
"
和svg:
"
svg string
"
。 查看以下演示:
见笔Lydgoo由布里昂迪尔( @bkdiehl上) CodePen 。
您会在上方的笔中注意到,我将其称为icon-hamburger
,就像我将其称为字体图标一样。 只要记住, selector
属性需要一个CSS选择器作为其值。
由于此插件是Snap.svg的扩展, 因此我们可以做更多的事情, Snap.svg是一个JavaScript库,用于创建和设置SVG动画。 因此,让我们看看为这个汉堡包图标提供一些基本动画需要些什么。
当我创建SVG时,我向知道要设置动画的元素添加了类。
<g>
<path class="hamburger-top" d="m 5,10 55,0" />
<path class="hamburger-middle" d="m 5,30 55,0" />
</g>
<path class="hamburger-bottom" d="m 5,50 55,0" />
在我的模式中,我可以开始包含动画所需的属性,并从开始设置transitionTime: 250
。 过渡时间应用于变换链中的每个步骤,以后可以由单个变换覆盖。
现在是时候包括我的动画状态了。 我首先设置属性states:{}
。 该对象的属性名称应与动画将导致的状态相关。 在这种情况下,我将命名我的属性为open
和closed
。 该对象的属性值是变换对象的数组。 到目前为止,对架构的添加应如下所示:
transitionTime: 250,
states: {
open:[],
closed: []
}
接下来,我们需要包含定义SVG元素将如何转换的转换对象。
open:[
{ id: "top-lower", element: ".hamburger-top", y:20 },
{ id: "bottom-raise", element: ".hamburger-bottom", y:-20 },
{ waitFor: "top-lower", element: "g", r:45 },
{ waitFor: "bottom-raise", element: ".hamburger-bottom", r:-45},
]
每个转换对象都有一个id
,一个waitFor
或两者的组合。 每个id
必须是唯一的。 具有id
对象表示动画链中的链接。 waitFor
始终需要引用其前面的链接id
。 在这种情况下,有一个id:
"
top-lower
"
的对象和一个waitFor:
"
top-lower
"
。 动画开始时, id:top-lower
将是链中的第一个链接,它将持续250ms。 完成后, waitFor:
"
top-lower
"
将运行250ms。
每个变换对象都必须引用一个元素。 元素值可以是css选择器或直接元素引用。 例如,一个元素属性的值"
g
"
引用SVG中的<g>
元素,而另一个元素属性的值"
.hamburger-bottom
"
引用我添加到<path>
元素中的类。
现在我们知道了动画的顺序和需要变换的元素,我们只需要定义变换对象即可。 如果您不熟悉SVG转换的工作原理,则可以从SVG Elements上的转换开始。 否则,简单地说,假设您要操纵的SVG元素从x / y轴上的点[0,0]开始。 还需要记住的是,x从左到右,而y从上到下。 在上面的示例中,我们看到:
{ id: "top-lower", element: ".hamburger-top", y:20 },
此转换对象是指汉堡菜单的顶行。 y: 20
告诉插件,从元素的原点[0,0]开始,我想将顶行向下移动20px
。 相反的情况适用于:
{ id: "bottom-raise", element: ".hamburger-bottom", y:-20 },
在这里,我告诉插件将元素向上移动20px
。 相同的原则适用于轮换:
{ waitFor: "top-lower", element: "g", r:45 },
{ waitFor: "bottom-raise", element: ".hamburger-bottom", r:-45}
旋转的元素以0度的旋转开始。 r: 45
告诉插件将r: -45
从0度旋转到45度,反之亦然。
我们在状态对象中的第二个状态如下所示:
closed: [
{ id: "top-angle", element: "g", r: 0 },
{ id: "bottom-angle", element: ".hamburger-bottom", r: 0 },
{ waitFor: "top-angle", element: ".hamburger-top", y: 0 },
{ waitFor: "bottom-angle", element: ".hamburger-bottom", y: 0 }
]
您会注意到,对于所有要转换的元素,其y
和r
值均设置为0。这是因为此状态的目的是使SVG元素返回其原始状态。 由于0是原点,因此我们只是对每个元素执行转换,以将其返回原点。
我们快完成了。 现在已经定义了动画状态,我必须决定要启动这些动画的方式。 这需要架构上的另一个属性: events
。 events
需要一组对象,因为您可能希望通过多个事件来启动动画。 对于汉堡图标,它将如下所示:
events: [
{ event: "click", state: ["open", "closed"] }
]
数组中的对象可能包含以下属性:
-
event
:旨在监听javascript事件。 汉堡包图标侦听<i class="icon-hamburger"</i>
上的“点击”事件,因为这是架构中的选择器所引用的。 -
state
:采用字符串或数组。 如果此处的state
为"
open
"
,则单击<i class="icon-hamburger"></i>
,只有“ open”动画会在click事件上运行。 由于state
的值是一个数组,因此click事件实际上将在“打开”和“关闭”动画之间切换。 该数组仅设计为采用两个值并启用切换。 - 最后一个属性是可选的:
selector
。 默认情况下,此值为您的模式selector
+“动画”。 在这种情况下,它将是icon-hamburger-animate
。 您可以根据需要在此处进行声明来更改选择器。 目的是使javascript事件绑定到SVG的父元素或同级元素。 例如,如果我有一个要在单击按钮时在其内部设置动画的SVG,则需要执行以下操作:
<button class="icon-hamburger-animate">
<i class="icon-hamburger"></i>
</button>
哇,我们做到了。 现在是时候查看最终产品了。
见笔bWwQJZ由布里昂迪尔( @bkdiehl上) CodePen 。
它值得吗? 您可能在想,仅拥有一个图标需要进行很多工作。 我会同意你的。 这就是为什么我创建了一个Gulp插件来帮助您完成繁重工作的原因。
Gulp动画状态
到目前为止,我们只有一个图标,可以在包含模式的任何地方使用。 理想情况下,将icon-hamburger
的架构保存到一个js文件中,该文件会捆绑在一起并包含在整个网站范围内,这意味着我可以在任何需要的地方调用icon-hamburger
。 如果此js文件是自动生成的,并且包含您有权访问的SVG图标的架构和插件调用,该怎么办? 您可以轻松访问SVG图标库! 这就是Gulp动画国家的目的。 确保在此处查看文档。
让我们从文件结构开始。 假设我去了IcoMoon,并生成了新项目所需的所有SVG文件。 我想将所有这些新生成的文件拖放到项目中的文件夹中。 我们将该文件夹称为“ svg”。 我的文件结构如下所示:
svg
|-- icon-folder.svg
|-- icon-hamburger.svg
|-- icon-mic.svg
|-- icon-wall.svg
|-- icon-wrench.svg
使用Gulp动画状态,我可以将svg文件夹中的所有SVG文件合并为一个js文件,并根据SVG的文件名为每个图标设置selector
。 文件内容如下所示:
var iconFolder = {"selector": ".icon-folder","svg": "<svg>Content</svg>"};
SnapStates(iconFolder);
var iconHamburger= {"selector": ".icon-hamburger","svg": "<svg>Content</svg>"};
SnapStates(iconHamburger);
var iconMic= {"selector": ".icon-mic","svg": "<svg>Content</svg>"};
SnapStates(iconMic);
var iconWall= {"selector": ".icon-wall","svg": "<svg>Content</svg>"};
SnapStates(iconWall);
var iconWrench= {"selector": ".icon-wrench","svg": "<svg>Content</svg>"};
SnapStates(iconWrench);
该文件可以与网站其他关键JavaScript捆绑在一起,从而在需要的地方使用SVG图标。 但是动画呢? 它们如何包含在此JavaScript文件中?
我们已经有汉堡图标的动画,因此我们将使用它。 在“ svg”文件夹中,我们需要创建一个名为“ icon-hamburger.js”的新文件。 请注意,其名称与对应的SVG文件相同。 这是新的文件结构:
svg
|-- icon-folder.svg
|-- icon-hamburger.svg
|-- icon-hamburger.js
|-- icon-mic.svg
|-- icon-wall.svg
|-- icon-wrench.svg
并且icon-hamburger.js的内容将是:
{
transitionTime: 250,
states: {
open:[
{ id: "top-lower", element: ".hamburger-top", y:20 },
{ id: "bottom-raise", element: ".hamburger-bottom", y:-20 },
{ waitFor: "top-lower", element: "g", r:45 },
{ waitFor: "top-lower", element: ".hamburger-bottom", r:-45},
],
closed: [
{ id: "top-angle", element: "g", r: 0 },
{ id: "bottom-angle", element: ".hamburger-bottom", r: 0 },
{ waitFor: "top-angle", element: ".hamburger-top", y: 0 },
{ waitFor: "bottom-angle", element: ".hamburger-bottom", y: 0 },
]
},
events: [
{ event: "click", state: ["open", "closed"] }
]
}
Gulp插件将查找与为其创建架构的SVG文件同名的js文件。 再次以动画状态演示输出:
var iconFolder = {"selector": ".icon-folder","svg": "<svg>Content</svg>"};
SnapStates(iconFolder);
var iconHamburger= {"selector": ".icon-hamburger","svg": "<svg>Content</svg>", "transitionTime":250,"states":{"open":[{"id":"top-lower","element":".hamburger-top","y":20},{"id":"bottom-raise","element":".hamburger-bottom","y":-20},{"waitFor":"top-lower","element":"g","r":45},{"waitFor":"top-lower","element":".hamburger-bottom","r":-45}],"closed":[{"id":"top-angle","element":"g","r":0},{"id":"bottom-angle","element":".hamburger-bottom","r":0},{"waitFor":"top-angle","element":".hamburger-top","y":0},{"waitFor":"bottom-angle","element":".hamburger-bottom","y":0}]},"events":[{"event":"click","state":["open","closed"]}};
SnapStates(iconHamburger);
var iconMic= {"selector": ".icon-mic","svg": "<svg>Content</svg>"};
SnapStates(iconMic);
var iconWall= {"selector": ".icon-wall","svg": "<svg>Content</svg>"};
SnapStates(iconWall);
var iconWrench= {"selector": ".icon-wrench","svg": "<svg>Content</svg>"};
SnapStates(iconWrench);
使用Gulp动画状态,您可以保留较小的,一口大小的文件,可以在需要更改某些内容时轻松地对其进行编辑。 这些小巧的片段可以很好地编译为一个文件,该文件可以与站点的其他关键组件捆绑在一起,从而允许快速轻松地调用以在HTML文档中包含SVG。
进一步的例子
汉堡包图标非常简单,因此让我们看一些更复杂的图标。 我们将从扬声器图标开始。
在CodePen上查看Briant Diehl( @bkdiehl )的Pen WjoOoy 。
您会注意到,总体而言,该架构几乎是相同的。 您会注意到房地产easing
是新的。 easing
具有easeinout
的默认值。 除此之外,唯一值得注意的更改是在我的转换对象中。
{ id: "waveline1", element: ".wave-line-1", x:-10, s:0.1, attr:{ opacity:.8 }, transitionTime: 250 },
{ id: "waveline2", element: ".wave-line-2", x:-16, s:0.1, attr:{ opacity:.8 }, transitionTime: 300 },
{ id: "waveline3", element: ".wave-line-3", x:-22, s:0.1, attr:{ opacity:.8 }, transitionTime: 350 }
s
用于缩放,就像在CSS中一样,对象的缩放始终始于attr
属性允许您修改SVG元素上的任何属性,在这种情况下为不透明度。 最后,还记得本文开头提到的如何用单个转换覆盖transitionTime
方法吗? 好吧,这是完成的过程。 我什至没有在主模式中声明transitionTime
。 那是因为我希望每个变换都具有唯一的过渡时间。
接下来,让我们看一下线条画动画。
见笔OmbxVV由布里昂迪尔( @bkdiehl上) CodePen 。
我希望您看到的第一个主要区别是,我没有在架构中声明svg
。 SVG位于<i class="icon-new-document"></i>
。 这主要是出于演示目的,以免夸大我希望您查看的架构。 但是,插件确实允许此功能。 该用例适用于那些仅在文档中只需要几个SVG图标且不希望使用gulp插件的用户。
转换对象是我真正要重点关注的对象。 这里有很多新东西。
{ id: 'line1-init', element: ".new-document-line1", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line2-init', element: ".new-document-line2", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line3-init', element: ".new-document-line3", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line4-init', element: ".new-document-line4", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ id: 'line5-init', element: ".new-document-line5", drawPath: { min: 25, max: 75 }, transitionTime: { min: 500, max: 1000 }, repeat: {times:1} },
{ waitFor: 'line1-init', element: ".new-document-line1", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line2-init', element: ".new-document-line2", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line3-init', element: ".new-document-line3", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line4-init', element: ".new-document-line4", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
{ waitFor: 'line5-init', element: ".new-document-line5", drawPath: 100, transitionTime: { min: 500, max: 1000 } },
如果您看过钢笔,您会注意到,将鼠标悬停在新文档图标上会导致线条缩小和增长。 每条线都是一个path
,可以绘制一条path
。 上面的第一个转换对象包括drawPath
。 drawpath
可以使用数字或具有min
和max
属性的对象。 该数字代表百分比。 假设转换对象具有drawPath: 0
。 这意味着我希望将当前path
绘制为其长度的0%。 转换对象确实具有drawPath: { min: 25, max: 75 }
。 当drawpath
的值是一个对象时,我告诉插件我希望将路径绘制为min
和max
之间的随机百分比。 在这种情况下,它将是25到75之间的随机数。如果再次将鼠标悬停在图标上,则可以看到每次动画发生时,行长都会发生变化。 使用min
和max
设置随机数的相同原理适用于transitionTime
。
repeat
此动画模式的最后一个新手。 repeat
接受具有四个有效属性的对象。
-
loop
:采用布尔值。 如果设置为true,则动画和整个链的所有变换将重复执行,直到另行通知为止。 为了打破循环,您必须设置loopDuration
或更改为另一个动画状态。 -
loopDuration
:取一个整数。 如果将loop
设置为true并将loopDuration
为5000,则动画链将重复自身5000ms。 如果动画循环的持续时间不完全是5000ms,则循环将在设置的时间后继续其最终动画。 -
times
:取一个整数。 如果将times
设置为2,则动画将总共运行3次。 因为动画总是至少运行一次然后再运行2次,所以选择一次。 -
delay
:取整数。 表示从动画结束到重复循环开始之间所需的时间。
接下来,我想说明更长的动画链。
见笔KmNXdW由布里昂迪尔( @bkdiehl上) CodePen 。
看一下shake
状态。 第一个和最后一个转换对象具有id
或waitFor
属性。 每个其他转换对象都具有id
和waitFor
属性。
shake: [
{ id: "shake-right", element: '.wrench', r: 10 },
{ id: "shake-left", waitFor: 'shake-right', element: '.wrench', r: -10 },
{ id: "back-to-right", waitFor: 'shake-left', element: '.wrench', r: 10 },
{ id: "back-to-left", waitFor: 'back-to-right', element: '.wrench', r: -10 },
{ waitFor: 'back-to-left', element: '.wrench', r: 0 }
]
三个中间变换对象中的每一个都使用其waitFor
引用前一个变换对象的id
。 第一个动画开始一个链,该链最终导致复位值r:0
。
最后,我想演示如何通过设置stroke-dashoffset
和stroke-dasharray
来画线。
见笔rmWzyW由布里昂迪尔( @bkdiehl上) CodePen 。
首先,我希望您注意到在我的许多path
元素上都包含stroke-dashoffset:1000; stroke-dasharray:1000, 1000;
stroke-dashoffset:1000; stroke-dasharray:1000, 1000;
<path class="right-upper-branch" d="M45.998,21.196C43.207,23.292 44.195,27.857 47.629,28.59C48.006,28.671 48.399,28.699 48.784,28.672C49.659,28.611 50.276,28.34 50.994,27.849C51.413,27.563 51.839,27.05 52.092,26.616C53.906,23.507 50.981,19.611 47.489,20.486C46.946,20.622 46.446,20.86 45.998,21.196L41.015,14.571" style="fill:none;stroke:#fff;stroke-width:1.7px;stroke-dashoffset:1000; stroke-dasharray:1000, 1000;"/>
有关stroke-dasharray
详细说明,请查看stroke-dasharray 。 就我的目的而言,我们说一下stroke-dasharray
基本上是设置我不想显示的路径的长度。 现在,我的路径肯定不是1000px长。 有点夸张,但这是夸张,可以确保我的路径的任何部分都不会过早显示。 在下面的转换中,路径绘制完成。
{ id:["right-upper-branch", 600], element: ".right-upper-branch", drawPath:100 },
当我设置drawPath
回0将调整stroke-dasharray
和stroke-dashoffset
相应。 关于这一行,我想指出的最后一件事是id
。 它是一个数组而不是字符串。 数组中的第一个值是id
的名称。 第二个值将始终是代表超时的整数。 如果仅在动画中使用此变换对象,则将仅在mouseover
事件之后600ms看到一条绘制的路径。
有关更多示例和更多文档,您可以查看我的演示页面 。
结论
关于将图标系统切换到新的东西是否是一件好事,可能还有很多人仍在讨论中。 当前可用的不同图标系统各有利弊。 我试图为您创建一种简单的方法,使您可以转到SVG图标。 希望对你有帮助。