Skip to content

Instantly share code, notes, and snippets.

@alehlopeh
Created January 21, 2016 07:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alehlopeh/467cf5cfe77a5e4d27f0 to your computer and use it in GitHub Desktop.
Save alehlopeh/467cf5cfe77a5e4d27f0 to your computer and use it in GitHub Desktop.
Canvas Grid lines animation
<canvas class="canvas-bg js-grid-canvas">
/* GLOBAL */
const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
/* Robert Penners easing algorithms
* http://www.gizma.com/easing/
*
* t: current time
* b: start value
* c: total change in value
* d: duration
*/
window.easing = {
easeInSine: function (t, b, c, d) {
'use strict';
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOutSine: function (t, b, c, d) {
'use strict';
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOutSine: function (t, b, c, d) {
'use strict';
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
},
easeInQuad: function (t, b, c, d) {
'use strict';
t /= d;
return c*t*t + b;
},
easeOutQuad: function (t, b, c, d) {
'use strict';
t /= d;
return -c * t*(t-2) + b;
},
easeInOutQuad: function (t, b, c, d) {
'use strict';
t /= d/2;
if (t < 1) { return c/2*t*t + b; }
t--;
return -c/2 * (t*(t-2) - 1) + b;
},
easeInOutQuart: function (t, b, c, d) {
'use strict';
t /= d/2;
if (t < 1) { return c/2*t*t*t*t + b; }
t -= 2;
return -c/2 * (t*t*t*t - 2) + b;
},
easeInQuart: function (t, b, c, d) {
t /= d;
return c*t*t*t*t + b;
},
easeOutQuart: function (t, b, c, d) {
t /= d;
t--;
return -c * (t*t*t*t - 1) + b;
},
easeOutQuint: function (t, b, c, d) {
t /= d;
t--;
return c*(t*t*t*t*t + 1) + b;
},
easeInOutQuint: function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t*t*t*t + b;
t -= 2;
return c/2*(t*t*t*t*t + 2) + b;
}
};
class Animatable {
animateTo(properties, duration, delay, easing) {
this.animation = {
target: properties,
duration: duration,
delay: delay,
easing: easing
};
}
_getAnimatedPropery(property, progress) {
if (this.animation && typeof this.animation.target[property] !== 'undefined') {
const delta = this.animation.target[property] - this[property];
const duration = this.animation.duration + this.animation.delay;
let time = Math.max(0, duration * progress - this.animation.delay);
return this.animation.easing(time, this[property], delta, this.animation.duration);
}
return this[property];
}
}
class GridLine extends Animatable {
constructor(ctx, canvas, x, y) {
super();
this.ctx = ctx;
this.canvas = canvas;
this.x = x;
this.y = y;
}
draw(progress) {
this.ctx.beginPath();
this.ctx.moveTo(this.x, this.y);
const x = this._getAnimatedPropery('x', progress);
const y = this._getAnimatedPropery('y', progress);
this.ctx.lineTo(x, y);
this.ctx.stroke();
}
}
class GridDot extends Animatable {
constructor(ctx, canvas, x, y, size) {
super();
this.ctx = ctx;
this.canvas = canvas;
this.x = x;
this.y = y;
this.size = size;
}
draw(progress) {
this.ctx.beginPath();
this.ctx.moveTo(this.x, this.y);
const size = this._getAnimatedPropery('size', progress);
this.ctx.arc(this.x, this.y, size, 0, 2 * Math.PI);
this.ctx.fill();
}
}
// Main animation class
class GridAnimation {
constructor(el, duration = 2500, size = 64) {
this.canvas = el;
this.ratio = 1;
this.size = size;
this.duration = duration;
this.dotSize = 1;
this.lineColor = 'rgba(50,80,150, .5)';
this.dotColor = 'rgba(0, 173, 255, 1)';
this.lineEasing = easing.easeInOutQuint; //easing.easeOutQuart;
this.dotEasing = easing.easeInOutQuad;
this.lines = [];
this.dots = [];
this.numberOfHorizontalLines = undefined;
this.numberOfVerticalLines = undefined;
this.global_width = undefined;
this.global_height = undefined;
this.ctx = this.canvas.getContext('2d');
this._update = this._update.bind(this);
this._rescale = this._rescale.bind(this);
this._init();
}
////////////////////////////////////////////////
// PUBLIC API
////////////////////////////////////////////////
// Play animation from beginning
play() {
this.canvas.classList.add('js-animate');
if (this.ctx) {
console.log('GridAnimation.play()');
this.startTime = new Date();
this._draw();
this._update();
}
}
// Reset animation
reset() {
this._clearCanvas();
this.canvas.classList.remove('js-animate');
}
// Skip to end of animation and draw last frame
skipToEnd() {
this.canvas.classList.add('js-animate');
if (this.ctx) {
this._draw(1);
}
}
////////////////////////////////////////////////
// PRIVATE METHODS
////////////////////////////////////////////////
_init() {
if (this.ctx) {
window.addEventListener('resize', this._rescale);
this._rescale();
}
}
_clearCanvas() {
if (this.ctx) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
// return animation progress as value between 0 and 1
_animationProgress() {
const elapsed = new Date() - this.startTime;
return Math.min(1, Math.max(0, elapsed / this.duration));
}
/* Returns a random integer between min (inclusive) and max (inclusive)
* Using Math.round() will give you a non-uniform distribution!
*/
_getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
_buildAnimation() {
this._buildLines();
this._buildDots();
}
_buildLines() {
// create Horizontal lines
const delayPerHLine = this.duration / this.numberOfHorizontalLines / 2;
for (let i=0; i < this.numberOfHorizontalLines; i++) {
let lineDelay = delayPerHLine * i;
let line = new GridLine(this.ctx, this.canvas, 0, this.size*i);
if (i % 2) {
line.animateTo({x: this.global_width}, this.duration - lineDelay, lineDelay, this.lineEasing);
}
else {
line.x = this.global_width;
line.animateTo({x: 0}, this.duration - lineDelay, lineDelay, this.lineEasing);
}
this.lines.push(line);
}
// create Vertical Lines
const delayPerVLine = this.duration / this.numberOfVerticalLines / 2;
for (let j=0; j < this.numberOfVerticalLines; j++) {
let lineDelay = delayPerVLine * j;
let line = new GridLine(this.ctx, this.canvas, this.size*j, 0);
if (j % 2) {
line.animateTo({y: this.global_height}, this.duration - lineDelay, lineDelay, this.lineEasing);
}
else {
line.y = this.global_height;
line.animateTo({y: 0}, this.duration - lineDelay, lineDelay, this.lineEasing);
}
this.lines.push(line);
}
}
_buildDots() {
const numberOfDots = this.numberOfHorizontalLines * this.numberOfVerticalLines;
const dotDuration = this.duration*0.3;
const dotMinDelay = this.duration*0.2;
const dotMaxDelay = this.duration*0.6;
for (let k=0; k < numberOfDots; k++) {
const dotDelay = this._getRandomInt(dotMinDelay, dotMaxDelay);
const x = this.size * (k % this.numberOfVerticalLines);
const y = this.size * Math.floor(k / this.numberOfVerticalLines);
const dot = new GridDot(this.ctx, this.canvas, x, y, 0);
dot.animateTo({size: this.dotSize}, dotDuration, dotDelay, this.dotEasing);
this.dots.push(dot);
}
}
_rescale() {
this.global_width = Math.min(window.innerWidth, window.screen.width);
this.global_height = Math.min(window.innerHeight, window.screen.height);
if (this.ctx.webkitBackingStorePixelRatio < 2) {
this.ratio = window.devicePixelRatio || 1;
}
this.canvas.setAttribute('width', this.global_width * this.ratio);
this.canvas.setAttribute('height', this.global_height * this.ratio);
this.lines = [];
this.dots = [];
this.numberOfHorizontalLines = Math.ceil(this.global_height / this.size);
this.numberOfVerticalLines = Math.ceil(this.global_width / this.size);
this._buildAnimation();
this._draw();
}
// Draw current frame of animation
_draw(progress = this._animationProgress()) {
this.ctx.save();
// clear previous frame
this._clearCanvas();
// scale up for retina
this.ctx.scale(this.ratio, this.ratio);
// straddle our lines to make them sharp: http://diveintohtml5.info/canvas.html#pixel-madness
this.ctx.translate(0.5, 0.5);
/////////////////////////////////////
// high-dpi drawing start
/////////////////////////////////////
// Draw lines
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = this.lineColor;
for (let line of this.lines) {
line.draw(progress);
}
// Draw dots
this.ctx.fillStyle = this.dotColor;
for (let dot of this.dots) {
dot.draw(progress);
}
/////////////////////////////////////
// high-dpi drawing end
/////////////////////////////////////
this.ctx.restore();
}
// Request animation Frame callback
_update() {
// only draw until animation has completed
if (this._animationProgress() < 1) {
requestAnimationFrame(this._update);
}
this._draw();
}
}
const $el = $('.js-grid-canvas');
const gridAnimation = new GridAnimation( $el[0] );
gridAnimation.play();
//gridAnimation.skipToEnd();
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
// Quad
$easeInQuad = cubic-bezier(0.550, 0.085, 0.680, 0.530);
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940);
$easeInOutQuad = cubic-bezier(0.455, 0.030, 0.515, 0.955);
*
box-sizing border-box
html
background #000
color #fff
body
margin 0
.canvas-bg
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition: opacity 1s $easeInQuad;
&.js-animate
opacity: 1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment