Skip to content

Commit 4ff3e2d

Browse files
sdraschrisvfritz
authored andcommitted
Cookbook Example: Editable SVG Icon System (vuejs#1361)
* first part of editable icons recipe * write up the rest of the example * another round of content * update post based on chris' edits- mostly readability
1 parent 8912ec6 commit 4ff3e2d

File tree

4 files changed

+178
-9
lines changed

4 files changed

+178
-9
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
"hexo-server": "^0.2.2",
2626
"request": "^2.83.0"
2727
}
28-
}
28+
}

src/v2/cookbook/cookbook-contributions.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
---
22
title: Cookbook Contributions
33
type: cookbook
4-
order: 1000
4+
order: 1
55
---
66

77
## What we're looking for
88

9-
The Cookbook gives developers examples to work off of that both cover common or interesting use cases, and also progressively explain more complex detail. Our goal is to move beyond a simple introductory example, and demonstrate concepts that are more widely applicable, as well as some caveats to the approach.
9+
The Cookbook gives developers examples to work off of that both cover common or interesting use cases, and also progressively explain more complex detail. Our goal is to move beyond a simple introductory example, and demonstrate concepts that are more widely applicable, as well as some caveats to the approach.
1010

1111
If you're interested in contributing, please initiate collaboration by filing an issue under the tag **cookbook idea** with your concept so that we can help guide you to a successful pull request. After your idea has been approved, please follow the template below as much as possible. Some sections are required, and some are optional. Following the numerical order is strongly suggested, but not required.
1212

@@ -33,6 +33,7 @@ _required_
3333
_required_
3434

3535
Demonstrate the code that would power a common or interesting use case, either by:
36+
3637
1. Walking through a few terse examples of setup, or
3738
2. Embedding a codepen/jsfiddle example
3839

@@ -58,4 +59,4 @@ This section is required when you've provided the section above about avoidance.
5859

5960
## Thank you
6061

