The Web is just starting to use animation well. For
years, animated GIFs and Flash ruled. Text moved and flashed, but it was
never seamless. Animations had boxes around them like YouTube videos.
HTML5 canvas
changes everything about Web animation.
The canvas
element makes it possible to integrate
drawings and animations with the rest of your page. You can combine them
with text and make animations interactive. This drawing mechanism is
powerful, but very low-level.
Animations get more power and need less coding when you combine the canvas
tag with higher-level libraries such as Paper.js. This article introduces HTML5 animation and walks you through creating an animation of dandelion seeds blowing in the wind.
[Editor's note: A must-have for professional Web designers and developers: The Printed Smashing Books Bundle is full of practical insight for your daily work. Get the bundle right away!]
Neat Is Easy, But Messy Is Hard
Computers love clean. They make spreadsheets, do statistics and plot multivariate curves; they always color inside the lines.
In the real world, even simple things are messy. Leaves falling from
trees, water splashing — all the little interactions around us feel
simple because we’re used to them; but little bursts of wind are
actually messy and unpredictable.
For this article, we’ll animate dandelion seeds blowing in the breeze.
Dandelions are tricky because we all know what they look like: we’ve
touched them and blown their seeds off. Commonplace objects produce
instant recognition and feelings. I don’t have to tell you what
dandelions are — you just know. Dandelions are a chaos of seeds piled on
top of each other.
Our dandelion animation will never reproduce the complexity of the
real thing, and it will work better if we don’t try: make it too close
to real and it will feel funny. Instead, we’ll create a stylized
dandelion that makes the right impression without all of the details.
Paper.js
Drawing simple shapes with the canvas
tag, without any special drawing libraries, is easy. Create your canvas
:
1 | < canvas id = "canvas" width = "300" height = "300" ></ canvas > |
Then add a little JavaScript.
02 | var canvas = $( '#canvas' )[0].getContext( "2d" ); |
06 | canvas.arc(100, 100, 15, 0, Math.PI*2, true ); |
There are two basic types of graphics, vectorized and rasterized.
Rasterized graphics are like the pictures you take with your camera:
big rectangles with maps denoting the color of each pixel. Enlarge them
and you’ll get blurry dots.
Vector graphics are like connect-the-dots pictures: they’re sets of
lines and shapes that give instructions on how to draw the image at any
size. Using vector graphics, you can make an image of the letter Z
really big and it will still look sharp. If you turned it into a
rasterized graphic by taking a picture of it and then blowing it up, the
letter would get all blurry.
Vector graphics libraries are perfect for animation because they make
resizing, rotating and moving objects easy. They’re also much faster,
because the program has instructions for drawing each object instead of
needing to figure it out.
The dandelion is a complete functioning example, and you can see it all running on the example page.
You can also change the code by clicking the “Edit” button, see your
changes live, and copy and paste the code to your own website. Over the
course of the article, we’ll explain each part of the code in turn, but
please note that in order to run the code yourself, you will need to
head over to the example page and copy and paste it to your own
environment.
Drawing Our Dandelion
The first step is to import our JavaScript and PaperScript files.
1 | < script src = "paper.js" type = "text/javascript" charset = "utf-8" ></ script > |
2 | < script type = "text/paperscript" canvas = "canvas" src = "dandelion.pjs" id = "script" ></ script > |
The PaperScript code for running the animation is declared as text/paperscript
. Now we’re ready to start drawing.
The first part of our dandelion is the stem. The stem is the green
arc, with a circle on the top for the bulb. We’ll make both shapes with a
path, a list of shapes, points and lines that the browser is instructed to display.
Paths are the basic building blocks of animation. They render lines,
curves and polygons. You can also fill them in to make complex shapes.
Our path looks like this:
02 | path.strokeColor = '#567e37' ; |
05 | var firstPoint = new Point(0, 550); |
08 | var throughPoint = new Point(75, 400); |
09 | var toPoint = new Point(100, 250); |
10 | path.arcTo(throughPoint, toPoint); |
Our path is an arc, so it needs three points: the start, the end and a
midpoint to arc through. Three points are enough to define any arc we
need. The arcTo
function draws the line between them. The path item also supports styling information, such as stroke color and stroke width; #567e37
and 5
will make our arcing line green and thick. Paper.js supports the same color definitions as CSS.
We can add a few more items to make it all easier to see:
1 | path.fullySelected = true ; |
3 | var circle = new Path.Circle(throughPoint, 5); |
4 | circle.fillColor = '#CC0000' ; |
Fully selecting the path will display some lines to show us the arc; the red circle shows us where the through point is.
The stem ends with a circle to show the bulb of the flower and give
us a place to attach all of the seeds. Circles are much easier in
Paper.js than in direct canvas
.
1 | var bulb = new Path.Circle(toPoint, 10); |
2 | bulb.fillColor = '#567e37' ; |
One line of code draws our circle, one more makes it green, and now we’re ready to add our seeds.
Drawing The Seeds
Each seed has a bulb, a little stem and a wispy part on top.
Our seed starts with a small oval for the bulb and an arc for the stem. The oval is a rectangle with rounded corners:
1 | var size = new Size(4, 10); |
2 | var rectangle = new Rectangle(p, size); |
3 | var bottom = new Path.Oval(rectangle); |
4 | bottom.fillColor = '#d0aa7b' ; |
The seed stem is another arc, but this one is much thinner than the flower stem:
2 | stem.strokeColor = '#567e37' ; |
4 | stem.add( new Point(p.x + 2, p.y)); |
6 | var throughPoint = new Point(p.x + 4, p.y - height / 2); |
7 | var toPoint = new Point(p.x + 3, p.y - height); |
8 | stem.arcTo(throughPoint, toPoint); |
The wisps are more arcs with a circle at the end of each line. Each
seed has a random number of wisps that start at the top of the stem arc
and curve out in different directions. Randomness makes them look a
little bit messy and thus more natural. Each seed gets a random number
of wisps, between 4 and 10.
01 | for ( var i = 0; i < random(4, 10); i++) { |
03 | path.strokeColor = '#fff3c9' ; |
06 | var p1 = new Point(p.x, p.y); |
07 | path.add( new Point(p1.x + 2, p1.y + 2)); |
15 | throughPoint = new Point(p1.x + random(1, 3), p1.y - y); |
16 | toPoint = new Point(p1.x + random(5, 35), p1.y - 20 - y); |
18 | throughPoint = new Point(p1.x - random(1, 3), p1.y - y); |
19 | toPoint = new Point(p1.x - random(5, 35), p1.y - 20 - y); |
22 | path.arcTo(throughPoint, toPoint); |
25 | circle = new Path.Circle(toPoint, 2); |
26 | circle.fillColor = '#fff3c9' ; |
Now that we’ve drawn the seed, we need to manage it; later, we’ll
want to move and rotate it. The seed is made up of a lot of parts, and
we don’t want to have to manage each one separately. Paper.js has a nice
group object. Groups associate a set of objects together so that we can manipulate them all at once.
1 | var group = new Group(); |
The last step is to package our seed into a reusable object called Seed
. We add all of the code we’ve been writing to a new function with the name Seed
and add a function to create the initial variables. This example calls that function create
, but you can name it anything you want.
3 | this .create = function ( p, shortStem) { |
The create
function draws the seed at the specified Point, and the shortStem
boolean tells us whether this is a short stem. We’ll look at short-stemmed seeds a little later.
These types of functions don’t work as constructors in JavaScript, but are supported in PaperScript.
2 | seed.create( new Point(100, 100), false ); |
Our seeds will look like this when we draw them:
The Seed
object draws our random dandelion seeds. Now we can add them to our flower.
Adding A Little Chaos
The seeds will look better when we space them out around the circle
of our dandelion bulb to feel like a halo of seeds. The bulb is a
circle, and the circle is a path, so we can get each point on the path.
01 | var bulb = new Path.Circle(toPoint, 10); bulb.fillColor = '#567e37' ; |
03 | var angle = 360 / bulb.length; |
06 | for ( var i = 0; i < bulb.length; i++) { |
08 | seed.create(bulb.getPointAt(i)); |
11 | seed.rotate(i * angle); |
This will make a circle of seeds around the bulb but leave a space in
the middle. We’ll add a few more seeds to fill in the center. We’re
giving the center seeds short stems so that they show the white of the
wisps more than the beige of the stems.
1 | for ( var i = 0; i < 18; i++) { |
3 | var point = new Point(toPoint.x + random(-3, 3), |
4 | toPoint.y + random(-3, 3)); |
5 | seed.create( new Point(toPoint), true ); |
6 | seed.rotate(random(0, 360)); |
The seeds in the middle will bunch randomly and make our dandelion look nicely messy. Now we can make them blow off.
Animating The Seeds
Wind pushes seeds in complex patterns, and two seeds will never blow
off the same way. We want to make them look real, so we’ll need a little
more randomness.
Reproducing real wind is much too complicated, so we’ll make the
seeds float off in a random-looking pattern. Each seed is assigned a
random point on the right side of the screen as a final destination:
1 | this .dest = new Point(1800, random(-300, 1100)); |
The rotateMove
function pushes each seed toward its
destination point and rotates it. We can work with our Seed object as a
group to rotate and move it with one function.
01 | this .rotateMove = function ( angle) { |
02 | if ( this .group.position.x < 850 && this .group.position.y < 650) { |
03 | var vector = this .dest - this .group.position; |
04 | this .group.position += vector / 150; |
07 | this .group.rotate(angle); |
09 | this .isOffScreen = true |
This function will move the seed until it’s off the screen. Calling rotateMove
for each frame of our animation will make the seed float across the screen.
Paper.js gives us an easy way to make animations with the onFrame
function; when we implement onFrame
,
Paper.js will call it for every frame of our animation. With each
frame, we iterate over each seed and move it across the screen.
1 | function onFrame(event) { |
2 | for ( var i = 0; i < seedCount; i++) { |
3 | if (!seeds[i].isOffscreen()) { |
4 | seeds[i].rotateMove(random(2, 4)); |
The seeds slide and rotate a little closer to the destination point
with each frame of the animation. Starting all of the seeds at the same
point and ending them far apart makes them space out nicely as they
move.
We don’t want all of the seeds to fall off at once, so we’ll use a timer to make them drift away.
2 | var id = setInterval( function () { |
4 | if (seedCount === seeds.length) { |
The timer waits for one second before releasing the next seed, giving our dandelion a nice floaty feel.
Some green grass and blue sky as a background image for our canvas
puts it all into context. Now we have a dandelion with seeds floating on the breeze.
Paper.js In The Real World
Paper.js has some impressive examples and a nice coding model, but
you should know a few gotchas before using it on your website.
It Doesn’t Work In Old Browsers
All Paper.js drawings use the canvas
tag and require
HTML5. This means that you need Internet Explorer 9+, Firefox 4+, Safari
5+ or Chrome. If your website must support older browsers, then you
won’t be able to use canvas
.
There’s no way around this requirement; if you need older browsers, you’re out of luck. As the Paper.js website says, “Let’s go forward!.”
Performance Can Be Slow
Paper.js can make a browser grind to a halt even if the browser
supports HTML5. Pixar renders Buzz and Woody on giant server farms — all
you get is your user’s cheap MacBook.
Not only are laptops slower than server clusters, but browsers make things worse by rendering the canvas
tag with the CPU instead of the GPU.
Games like Halo and Rage take advantage of the graphics processor on
your video card to render rocket launchers and mutants. The CPU is less
efficient with graphics, so the same computer that handles complex video
games smoothly can make floating dandelion seeds look slow and jerky.
Make sure to test all of your animations with slower hardware, and
watch the CPU usage. Use groups to minimize the calculations, and be
very careful about what you do in each invocation of the onFrame
function.
Mobile Devices Are Slower
Mobile performance is even worse. Most mobile devices support canvas
, but they are mostly too slow to render canvas
animations well. Even more powerful devices, like the iPad 2, can’t handle the dandelion seeds smoothly.
It Doesn’t Support Object-Level Events
Other drawing libraries, such as SVG (see below), support
object-level mouse and keyboard events. Those events make it easy to
respond when a path or a polygon is clicked, hovered over or touched.
The canvas
tag doesn’t support object-level events. Paper.js has some basic functionality for hit testing, but it’s very low-level. You can listen for mouse and keyboard events on the whole canvas, but you’ll need to handle mapping those events to individual controls.
What About SVG?
The SVG (Scalable
Vector Graphics) specification was defined over 10 years ago, but came
to the forefront with support libraries such as Raphaël.js,
which make it easy to generate SVG images with JavaScript. SVG is
powerful, works well for smaller images, and is supported all the way
back to Internet Explorer 7 with conversion to VML (Vector Markup Language). SVG is the best choice if you need to support older browsers.
The real issues with SVG are speed, future support and mobile devices. Every browser maker is actively working on making canvas
faster. Safari 5 already offers hardware acceleration with the GPU for canvas
, and the rest are working on it. SVG is also unsupported on Android devices.
There’s a growing community around canvas
, the new technology that vendors are focusing on. They’re adding new features, fixing bugs and making it better every day.
Other Canvas Drawing Libraries
The three.js engine supports canvas
and the WebGL library, and it focuses more on 3-D drawings. Google Dart will also support canvas
with built-in rendering objects.
Paper.js is a mature library with a very supportive community on the Paper.js Google Group and many impressive and well-documented examples. Check out some of the amazing things people are doing with it.
More Paper.js Examples
Our dandelion is just the beginning. Below are a few other impressive animations written in Paper.js.
Where’s your Paper.js amazingness?
No hay comentarios:
Publicar un comentario