Demo:
Modern day web browsing has come a long way since the 90's. Do you remember web surfing on your dial up internet and coming across every other website using really cheesy flash animations like spinning 3-d logos or mouse trails? I certainly do.
Now that flash is being pushed out of the scope of things (firefox recently announced that it will prevent flash from working in its browser) developers are starting to use an HTML element called Canvas. It has adopted some of the better features from flash and is being used widely in the front-end development community for creating interactive graphics, videos, and games that run in the browser.
Canvas uses scripting, such as JavaScript, to render graphics. It is currently supported by all recent versions of all major browsers and by most (currently 95%) of all active versions of those browsers. Canvases are very simple to create. The only HTML needed is a simple tag. There are no required attributes. If you are worried about supporting all browsers, you can put a fallback element inside of this tag for older browsers to read when they can't use canvas.
[code language="html"]
<canvas><!-- Fallback goes here --></canvas>
[/code]
For our project, we will be making two canvases. One for our reference image and the other for our graphics. In this example we will give both elements id attributes to allow easy implementation of JavaScript. You can also set an inline width and height in the attributes, which is recomended when going smaller than the window size, but for the purposes of this demo our project will be set to the window's width and height.
[code language="html"]
<canvas id="canvas-interactive"></canvas>
<canvas id="canvas-reference"></canvas>
[/code]
The canvas is initially transparent. The next step is to target each canvas in JavaScript and set up their contexts. This defines what type of content the canvas will hold. In this demo, our content will sit on a 2d surface.
[code language="javascript"]
var canvasInteractive = document.getElementById('canvas-interactive');
var canvasReference = document.getElementById('canvas-reference');
var contextInteractive = canvasInteractive.getContext('2d');
var contextReference = canvasReference.getContext('2d');
[/code]
Our canvases are now setup and ready to be drawn on. Before we jump into particles, let's go over how the 'rendering' will work. Our animation will rely on the method: window.requestAnimationFrame(callback). You might be familiar with setInterval(function, delay) or setTimeout(function, delay). The main difference with window.requestAnimationFrame(callback) is it will limit itself to the user's browser for the amount of requests made over time. So it only runs when the browser is ready for the next request. This allows the loop to run in a controlled environment instead of relying on a set number of milliseconds for the next call. The request also caps out at 60 times per second. If you have any experience with FPS (frames per second) this should start sounding familiar. Our main function that we will tie to this method will create shapes on the canvas, then delete them. Repeatedly. In this cycle, we are calculating each shape's new position, which updates with each frame. This presents the illusion of movement.
The positions of these shapes, or particles, are based on a coordinate system. This coordinate grid is basically a map of the canvas pixels starting at (0,0) in the top left corner. So if you put an image at the start of the canvas that was 300px by 300px, the bottom right corner would sit at the coordinates: (300, 300).
For this specific example, we use a logo that has a single color and no background. The detailed specifications are:
• PNG format (allowing transparency)
• Completely transparent Background
• Foreground color set to the same color of the background it will sit ontop of
• Foreground opacity set very low (we were able to get ours to work at 3%, but this might vary depending on your logo. Start at 100% and when your program works, adjust the logo down until you find the lowest opacity possible).
Place the image src in an img tag under the canvases:
[code language="html"]
<img src="logo.png" alt="..." id="img">
[/code]
The image sitting on the page should be hidden and the canvas should sit in the middle of the page, so we set up some simple css styling:
[code language="css"]
html, body {
margin: 0px;
position: relative;
background-color: #303030; /* This should match your logo's color */
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
img {
display: none;
}
[/code]
Let's create our first frame.
The first step is to create our global JavaScript. This is where we will store all the information that will be accessed by the functionality of our script. We will define the canvas dimensions, logo dimensions, the center point, a logo location to get the top left corner of the logo for a reference of where to begin drawing our particles, the mouse attributes, particle array and the particle attributes.
[code language="javascript"]
var image = document.getElementById('img');
var width = canvasInteractive.width = canvasReference.width = window.innerWidth;
var height = canvasInteractive.height = canvasReference.height = window.innerHeight;
var logoDimensions = {
x: 500,
y: 500
};
var center = {
x: width / 2,
y: height / 2
};
var logoLocation = {
x: center.x - logoDimensions.x / 2,
y: center.y - logoDimensions.y / 2
};
var mouse = {
radius: Math.pow(100, 2),
x: 0,
y: 0
};
var particleArr = [];
var particleAttributes = {
friction: 0.95,
ease: 0.19,
spacing: 6,
size: 4,
color: "#ffffff"
};
[/code]
Next, we create the particle functions. To do this, I made a constructor function that creates a particle and has a prototype method that will a update the particles. The math in the update function would be a very long topic, so if you are unfamiliar with physics and trigonometry, I highly suggest checking out 'Coding Math' by Keith Peters. (http://www.codingmath.com/). Simply put, our update function calculates a new position for the particle by taking the old position and applying a formula. The result is a new (x,y) coordinate for this particle.
[code language="javascript"]
function Particle(x, y) {
this.x = this.originX = x;
this.y = this.originY = y;
this.rx = 0;
this.ry = 0;
this.vx = 0;
this.vy = 0;
this.force = 0;
this.angle = 0;
this.distance = 0;
}
Particle.prototype.update = function() {
this.rx = mouse.x - this.x;
this.ry = mouse.y - this.y;
this.distance = this.rx * this.rx + this.ry * this.ry;
this.force = -mouse.radius / this.distance;
if(this.distance < mouse.radius) {
this.angle = Math.atan2(this.ry, this.rx);
this.vx += this.force * Math.cos(this.angle);
this.vy += this.force * Math.sin(this.angle);
}
this.x += (this.vx *= particleAttributes.friction) + (this.originX - this.x) * particleAttributes.ease;
this.y += (this.vy *= particleAttributes.friction) + (this.originY - this.y) * particleAttributes.ease;
};
[/code]
The next step is to layout our particles into a bigger shape, like pixels in a photo. This will be done by 'drawing' our logo to the reference canvas using the drawImage method. We then get the data of this drawing pixel by pixel. This data can get really confusing because it is in a one-dimensional array. This complicates things because we are working in a two-dimensional space. To add to the complexity, the only data logged is the RGBA(red, green, blue, alpha) values of each pixel. So for a single, transparent pixel, the data pushed is '0,0,0,0'.
In this example, we only need to check the alpha values and map a pixel when we come across one that is above 0.
To iterate through this data and translate each desired pixel into a coordinate, we need to create a nested for loop. This double iteration will move along the y-axis of the canvas grid and check every x pixel that column.
When the iteration comes across a pixel that has an alpha value greater than 0, we will create a particle at this coordinate.
[code language="javascript"]
function init() {
contextReference.drawImage(image,logoLocation.x, logoLocation.y);
var pixels = contextReference.getImageData(0, 0, width, height).data;
var index;
for(var y = 0; y < height; y += particleAttributes.spacing) {
for(var x = 0; x < width; x += particleAttributes.spacing) {
index = (y * width + x) * 4;
if(pixels[++index] > 0) {
particleArr.push(new Particle(x, y));
}
}
}
};
init();
[/code]
Utilizing our particle array, we create a function that will iterate through each particle and run that particle's update function to set its new x and y coordinates.
[code language="javascript"]
function update() {
for(var i = 0; i < particleArr.length; i++) {
var p = particleArr[i];
p.update();
}
};
[/code]
The next step is to render these updated particles. We first clear the canvas to delete the old particles, then we draw new ones. Since our particles are going to live on our interactive canvas, we need to use the interactive canvas context. To clear the canvas, use: clearRect(starting x, starting y, ending x, ending y).
Next, we iterate through the particles and apply styles to them. We are applying a color using fillStyle = color, then drawing them to the canvas using fillRect(x, y, width, height).This method takes in the coordinates that we updated with the previous function, then creates a shape at that point.
There are many ways to draw shapes to a canvas. If you would like to learn more about the types of shapes you can draw, check out: (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes).
[code language="javascript"]
function render() {
contextInteractive.clearRect(0, 0, width, height);
for(var i = 0; i < particleArr.length; i++) {
var p = particleArr[i];
contextInteractive.fillStyle = particleAttributes.color;
contextInteractive.fillRect(p.x, p.y, particleAttributes.size, particleAttributes.size);
}
};
[/code]
The next step is where it all comes together. We create an animate function that runs the update and render functions. It then opens up its special loop using the requestAnimationFrame() method that we mentioned earlier.
[code language="javascript"]
function animate() {
update();
render();
requestAnimationFrame(animate);
}
animate();
[/code]
Finally, to track the mouse position (or touch position for mobile/tablet users). Add the following event handlers:
[code language="javascript"]
document.body.addEventListener("mousemove", function(event) {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
document.body.addEventListener("touchstart", function(event) {
mouse.x = event.changedTouches[0].clientX;
mouse.y = event.changedTouches[0].clientY;
}, false);
document.body.addEventListener("touchmove", function(event) {
event.preventDefault();
mouse.x = event.targetTouches[0].clientX;
mouse.y = event.targetTouches[0].clientY;
}, false);
document.body.addEventListener("touchend", function(event) {
event.preventDefault();
mouse.x = 0;
mouse.y = 0;
}, false);
[/code]