61-
It takes time to contribute to documentation, and if you spend the time to submit a PR this section of our docs, you do so with our gratitude.
62+
It takes time to contribute to documentation, and if you spend the time to submit a PR this section of our docs, you do so with our gratitude.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: Editable SVG Icon Systems
3+
type: cookbook
4+
order: 2
5+
---
6+
7+
## Base Example
8+
9+
There are many ways to create an SVG Icon System, but one method that takes advantage of Vue's capabilities is to create editable inline icons as components. Some of the advantages of this way of working is:
10+
11+
* They are easy to edit on the fly
12+
* They are animatable
13+
* You can use standard props and defaults to keep them to a typical size or alter them if you need to
14+
* They are inline, so no HTTP requests are necessary
15+
* They can be made accessible dynamically
16+
17+
First, we'll create a folder for all of the icons, and name them in a standardized fashion for easy retrieval:
18+
19+
> components/icons/IconBox.vue
20+
> components/icons/IconCalendar.vue
21+
> components/icons/IconEnvelope.vue
22+
23+
Here's an example repo to get you going, where you can see the entire setup: [https://github.com/sdras/vue-sample-svg-icons/](https://github.com/sdras/vue-sample-svg-icons/)
24+
25+
![Documentation site](https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/screendocs.jpg 'Docs demo')
26+
27+
We'll create a base icon (`IconBase.vue`) component that uses a scoped slot.
28+
29+
```html
30+
<template>
31+
<svg xmlns="http://www.w3.org/2000/svg"
32+
:width="width"
33+
:height="height"
34+
viewBox="0 0 18 18"
35+
:aria-labelledby="iconName"
36+
role="presentation"
37+
>
38+
<title :id="iconName" lang="en">{{iconName}} icon</title>
39+
<g :fill="iconColor">
40+
<slot />
41+
</g>
42+
</svg>
43+
</template>
44+
```
45+
46+
You can use this base icon as is- the only thing you might need to update is the `viewBox` depending on the `viewBox` of your icons. In the base, we're making the `width`, `height`, `color`, and name of the icon props so that it can be dynamically updated with props. The name will be used for both the title `id` for accessibility, and the `title`.
47+
48+
Our script will look like this, we'll have some defaults so that our icon will be rendered consistently unless we state otherwise:
49+
50+
```js
51+
export default {
52+
props: {
53+
iconName: {
54+
type: String,
55+
default: 'box'
56+
},
57+
width: {
58+
type: [Number, String],
59+
default: 18
60+
},
61+
height: {
62+
type: [Number, String],
63+
default: 18
64+
},
65+
iconColor: {
66+
type: String,
67+
default: 'currentColor'
68+
}
69+
}
70+
}
71+
```
72+
73+
The currentColor property that's the default on the fill will make the icon inherit the color of whatever text surrounds it. We could also pass in a different color as a prop if we wish.
74+
75+
We can use it like so, with the only contents of IconWrite.vue containing the paths inside the icon:
76+
77+
```html
78+
<icon-base icon-name="write"><icon-write /></icon-base>
79+
```
80+
81+
Now, if we'd like to make many sizes for the icon, we can do so very easily:
82+
83+
```html
84+
<p>
85+
<!--you can pass in a smaller width and height as props-->
86+
<icon-base width="12" height="12" icon-name="write"><icon-write /></icon-base>
87+
<!--or you can use the default, which is 18-->
88+
<icon-base icon-name="write"><icon-write /></icon-base>
89+
<!--or make it a little bigger too :) -->
90+
<icon-base width="30" height="30" icon-name="write"><icon-write /></icon-base>
91+
</p>
92+
```
93+
94+
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/Screen%20Shot%202018-01-01%20at%204.51.40%20PM.png" width="450" />
95+
96+
## Animatable Icons
97+
98+
Keeping icons in components comes in very handy when you'd like to animate them, especially on an interaction. Inline SVGs have the highest support for interaction of any method. Here's a very basic example of an icon that's animated on click:
99+
100+
```html
101+
<template>
102+
<svg @click="startScissors"
103+
xmlns="http://www.w3.org/2000/svg"
104+
viewBox="0 0 100 100"
105+
width="100"
106+
height="100"
107+
aria-labelledby="scissors"
108+
role="presentation"
109+
>
110+
<title id="scissors" lang="en">Scissors Animated Icon</title>
111+
<path id="bk" fill="#fff" d="M0 0h100v100H0z"/>
112+
<g ref="leftscissor">
113+
<path d="M..."/>
114+
...
115+
</g>
116+
<g ref="rightscissor">
117+
<path d="M..."/>
118+
...
119+
</g>
120+
</svg>
121+
</template>
122+
```
123+
124+
```js
125+
import { TweenMax, Sine } from 'gsap'
126+
127+
export default {
128+
methods: {
129+
startScissors() {
130+
this.scissorAnim(this.$refs.rightscissor, 30)
131+
this.scissorAnim(this.$refs.leftscissor, -30)
132+
},
133+
scissorAnim(el, rot) {
134+
TweenMax.to(el, 0.25, {
135+
rotation: rot,
136+
repeat: 3,
137+
yoyo: true,
138+
svgOrigin: '50 45',
139+
ease: Sine.easeInOut
140+
})
141+
}
142+
}
143+
}
144+
```
145+
146+
We're applying `refs` to the groups of paths we need to move, and as both sides of the scissors have to move in tandem, we'll create a funciton we can reuse where we'll pass in the `refs`. The use of GreenSock helps resolve animation support and transform-origin issues across browser.
147+
148+
<p data-height="300" data-theme-id="0" data-slug-hash="dJRpgY" data-default-tab="result" data-user="Vue" data-embed-version="2" data-pen-title="Editable SVG Icon System: Animated icon" class="codepen">See the Pen <a href="https://codepen.io/team/Vue/pen/dJRpgY/">Editable SVG Icon System: Animated icon</a> by Vue (<a href="https://codepen.io/Vue">@Vue</a>) on <a href="https://codepen.io">CodePen</a>.</p><script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
149+
150+
<p style="margin-top:-30px">Pretty easily accomplished! And easy to update on the fly.</p>
151+
152+
You can see more animated examples in the repo [here](https://github.com/sdras/vue-sample-svg-icons/)
153+
154+
## Additional Notes
155+
156+
Designers may change their minds. Product requirements change. Keeping the logic for the entire icon system in one base component means you can quickly update all of your icons and have it propogate through the whole system. Even with the use of an icon loader, some situations require you to recreate or edit every SVG to make global changes. This method can save you that time and pain.
157+
158+
## When To Avoid This Pattern
159+
160+
This type of SVG icon system is really useful when you have a number of icons that are used in different ways throughout your site. If you're repeating the same icon many times on one page (e.g. a giant table a delete icon in each row), it might make more sense to have all of the sprites compiled into a sprite sheet and use `use` tags to load them.
161+
162+
## Alternative Patterns
163+
164+
Other tooling to help manage SVG icons includes:
165+
166+
* [svg-sprite-loader](https://github.com/kisenka/svg-sprite-loader)
167+
* [svgo-loader](https://github.com/rpominov/svgo-loader)
168+
169+
These tools bundle SVGs at compile time, but make them a little harder to edit during runtime, because `use` tags can have strange cross-browser issues when doing anything more complex. They also leave you with two nested `viewBox` properties and thus two coordinate systems. This makes the implementation a little more complex.

src/v2/cookbook/index.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ order: 0
1212

1313
How is the cookbook different from the guide? Why is this necessary?
1414

15-
- __Greater Focus__: In the guide, we're essentially telling a story. Each section builds on and assumes knowledge from each previous section. In the cookbook, each recipe can and should stand on its own. This means recipes can focus on one specific aspect of Vue, rather than having to give a general overview.
15+
* **Greater Focus**: In the guide, we're essentially telling a story. Each section builds on and assumes knowledge from each previous section. In the cookbook, each recipe can and should stand on its own. This means recipes can focus on one specific aspect of Vue, rather than having to give a general overview.
1616

17-
- __Greater Depth__: To avoid making the guide too long, we try to include only the simplest possible examples to help you understand each feature. Then we move on. In the cookbook, we can include more complex examples, combining features in interesting ways. Each recipe can also be as long and detailed as it needs to be, in order to fully explore its niche.
17+
* **Greater Depth**: To avoid making the guide too long, we try to include only the simplest possible examples to help you understand each feature. Then we move on. In the cookbook, we can include more complex examples, combining features in interesting ways. Each recipe can also be as long and detailed as it needs to be, in order to fully explore its niche.
1818

19-
- __Teaching JavaScript__: In the guide, we assume at least intermediate familiarity with ES5 JavaScript. For example, we won't explain how `Array.prototype.filter` works in a computed property that filters a list. In the cookbook however, essential JavaScript features (including ES6/2015+) can be explored and explained in the context of how they help us build better Vue applications.
19+
* **Teaching JavaScript**: In the guide, we assume at least intermediate familiarity with ES5 JavaScript. For example, we won't explain how `Array.prototype.filter` works in a computed property that filters a list. In the cookbook however, essential JavaScript features (including ES6/2015+) can be explored and explained in the context of how they help us build better Vue applications.
2020

21-
- __Exploring the Ecosystem__: For advanced features, we assume some ecosystem knowledge. For example, if you want to use single-file components in Webpack, we don't explain how to configure the non-Vue parts of the Webpack config. In the cookbook, we have the space to explore these ecosystem libraries in more depth - at least to the extent that is universally useful for Vue developers.
21+
* **Exploring the Ecosystem**: For advanced features, we assume some ecosystem knowledge. For example, if you want to use single-file components in Webpack, we don't explain how to configure the non-Vue parts of the Webpack config. In the cookbook, we have the space to explore these ecosystem libraries in more depth - at least to the extent that is universally useful for Vue developers.
2222

2323
## Guidelines for Recipes
2424

@@ -39,4 +39,3 @@ Recipes should generally:
3939
> 7. Explain the pros and cons of your strategy, including when it is and isn't appropriate
4040
4141
> 8. Mention alternative solutions, if relevant, but leave in-depth explorations to a separate recipe
42-

0 commit comments

Comments
 (0)