Previous articles were focused on how to serve the user with a smooth animation, but there was no word how to create it. This article aims at fixing this. I will review browser capabilities to draw an animation and mention some specific aspects that should be taken into consideration during its creation.

How can we draw in a browser?

There are at least three approaches available:

  • CSS Animation
  • HTML animation using JavaScript
  • Canvas

CSS Animation.

CSS animation allows to animate an element transition from one CSS style configuration to another. For example, the code below allows us to make a heart beat.

<!-- HTML -->

<div class="heart-container">
    <div class="heart-img"></div>
</div>
<style>
    .heart-container{
        padding: 5px;
        height: 100px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .heart-img {
        background-size: cover;
        background-image: url('/img/Heart.png');

        animation-duration: 0.5s;
        animation-name: beats;
        animation-iteration-count: infinite;
    }

    @keyframes beats{
        from{
            width: 75px;
            height: 75px;
        }

        50% {
            width: 100px;
            height: 100px;
        }

        to{
            width: 75px;
            height: 75px;
        }
    }
</style>

There are three important components of CSS animation should be pointed:

  • Keyframes that represent waypoints that the animation should pass through (from; percentage from 0 to 100; to)
  • Element style per keyframe that describes how the DOM element should look like when the animation reaches this frame
  • Animation properties that tell the browser how the animation should be performed

Keyframes and element style components are well described in their definition and the example, therefore I will skip their further explanation and focus on animation properties, thank God, their list is very short:

  • animation-name specifies the name of the keyframes that should be applied to DOM element
  • animation-duration specifies the length of time that should take an animation to complete one cycle
  • animation-timing-function specifies a function that is responsible for how the animation transitions through keyframes. Available functions are: ease, linear, ease-in, ease-out, ease-in-out, cubic-bezier, steps, step-start, step-end
  • animation-delay specifies the delay between the time DOM element is loaded and the beginning of the animation
  • animation-iteration-count specifies the number of times the animation should repeat
  • animation-direction specifies the direction of animation. Available options are: normal, reverse, alternate, and alternate-reverse
  • animation-fill-mode configures what style is applied to the animation before and after it is executing. Avalable options are: none, forwards, backwards, both
  • animation-play-state lets you pause and resume the animation

I have not set the goal to describe these properties in details, I have just tried to show you how limited the abilities of CSS animation are. If you require more details here, please look at developer.mozilla.org.


As you can see, CSS animation is a pretty simple way to animate DOM elements. Unfortunately, if we are going to create a game, all these CSS transformations become a nightmare. Why? Because any game consists of hundreds of elements that interact with or influence each other. You have to be a genius to perform all the required transformations at appropriate time. Also, I cannot imagine how the user input can be processed. I’m not a genius, therefore I will look at the next animation approach that is called HTML animation using JavaScript.

HTML animation using JavaScript

HTML animation using JavaScript in its essence is the same as CSS animation. All actions that we perform are to change a DOM element style and position. But there are many more available possibilities. We can manage timing with the help of setInterval or requestAnimationFrame functions and easily process the user input. Let’s see how it works.

<!-- HTML -->

<div id="draggable-container">
    <div id="draggable-square">Drag Me</div>
</div>

<style>
    #draggable-container{
        height: 100px;
        background-color: lightgray;
    }
    #draggable-square {
        width: 40px;
        height: 40px;
        background-color: #FE5F55;
        position: absolute;
        text-align: center;
        font-size: 12px;
        border-radius: 8px;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }
</style>
<script type="text/javascript">
    var isDragging = false;
    var yd, xd = 0;
    var element = document.getElementById('draggable-square');
    var parent = document.getElementById('draggable-container');
    var parentboundaries = parent.getBoundingClientRect();

    element.addEventListener('mousedown', enabledraggable);
    document.addEventListener('mousemove', movedraggable);
    document.addEventListener('mouseup', disabledraggable);

    function enabledraggable(e){
        isDragging = true;

        var boundaries = element.getBoundingClientRect();

        yd = e.y - boundaries.top;
        xd = e.x - boundaries.left;
    }

    function disabledraggable(){
        isDragging = false;
        yd, xd = 0;
    }

    function movedraggable(e){
        if (isDragging){
            var y = e.pageY - yd;
            var x = e.pageX - xd;
            
            if (y > parent.offsetTop && y < parent.offsetTop + parentboundaries.height - 40){
                element.style.top = y + 'px';
            }

            if (x > parentboundaries.left && x < parentboundaries.left + parentboundaries.width - 40){
                element.style.left = x + 'px';
            }
        }
    }
</script>

The code above displays an element that the user can drag over the container.

Drag Me

I’ve tried to make the code as simple as possible, but some parts of it require a little of explanation.

//js

yd = e.y - boundaries.top;
xd = e.x - boundaries.left;

...

var y = e.pageY - yd;
var x = e.pageX - xd;

Mouse event provides a cursor position. It means that if we do not take into account difference between the cursor position and the left top corner of the element, the element will go beyond top and left boundaries of the container. On the other hand, we need to take into consideration the element size (40px) to be sure that the element will not go beyond bottom and right boundaries.

//js

parentboundaries.height - 40
parentboundaries.width - 40

I hope that the code is clear now, so I propose to add some fun. Let’s modify the code in the following way:

//js

//Make new global bariables
var interval;
var deg = 0;

