WARNING: unbalanced footnote start tag short code found.
If this warning is irrelevant, please disable the syntax validation feature in the dashboard under General settings > Footnote start and end short codes > Check for balanced shortcodes.
Unbalanced start tag short code found before:
“size/2), padding - bounceT, (size-padding), padding); context.quadraticCurveTo”
Today I discovered r/loadingicon and promptly spent an unwise amount of my precious free time staring mesmerised at the screen. When I finally snapped out of it I finally resolved to actually sit down and learn how to use the HTML5 canvas element. I’m not claiming to have the arty chops of the people posting those icons, but it seemed like a really fun way of getting into the spirit of things.
And you know what? Basic canvas drawing with Javascript is easy. Really, really easy. An hour later I had something I’m going to call halfway fun to sit and stare at.
The temptation to tweak, improve or over-complicate was strong and I spent another half hour adding blur effects, trails and more complex distortion effects, and ended up deleting all of them. Simplicity really is the key here.
How it works
<section style="margin: 10px auto; width: 120px; height: 120px; padding: 0px;"> <canvas id="frame" width="100" height="100" /> </section>
That’s all it takes to define the canvas and frankly the section is only for layout. Then import your script that does all the actual work.
<script src="https://www.pixieland.org.uk/playground/bounce/bounce.js"></script>
Now let’s take a look at the script itself.
[Disclaimer: my knowledge of Javascript is bare-bones and I haven’t cleaned, tidied or optimized the following code. I’ve barely sanity-checked it]
First, add a hook to the onload
event to get the ball rolling (pun not… oh never mind):
window.onload = begin; function begin() { var canvas = document.getElementById("frame"); context = canvas.getContext("2d"); startTimer(); }
The canvas
variable is a reference to the HTML element we defined earlier, and the context is an object that actually has the capabilities we want – in this case 2D drawing. Then finally we call a method that will set things up and press the proverbial “go” button.
function startTimer() { size = 100; padding = 10; backCol = "#F3F1F5"; ballX = padding; ballY = size / 2; ballDX = 1; ballDY = -1; bounceL = 0; bounceR = 0; bounceT = 0; bounceB = 0; bounceDepth = 10; bounceRebound = 0.4; edgeWidth = 8; edgeCol = "#272727"; ballCol = "#272727"; ballSize = 10; mainTimer = setInterval("update()", 5); }
This mass of variables basically defines the look and feel of the animation and provides a “single point of tweaking”. In the middle of it all (where is really shouldn’t be, but see above) is the line mainTimer = setInterval("update()", 5);
. That starts the timer that calls a method ever 5 milliseconds. Now we’re cooking.
The update()
method is a little long and dull to transcribe in its entirety here. You can see it in the linked script easily enough if you’re that interested. The main points are:
context.clearRect(0,0,size,size);
This just clears the specified area of the canvas so it’s fresh for the next frame.
context.beginPath(); context.arc(ballX, ballY, ballSize, 0, 2 * Math.PI, false); context.fillStyle = ballCol; context.fill();
If you’re better at maths than I am you will quickly realise this code is defining a circle. If not, this code is defining a circle. beginPath()
tells the canvas that we’re about to draw a something. arc(blah)
actually defines an arc, as the name suggests. The parameters are “x” and “y” coordinates, radius, start angle and end angle (if you know that “2*PI radians” is 360 degrees, it makes more sense). The last parameter is actually optional and specifies if we’re going counter-clockwise or not. For a circle it’s irrelevant.
Then we set the fill style – a solid colour in our case – and tell the canvas to fill the path we just drew. Viola! A filled circle.
context.beginPath(); context.lineCap="square"; context.strokeStyle = edgeCol; context.lineWidth = edgeWidth; context.moveTo(padding, padding); context.quadraticCurveTo((size/2), padding - bounceT, (size-padding), padding); context.quadraticCurveTo((size-padding) + bounceR, (size/2), (size-padding), (size-padding)); context.quadraticCurveTo((size/2), (size-padding) + bounceB, padding, (size-padding)); context.quadraticCurveTo(padding - bounceL, (size/2), padding, padding); context.stroke(); context.fillStyle=backCol; context.fill();
Ooookay. A little more complex, and a little involved. This one is drawing the bounding box and it does so with quadratic curves. From a given starting point we specify a “control point” and an “end point”. The start point and the end points are fixed – the curve will always touch them, but the control point defines how the curve is shaped. The further away it is from the straight line between the start and end points, the more pronounced the curve will be. Here we’re drawing four curves one after another, finishing up back where we started. Then as with the circle we fill it with a background colour and call it a day. The reason we’re using a curve rather than a line is to allow for the distortion effect on the edges. Each time the ball bounces the control point for the relevant curve is moved a few pixels away from the centre and returns to its starting point over the next few frames.
[Implementation note: A curve with a control point that lies on the line is itself a straight line so we get the transformation for free without having to choose between a line and a curve each frame. A curve is more expensive but this is hardly high-performance gaming now is it?]
And that’s it. My first canvas animation. I’ve dabbled in this sort of thing before in other languages – Java and C# mostly, though it started with Basic back in the day – so this was a nicely familiar area to start in. Now if you’ll excuse me I’ve got a thousand dancing circles in my head I need to somehow translate into Javascript.