C.S Weekly 1: Envelope SVG
My first hand crafted SVG, and how you can make one too
This is my first CS Weekly project. Read Code Something Weekly: How and Why for the inspiration and ideas behind the effort.
This week, I built from scratch an SVG envelope with animations. The deployed project is here and all of the code is on Github.
The Idea
The idea is simple: create a material inspired envelope that can open and close.
I’m not entirely sure where this idea came from. For starters, I’ve used SVGs or exported them from Adobe PS and Inkscape, but I’ve never coded one.
And that just won’t do.
Furthermore, I’ve noticed GMail has some really nifty animations, including the new loading/splash screen. That envelope quite literally pops out right at you!
So perhaps I didn’t exactly have free will in this decision (I think Derren Brown and Sam Harris would agree).
The Prototype
Most ideas should start with a playground. Avoid the dreaded wait times of spinning up a new project filled with configurations and boilerplate, it will only get in the way.
I used CodePen to prototype both the paper and envelope. You can check them out and tinker. These do not have the Vue application bloat or anything fancy — 100% es6/babel JS:
Tutorial — Paper Icon
Let’s run through the simple Paper icon (available on codepen).
First, let’s get an idea of what an SVG is and why we use one. As the name implies, an SVG is a scalable vector graphic. But what’s that?
- JPEG, PNG and related formats are raster graphics: a matrix of pixels. The data in the file defines how to paint those pixels on the screen.
- By contrast, vector graphics define a coordinate system and relative paths.
The key difference is that scaling raster graphics leads to distortion because of pixel interpolation. Think about it this way: if you have a file that defines exactly how each pixel of a 500x500 grid looks like, then suddenly add or remove pixels or densities — how does the image cope?
Vector graphics define a logic that can render at any scale. SVG is just a standardized vector graphic protocol for the web.
<SVG>
We start by adding a simple <svg>
tag to the HTML markup.
<svg
class="svg-paper"
id="paper"
preserveAspectRatio="xMidYMid meet"
viewbox="0 0 400 400"
width="100%"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
</svg>
- I set a
class
andid
in order to be able to resize etc. via CSS. viewbox
is the relative grid. Think of it like a telescope. It takes 4 arguments:min-x
,min-y
,width
,height
. Think of the first two arguments as the telescope pan and the latter two as the zoom. Thus0 0 400 400
states: “make a 400x400 grid and set top left coordinate to (0, 0)”.preserveAspectRatio
defines how to scale the svg, and only does something if theviewbox
is defined. The valuexMidYMid meet
is the default behaviour. To see various ways of scaling an SVG, checkout out the MDN docs.width="100%"
— denotes that the width of the paper will take up the entire width of its container (in the case of codepen, thebody
). Note how this is independent of theviewbox
. Theviewbox
just sets a coordinate system!- The
xml
attributes are references to the w3 svg namespace.
A Simple Shape: <rect>
Now that we have the SVG boiler out of the way let’s make our first shape. The following is the paper background (the white sheet).
<rect
class="svg-paper__sheet"
fill="#FFF"
height="100%"
width="100%"
x="0"
y="0"/>
- I set a
class
in order to use CSS should I wish to fill="#FFF"
gives it white colorheight
andwidth
are set to100%
: the sheet should fill the entire SVGviewbox
.x
andy
denote where to place the shape: the upper left corner.
Grouping and Render Order
Like <div>
s, group tags <g>
are a container for children. If we want to apply a CSS class or an animation to multiple components (for example, multiple <rect>
), we should group them.
Before moving further, let’s add a group to our shape so far:
<svg ...>
<g class="svg-paper__group">
<rect ... />
</g>
</svg>
The class svg-paper__group
can now be used to access all of its children — ie every shape in the SVG.
When we place multiple items in a SVG, we have to be cognizant of render order. As we work down in the code, we’re working up (towards us) in terms of layering. Thus if we place a <rect>
underneath the existing one, it will appear on top.
Adding Lines
Let’s add a new group for the lines, as there will be multiple and they’re all related:
<svg ...>
<g class="svg-paper__group">
<rect .../>
<g class="svg-paper__lines">
</g>
</g>
</svg>
In that new group, let’s add a first line:
<rect
class="svg-paper__line"
fill="#BBDEFB"
height="1%"
width="90%"
x="5%"
y="20%"/>
class="svg-paper__line"
gives each line a class so that we can define any CSS properties- I gave mine a blue
fill
, but feel free to change it up. Here’s a color picker tool. height="1%"
— this was a guess, but paper lines are fairly short relative to the entire height of the page. I liked how it came out. Notice that again, this is1%
so that if we change the height of the overall SVG, the lines will adjust accordingly to be 1% of the overall height.- I set
width="90%"
because lines don’t usually span the entire page width, there’s usually a margin x="5%"
positions the x-coordinate of the start of the line as 5% of the page width. Working on a 400x400 grid, that means starting out on 20.- Similarly,
y="20%"
positions the line 20% of the page height down the page. Thus, on a 400x400 grid, we’re starting on (20, 80).
At this point, adding more lines is as simple as copy-pasting and changing the y
attribute. I set mine to 20%, 30%, 40%, 50%, 60%, 70%, 80%
, though feel free to experiment.
This completes a simple paper icon — it really wasn’t too bad!
Animations
Let’s examine a simple animation using the concepts in the paper icon above. For a discussion on the more advanced animations seen in the envelope, read the documentation in the Github repository.
Add a new css
property .red
.red {
fill: #f45c42;
}
We’re going to listen to mouseover
and mouseout
events on the <group>
of lines, and toggle their colour (see, grouping is useful).
const lineGroup = document
.getElementsByClassName('svg-paper__lines')[0]
This is just vanilla JavaScript: query the document for the svg-paper__lines
class, and take the first result of the HTML collection, [0]
There should only be one anyways.
Next, setup an event listener for mouseover
and mouseout
:
lineGroup.addEventListener('mouseover', () => {
// do something on mouseover
})lineGroup.addEventListener('mouseout', () => {
// do something on mouseout
})
Currently we’re detecting events when we hover over any one given line in the Paper icon (but not between the lines) with barely any lines of code. Neat.
In order to change the class of the lines themselves, let’s get a reference to a collection of each line:
// this is an array-like HTML collection [<rect>, <rect>]
const lines = document.getElementsByClassName('svg-paper__line')
Finally, add the class red
in the mouseover
event and remove it in the mouseout
event:
lineGroup.addEventListener('mouseover', () => {
[...lines].forEach(line => {
line.classList.add('red')
})
})lineGroup.addEventListener('mouseout', () => {
[...lines].forEach(line => {
line.classList.remove('red')
})
})
Note: The spread syntax
[...lines]
is essentially converting the lines HTML collection into an Array, so that I can access theforEach
method using es6. If this is new to you or confusing, you can check out this thread on Stack Overflow.