//Add set interval function to the end of enabledraggable handler
interval = setInterval(function(){
    deg = deg > 360 ? 0 : deg + 10;

    element.style.webkitTransform = 'rotate('+deg+'deg)'; 
    element.style.mozTransform    = 'rotate('+deg+'deg)'; 
    element.style.msTransform     = 'rotate('+deg+'deg)'; 
    element.style.oTransform      = 'rotate('+deg+'deg)'; 
    element.style.transform       = 'rotate('+deg+'deg)'; 
}, 1000/60);

//Add clear interval function to the end of disabledraggable handler
clearInterval(interval);
Drag Me

Now we have element that rotates during dragging. It is an amazing result with little effort, isn’t it?

To sum up, I would like to mention that HTML animation using JavaScript is a powerfull approach. It allows to utilize CSS animation with ability to process the user input, but… is it enough to make a game? Perhaps. In some cases it is, for example if you would like to create a flying and rotating square. But what to do if you are going to create a snow falling from the sky? There are hundreds of snowflakes each of which can have its own trajectory depending on wind and other snowflakes. I don’t think that this kind of animation can be performed well with this approach, because when you make changes to DOM that triggers re-painting of the layout, the browser performance starts to slow down, especially if you do a lot of manipulations at once. What can we do? We can try to optimize our code or, and I think it is a better decision, to look at a canvas element.

Canvas

Canvas is a DOM element that allows to draw graphics via JavaScript. It can be used for drawing, animation, and pictures manipulations. Moreover, it can be used for real-time video processing! Amazing! The canvas mostly focuses on 2D graphics, but there are accelerators available to draw in 3D (for now 3D graphics and real-time video processing are out of scope of this blog, therefore I will not review their features).

So, how can we draw on canvas? First of all you need to add canvas element to the page.

<!-- HTML -->

<canvas id="example-canvas" width="400px" heigh="400px" style="background-color: #292a2d"></canvas>

Then you need to initialize canvas context.

//js

var example = document.getElementById("example-canvas");
var ctx = example.getContext('2d');

After that you can use numerous functions to draw any combination of elements, for example, circles that represent snowflakes mentioned above (to see available drawing functions please look at developer.mozila.org documentation).

It looks complicated, but it is not true. At first, we need to create an object that represents a snowflake and the method that is responsible for its drawing.

//js

function Snowflake(x, y, r){
    this.x = x;
    this.y = y;
    this.radius = r;

    this.draw = function(){
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, 0);
        ctx.fillStyle = 'white';
        ctx.fill();
    }
}

Then we need to create an array of snowflakes that should be displayed on the canvas and fill it with predefined quantity of snowflakes with random position and radius (for beauty).

//js
var snowflakes = [];
var count = 1000;

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min;
}

for(var i = 0; i <= count; i++){
    var x = getRandomInt(0, ctx.canvas.width);
    var y = getRandomInt(0, ctx.canvas.height);
    var r = getRandomInt(1, 3) * 0.7;

    var snowflake = new Snowflake(x, y, r);

    snowflakes.push(snowflake);
}

And the last thing that we need to do is to draw these snowflakes.

//js

function draw(){
    for(var i = 0; i < count; i++){
        snowflakes[i].draw();
    }
}

draw();

If I have not missed anything, on the screen you should see a black rectangle with a lot of white circles that now look like stars rather than snowflakes. To fix it, we need to allow the snowflakes to fall. For this we can add some velocity for the each snowlake. I propose to calculate velocity based on a snowflake radius, the bigger radius the higher velocity. Also, I propose to divide it by 2 to make the motion slow.

//js

function Snowflake(x, y, r){
    ...
    this.velocity = r/2;
    ...
}

Ok. Now we have the velocity, but how can it be applied? In our case, the snowlakes should fall, therefore all that we need is to simply increase y coordinate after or before each draw operation.

//js

function Snowflake(x, y, r){
    ...
    this.draw = function(){
        this.y = this.y + this.velocity;
        ...
    }
    ...
}

And the last step, we need to implement a game loop with the help of requestAnimationFrame.

//js

function draw(){
    //clear canvas from the previous frame.
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for(var i = 0; i < count; i++){
        snowflakes[i].draw();
    }

    window.requestAnimationFrame(draw);
}

window.requestAnimationFrame(draw);

What happens when a snowflake goes beyond the bottom boundary of the canvas? It is continuing to fall. We don’t see it, but its position is still calculated and draw function still tries to draw it. I don’t think that it is a correct behaviour, therefore I propose to get this kind of snowflakes and set their y coordinate to zero. This will allow us to create the feeling of endlessly falling snow. So, let’s modify again the draw method of snowflake object.

//js

function Snowflake(x, y, r){
    ...
    this.draw = function(){
        this.y = this.y + this.velocity;
        if (this.y > ctx.canvas.height){
            this.y = 0;
        }
        ...
    }
    ...
}

Amazing! We have endless falling snow with minimum efforts. Drawing with the Canvas is an enchanting approach, because even if we increase the quantity of snowflakes up to 10.000 it will still work fast and without any delay or performance issue.

Recap

There are at least three approaches to draw in the browser:

  • CSS Animation
  • HTML animation using JavaScript
  • Canvas

CSS animation is good if you are going to make a simple animation that does not require the user input. In other case, HTML animation using JavaScript can be useful. But they both have gaps in case if you are trying to make complex or resource consuming animation. For such animation, Canvas will suit better.