为网站制作图标有很多方法。内联 SVG 可缩放,易于使用 CSS 修改,甚至可以进行动画处理。如果您有兴趣详细了解使用内联 SVG 的优点,我建议您阅读 内联 SVG 与图标字体。随着浏览器支持的不断增加,现在是开始使用 SVG 的最佳时机。Snap 动画状态是一个围绕 Snap.svg 构建的 JavaScript 插件,旨在帮助使用可缩放、可编辑的 SVG 图标创建和扩展图标库。Snap 动画状态使您可以轻松地使用简单的模式加载和动画化这些 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 的地方,以便对其进行更新。这很不干净也不可重用。这就是 Snap 动画状态要解决的问题。
让我们继续使用相同的 SVG,但这次我们将使用该插件将其加载到 DOM 中。该插件的模式至少需要两个属性:selector:
"
.some-css-selector
"
和 svg:
"
svg 字符串
"
。查看以下演示
查看 CodePen 上 Briant Diehl (@bkdiehl) 的 Lydgoo。
您会在上面的 Pen 中注意到,我像调用字体图标一样调用 icon-hamburger
。请记住,selector
属性需要一个 CSS 选择器作为其值。
由于该插件是 Snap.svg 的扩展,因此我们可以做更多事情,这是一个用于创建和动画化 SVG 的 JavaScript 库。因此,让我们看看需要什么才能为这个汉堡图标添加一些基本动画。
在创建 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
将是链中的第一个链接,它将运行 250 毫秒。当它完成时,waitFor:
"
top-lower
"
将运行 250 毫秒。
每个转换对象都必须引用一个元素。元素值可以是 CSS 选择器或直接元素引用。例如,一个元素属性的值为 "
g
"
,引用 SVG 中的 <g>
元素,而另一个属性的值为 "
.hamburger-bottom
"
,引用我添加到 <path>
元素中的类。
现在我们知道了动画顺序和需要转换的元素,我们只需要定义转换对象。对于那些不熟悉 SVG 转换工作原理的人,您可以从 SVG 元素上的转换 开始。否则,简单地说,想象一下您正在操作的 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
告诉插件从 0 度旋转到 45 度,反之亦然,对于 r: -45
也是如此。
我们 states 对象中的第二个状态如下所示
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>
上的 “click” 事件,因为这就是模式中 selector 引用的内容。state
:接受一个字符串或一个数组。如果此处的state
是"
open
"
,那么当单击<i class="icon-hamburger"></i>
时,只会在单击事件上运行 “open” 动画。由于state
的值为一个数组,因此单击事件实际上将在 “open” 和 “closed” 动画之间切换。该数组仅设计为接受两个值并启用切换。- 最后一个属性是可选的:
selector
。默认情况下,此值是您的模式selector
+ “animate”。在本例中,它将是icon-hamburger-animate
。如果需要,您可以更改选择器。它的目的是使 javascript 事件能够绑定到 SVG 的父级元素或兄弟元素。例如,如果我有一个我希望在按钮被单击时在按钮内部进行动画处理的 SVG,那么我需要这样做
<button class="icon-hamburger-animate">
<i class="icon-hamburger"></i>
</button>
哇,我们做到了。现在是查看最终产品的时候了。
查看 CodePen 上 Briant Diehl (@bkdiehl) 的 bWwQJZ。
值得吗?您可能在想,仅仅为了一个图标就需要做这么多工作。我同意您的观点。这就是我创建 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 动画状态,您可以保留更小、更易于管理的文件,以便在需要更改内容时轻松编辑。这些小块文件很好地编译成一个文件,该文件可以与站点的其他关键组件捆绑在一起,从而允许快速轻松地调用以将 SVG 包含在您的 HTML 文档中。
更多示例
汉堡菜单图标非常简单,所以让我们看看一些更复杂的图标。 我们将从一个扬声器图标开始。
查看 Pen WjoOoy by Briant Diehl (@bkdiehl) on CodePen.
您会注意到,总体而言,模式基本相同。 您会注意到属性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中一样,对象的缩放始终从1开始。 attr
属性允许您修改SVG元素上的任何属性,在本例中是透明度。 最后,请记住,在本文开头,我提到过transitionTime
可以被单个变换覆盖? 嗯,这就是它的实现方式。 我甚至没有在主模式中声明transitionTime
。 这是因为我希望每个变换都有一个唯一的过渡时间。
接下来,让我们看一下线条绘制动画。
查看 Pen OmbxVV by Briant Diehl (@bkdiehl) on 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 } },
如果您查看了Pen,您会注意到,将鼠标悬停在新文档图标上会导致线条收缩和增长。 每条线都是一条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,那么动画链将重复自身5000毫秒。 如果动画循环的持续时间不完全是5000毫秒,那么循环将继续其最终动画,超过设定的时间。times
:接受一个整数。 如果我将times
设置为2,那么我的动画将总共运行3次。 一次是因为动画总是至少运行一次,然后又运行2次。delay
:接受一个整数。 代表您希望动画结束和重复循环开始之间的等待时间。
接下来,我想说明一个更长的动画链。
查看 Pen KmNXdW by Briant Diehl (@bkdiehl) on 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
来绘制线条。
查看 Pen rmWzyW by Briant Diehl (@bkdiehl) on CodePen.
首先,我想让您注意到,在我的许多path
元素上,我包含了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
事件发生后600毫秒才绘制的路径。
有关更多示例和进一步的文档,您可以查看我的演示页面。
结论
你们中可能还有很多人仍在犹豫,是否将图标系统切换到新的系统是一个好主意。 目前可用的不同图标系统各有优缺点。 我试图为您创建一个简单的方法,让您迁移到SVG图标。 希望您觉得它有用。
我什么动画也看不到 ;_;
Chrome 58 64位