Nikhil Kumar bio photo

Nikhil Kumar

A stylish blog on my code

Gmail LinkedIn Github Résumé
Changelog:

Today, I wanted to create a loading animation for my images on my garden page. Since I have a lot of high quality images, I decided to use unveil.js. This javascript plugin allows me to lazy-load my images and also present a loading image.

Setting up the image

You need to first import unveil.js

1
<script src="/assets/js/jquery.unveil.js"></script>

Then create your image tag:

1
<img class="lazy" data-src="/images/gardening/lily.JPG" src="/images/loading.gif" />

Then run the javascript plugin:

1
2
3
4
5
$(document).ready(function() {

$(".lazy").unveil();

});

Example

Generating the animation

I decided I wanted to create my own custom loading animation. I was able to create my own animation on the canvas using javascript.

1
2
3
<div id="canvas-container" style="width: 500px; height: 300px;">
    <canvas id="sineCanvas" style="width: 500px; height: 300px;" width="500" height="300"></canvas>
</div>

Then I created a custom animation using javascript. This is an extension of a sample code on drawing a sine curve here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
(function () {

if (typeof(Humble) == 'undefined') window.Humble = {};
Humble.Trig = {};
Humble.Trig.init = init;

var unit = 100,
    canvas, context, canvas2, context2,
    height, width, xAxis, yAxis, gif,
    draw;

/**
 * Init function.
 * 
 * Initialize variables and begin the animation.
 */
function init() {
 
	canvas = document.getElementById("sineCanvas");
    
    canvas.width = 500;
    canvas.height = 300;
    context = canvas.getContext("2d");
    context.fillStyle = "#fdf6e3";
    context.fillRect(0,0,500,300);
    context.font = '18px sans-serif';
    context.strokeStyle = '#000';
    context.lineJoin = 'round';
    
    height = canvas.height;
    width = canvas.width;
    
    xAxis = Math.floor(height/2);
    yAxis = Math.floor(width/4);
    
    context.save();
    draw();
}

/**
 * Draw animation function.
 * 
 * This function draws one frame of the animation, waits 20ms, and then calls
 * itself again.
 */
draw = function () {
    
    // Clear the canvas
    //context.clearRect(0, 0, width, height);
    context.fillStyle = "#fdf6e3";
    context.fillRect(0,0,500,300);


    // Draw the axes in their own path
    context.beginPath();
    
    context.stroke();
    
    // Set styles for animated graphics
    context.save();
    context.fillStyle = 'blue';
    context.fillText("Loading...",20,150);
    context.strokeStyle = '#00f';
    context.lineWidth = 2;
   
  
    // Update the time and draw again
    draw.seconds = draw.seconds - .007;
    draw.t = draw.seconds*Math.PI;
    drawSine(draw.t);
    drawinSine(draw.t);
    setTimeout(draw, 10);
	
	
};
draw.seconds = 0;
draw.t = 0;

 function drawSine(t) {
    context.beginPath();
    context.strokeStyle = 'orange';

    var x = t;
    var y = Math.sin(x);
  

    context.moveTo(yAxis, unit*y+xAxis);
    
    // Loop to draw segments
    for (i = yAxis; i <= width; i += 10) {
        x = t+(-yAxis+i)/unit;
        
    	y = Math.sin(x);
        context.lineTo(i, unit*y+xAxis);
		context.stroke();

	 }

 context.stroke();

} 

function drawinSine(t) {
    context.beginPath();
    context.strokeStyle = 'red';   
    var x = t;
    var y = -1 * Math.sin(x);
    context.moveTo(yAxis, unit*y+xAxis);
    
    for (i = yAxis; i <= width; i += 10) {
        x = t+(-yAxis+i)/unit;
        y = -1* Math.sin(x);
        context.lineTo(i, unit*y+xAxis);
	}
    context.stroke();
}





    Humble.Trig.init()
	    
})();

Example

Here is how the loading canvas animation looks:

Loading the animation into a gif

I had two approaches to converting the animation into a .gif. One approach was run the conversion in javascript with a tool known as jsgif. The other approach was to use byzanz, a tool that generate gifs from a screen capture.

Using byzanz

I was able to find an extension of byzanz that actually lets you trace the region you want to record here.

1
2
byzanz 
FFcast2

Then copy this into a file called byzanz-record-region.sh and run it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash

# Delay before starting
DELAY=10

# Sound notification to let one know when recording is about to start (and ends)
beep() {
    paplay /usr/share/sounds/KDE-Im-Irc-Event.ogg &
}

# Duration and output file
if [ $# -gt 0 ]; then
    D="--duration=$@"
else
    echo Default recording duration 10s to /tmp/recorded.gif
    D="--duration=10 /tmp/recorded.gif"
fi

# xrectsel from https://github.com/lolilolicon/FFcast2/blob/master/xrectsel.c
ARGUMENTS=$(xrectsel "--x=%x --y=%y --width=%w --height=%h") || exit -1

echo Delaying $DELAY seconds. After that, byzanz will start
for (( i=$DELAY; i>0; --i )) ; do
    echo $i
    sleep 1
done
beep
byzanz-record --verbose --delay=0 ${ARGUMENTS} $D
beep

The gif location would be /tmp/recorded.gif

This was much more effective than the other approach. However, I would still mention it as the quality may be improved in the future.

Using jsgif

You can find the git repo here. First load the javascript files.

1
2
3
4
<script type="text/javascript" src="/assets/js/LZWEncoder.js"></script>
<script type="text/javascript" src="/assets/js/NeuQuant.js"></script>
<script type="text/javascript" src="/assets/js/GIFEncoder.js"></script>
<script type="text/javascript" src="/assets/js/b64.js"></script>

Then add the encoder to the javascript. I was a able to find a nice code segment that I was able to tweak here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var w = setTimeout(function() { // give external JS 1 second of time to load

    console.log('Starting');

    var canvas = document.getElementById("sineCanvas");
    var context = canvas.getContext('2d');
    var shots  = [];
    var grabLimit = 60;  // Number of screenshots to take
    var grabRate  = 270; // Miliseconds. 500 = half a second
    var count     = 0;

    function showResults() {
        console.log('Finishing');
        encoder.finish();
        var binary_gif = encoder.stream().getData();
        var data_url = 'data:image/gif;base64,'+encode64(binary_gif);
        document.write('<img src="' +data_url + '"/>\n');
    }

    var encoder = new GIFEncoder();
    encoder.setRepeat(0);  //0  -> loop forever, 1+ -> loop n times then stop
    encoder.setDelay(0); //go to next frame every n milliseconds
    encoder.start();

    var grabber = setInterval(function(){
    console.log('Grabbing '+count);
    count++;

    if (count>grabLimit) {
        clearInterval(grabber);
        showResults();
      }

     encoder.addFrame(context);

    }, grabRate);

}, 1000);

Notice: Running the image capture on the javascript itself actually interfered with the javascript code generating the animation. It made the animation much slower. The only way to make the animation faster is to reduce the rate of frame capture. This noticeably reduces the quality of the animation.