Created
May 10, 2016 19:07
-
-
Save maxmumford/9ccf8f3132651857424abc02c1e7d170 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// builds an rgba string to use for canvas fill style | |
window.cocept.build_rgba = function(red, green, blue, alpha) { | |
var rgba = 'rgba(' | |
+ red | |
+ ', ' | |
+ green | |
+ ', ' | |
+ blue | |
+ ', ' | |
+ alpha | |
+ ')'; | |
return rgba; | |
}; | |
window.cocept.randomNumberBetween = function(min, max, can_be_negative){ | |
var num = Math.floor(Math.random() * max) + min; | |
if(can_be_negative) | |
num *= Math.floor(Math.random()*2) == 1 ? 1 : -1; // this will add minus sign in 50% of cases | |
return num; | |
} | |
// represents x and y coordinates | |
var Coordinates = function(x,y){ | |
this.x = x; | |
this.y = y; | |
this.copy = function(){ | |
return new Coordinates(this.x, this.y); | |
} | |
this.minus = function(coordinates){ | |
var this_new = this.copy(); | |
this_new.x -= coordinates.x; | |
this_new.y -= coordinates.y; | |
return this_new; | |
} | |
this.plus = function(coordinates) { | |
var this_new = this.copy(); | |
this_new.x += coordinates.x; | |
this_new.y += coordinates.y; | |
return this_new; | |
} | |
this.times = function(amount){ | |
var this_new = this.copy(); | |
this_new.x *= amount; | |
this_new.y *= amount; | |
return this_new; | |
} | |
this.equals = function(coordinates){ | |
if (coordinates.x == this.x && coordinates.y == this.y) | |
return true; | |
else | |
return false; | |
} | |
this.toString = function() | |
{ | |
return "x: " + this.x + ", y: " + this.y; | |
} | |
} | |
// Represents a circle on the canvas | |
var Circle = function(start_position, end_position, canvas, canvas_config){ | |
// remember all params | |
this.start_position = start_position; | |
this.end_position = end_position; | |
this.canvas = canvas; | |
this.canvas_config = canvas_config | |
// set current position | |
this.position = new Coordinates(start_position.x, start_position.y); | |
// make end position relative to image dimensions | |
this.make_end_position_relative = function(canvas_width, canvas_height, image_width, image_height){ | |
this.end_position.x = (canvas_width / 2) - (image_width / 2) + this.end_position.x; | |
this.end_position.y = (canvas_height / 2) - (image_height / 2) + this.end_position.y; | |
} | |
// lerp function - move gradually frame by frame | |
this.lerp = function(target_position){ | |
this.position.x += (target_position.x - this.position.x) * this.canvas_config.circle_movement_speed; | |
this.position.y += (target_position.y - this.position.y) * this.canvas_config.circle_movement_speed; | |
} | |
// update this.position according to how far towards the end position the circle should be | |
this.update_position = function(distance_as_percentage){ | |
if(distance_as_percentage > 100 || distance_as_percentage < 0) | |
throw "distance_as_percentage must be between 0 and 100"; | |
if(isNaN(distance_as_percentage)) | |
throw "distance_as_percentage must be a number"; | |
distance_as_percentage /= 100; | |
this.lerp(this.start_position.minus(this.end_position).times(distance_as_percentage).plus(this.end_position)); | |
return this; | |
} | |
// calculates the circle colour based on the distance as percentage | |
this.get_colour = function(distance_as_percentage){ | |
return window.cocept.build_rgba(this.canvas_config.circle_colour[0], | |
this.canvas_config.circle_colour[1], | |
this.canvas_config.circle_colour[2], | |
this.canvas_config.circle_colour[3] | |
); | |
} | |
// draw the circle on the canvas | |
this.draw = function(last_circle, distance_as_percentage, noise_x, noise_y){ | |
if(typeof(this.position) === 'undefined' || isNaN(this.position.x) || isNaN(this.position.y)) | |
throw "Circle position undefined or NaN" | |
if(typeof(noise_x) === 'undefined') | |
noise_x = 0; | |
if(typeof(noise_y) === 'undefined') | |
noise_y = 0; | |
// if position is end, add some noise | |
if (distance_as_percentage == 0){ | |
var draw_position_noise_x = window.cocept.randomNumberBetween(0, noise_x, true) | |
var draw_position_noise_y = window.cocept.randomNumberBetween(0, noise_y, true); | |
this.position = new Coordinates(draw_position_noise_x + this.position.x, | |
draw_position_noise_y + this.position.y); | |
} | |
// draw the circle | |
var colour = this.get_colour(distance_as_percentage); | |
this.canvas.draw_circle(this.position, this.canvas_config.circle_radius, colour); | |
// draw connecting lines | |
if(last_circle != null){ | |
this.canvas.draw_line(last_circle.position, this.position, colour); | |
} | |
} | |
}; | |
// Represents the canvas itself and provides functions for setup and drawing etc | |
var Canvas = function(){} | |
Canvas.prototype.init = function(config) { | |
// remember options and dimensions | |
this.config = config; | |
this.setDimensions(); | |
this.text_alpha = 1; | |
// get circles json | |
$.ajax({ | |
url: this.config.circles_json_path, | |
dataType: 'text', | |
context: this, | |
success: function(data){ | |
// minify json to remove comments before parsing | |
this.circle_json = JSON.parse(JSON.minify(data)); | |
this.parseCircleJsonIntoCircles(); | |
}, | |
fail: function(){ | |
console.log('There was a problem loading the circles'); | |
} | |
}); | |
// on window resize, get new canvas width and reparse circles | |
var context = this; | |
$(window).resize(function() { | |
context.parseCircleJsonIntoCircles(); | |
}); | |
// on mouse move, save mouse position | |
$(document).mousemove(function(e) { | |
context.mouse_position = new Coordinates(e.pageX, e.pageY); | |
}); | |
// draw every N frames | |
this.interval_id = setInterval(this.draw.bind(this), this.config.framerate); | |
} | |
Canvas.prototype.parseCircleJsonIntoCircles = function(number_of_duplicates){ | |
// number_of_duplicates default | |
if(typeof(number_of_duplicates) === 'undefined') | |
number_of_duplicates = 3 | |
// clear existing circles | |
this.circle_groups = []; | |
// circles in the same array will be connected by a line | |
var context = this; | |
for(var i = 0; i < number_of_duplicates; i++) { | |
$.each(this.circle_json, function(index, coordinate_group){ | |
var group = []; | |
$.each(coordinate_group, function(index, coordinates){ | |
// calculate circle start and end positions | |
rand_x = window.cocept.randomNumberBetween(0 - context.config.start_position_off_canvas_limit_x, | |
context.canvas_width + (context.config.start_position_off_canvas_limit_x * 2)); | |
rand_y = window.cocept.randomNumberBetween(0 - context.config.start_position_off_canvas_limit_y, | |
context.canvas_height + (context.config.start_position_off_canvas_limit_y * 2)); | |
start_position = new Coordinates(rand_x, rand_y); | |
end_position = new Coordinates(coordinates.x, coordinates.y); | |
// create circle | |
var circle = new Circle(start_position, end_position, context, context.config); | |
// make circle end position relative to the canvas dimensions | |
circle.make_end_position_relative(context.canvas_width, context.canvas_height, | |
context.config.image_width, context.config.image_height); | |
// save circle | |
group.push(circle); | |
}); | |
context.circle_groups.push(group); | |
}); | |
} | |
} | |
Canvas.prototype.calculateDistanceFromMouseToElement = function(element) { | |
if(typeof(this.mouse_position) === 'undefined') | |
return null; | |
return Math.floor( | |
Math.sqrt( | |
Math.pow( | |
this.mouse_position.x - (element.offset().left+(element.width()/2)), 2 | |
) + Math.pow( | |
this.mouse_position.y - (element.offset().top+(element.height()/2)), 2) | |
) | |
); | |
} | |
Canvas.prototype.draw_circle = function(position, radius, colour) { | |
this.config.ctx.beginPath(); | |
this.config.ctx.arc(position.x, position.y, radius, 0, Math.PI*2, true); | |
this.config.ctx.fillStyle = colour; | |
this.config.ctx.fill(); | |
this.config.ctx.closePath(); | |
} | |
Canvas.prototype.draw_line = function(start_position, end_position, colour){ | |
this.config.ctx.beginPath(); | |
this.config.ctx.moveTo(start_position.x, start_position.y); | |
this.config.ctx.lineTo(end_position.x, end_position.y); | |
this.config.ctx.strokeStyle = colour; | |
this.config.ctx.lineWidth = this.line_width; | |
this.config.ctx.stroke(); | |
} | |
Canvas.prototype.clear = function() { | |
this.config.ctx.clearRect(0, 0, this.canvas_width, this.canvas_height); | |
} | |
Canvas.prototype.setDimensions = function() { | |
// set and save canvas dimensions | |
this.config.canvas_element.attr('width', document.body.clientWidth); | |
this.config.canvas_element.attr('height', this.config.canvas_height); | |
this.canvas_width = document.body.clientWidth; | |
this.canvas_height = this.config.canvas_height; | |
}; | |
Canvas.prototype.draw = function() { | |
// check if canvas is visible | |
if($('canvas:visible').length == 0){ | |
return; | |
} | |
this.setDimensions(); | |
// clear existing drawings | |
this.clear(); | |
// calculate mouse distance to element | |
this.distance = this.calculateDistanceFromMouseToElement($(this.config.target_element_selector)); | |
if(this.distance != null){ | |
this.setDistanceAsPercentage(Math.max((this.distance / this.config.min_mouse_distance * 100) | |
- this.config.target_distance_leniency, 0)); | |
} | |
else { | |
this.setDistanceAsPercentage(100); | |
} | |
// update circle positions | |
var context = this; | |
$.each(this.circle_groups, function(index, circle_group){ | |
$.each(circle_group, function(index, circle){ | |
if(context.distance_as_percentage > 100){ | |
context.setDistanceAsPercentage(100); | |
} | |
circle.update_position(context.distance_as_percentage); | |
}); | |
}); | |
// calculate line width | |
this.calculate_line_width(); | |
// redraw circles | |
var context = this; | |
$.each(this.circle_groups, function(index, circle_group){ | |
var last_circle = null; | |
$.each(circle_group, function(index, circle){ | |
// draw circle | |
circle.draw(last_circle, context.distance_as_percentage, context.config.noise_x, context.config.noise_y); | |
last_circle = circle; | |
}); | |
}); | |
// calculate text alpha | |
this.calculate_text_alpha(); | |
// draw text line 1 | |
this.config.ctx.fillStyle = window.cocept.build_rgba(this.config.text_colour[0], this.config.text_colour[1], this.config.text_colour[2], this.text_alpha); | |
this.config.ctx.textBaseline = 'middle'; | |
this.config.ctx.textAlign = "center"; | |
this.config.ctx.font = this.config.text_line_1_size() + "px " + this.config.text_font; | |
this.config.ctx.fillText(this.config.text_line_1, this.canvas_width / 2, this.canvas_height / 2); | |
// draw text line 2 | |
this.config.ctx.font = this.config.text_line_2_size() + "px " + this.config.text_font; | |
this.config.ctx.fillText(this.config.text_line_2, this.canvas_width / 2, | |
(this.canvas_height / 2) + this.config.text_line_1_size() + this.config.text_line_2_margin_top); | |
} | |
Canvas.prototype.calculate_line_width = function() { | |
// calculate target line width | |
var target_line_width = this.config.line_width_max * Math.pow( (100 - this.distance_as_percentage) / 100, 2 ); | |
// lerp line width | |
if(typeof(this.line_width) != 'undefined'){ | |
var difference = target_line_width - this.line_width; | |
this.line_width += difference * 0.1; | |
} | |
else { | |
this.line_width = target_line_width; | |
} | |
// enforce minimum line width | |
this.line_width = Math.max(this.line_width, this.config.line_width_min); | |
return this.line_width; | |
}; | |
// updates the canvas text alpha depending on the distance as percentage | |
Canvas.prototype.calculate_text_alpha = function(){ | |
if(this.distance_as_percentage == 0 && this.text_alpha > 0) | |
this.text_alpha -= this.config.text_alpha_delta; | |
else if(this.distance_as_percentage != 0 && this.text_alpha < 1) | |
this.text_alpha += this.config.text_alpha_delta; | |
} | |
// event triggered when mouse distance as percentage hits zero | |
Canvas.prototype.onDistanceAsPercentageIsZero = function(){ | |
// grow logo in place | |
$('.nav-desktop #logo__container img').addClass('grow'); | |
} | |
// event triggered when mouse distance as percentage leaves zero | |
Canvas.prototype.onDistanceAsPercentageIsNotZero = function(){ | |
// shrink logo in place | |
$('.nav-desktop #logo__container img').removeClass('grow'); | |
} | |
// set distance_as_percentage variable and trigger related events | |
Canvas.prototype.setDistanceAsPercentage = function(new_value){ | |
if(new_value > this.config.max_distance_as_percentage) | |
new_value = this.config.max_distance_as_percentage; | |
this._old_distance_as_percentage = this.distance_as_percentage; | |
this.distance_as_percentage = new_value; | |
// trigger events | |
if(this._old_distance_as_percentage != 0 && new_value == 0) | |
this.onDistanceAsPercentageIsZero(); | |
if(this._old_distance_as_percentage == 0 && new_value != 0) | |
this.onDistanceAsPercentageIsNotZero() | |
} | |
Canvas.prototype.stop = function() { | |
clearInterval(this.interval_id); | |
}; | |
// setup the canvas | |
$(document).ready(function(){ | |
var canvas_config = { | |
// CANVAS // | |
ctx: $('canvas')[0].getContext("2d"), // canvas 2d context | |
canvas_element: $('canvas'), | |
canvas_height: 440, | |
// WORKINGS // | |
framerate: 25, | |
target_element_selector: '.nav-desktop #logo__container img', // the element the mouse must be on to reveal the image | |
image_width: 645, | |
image_height: 400, | |
max_distance_as_percentage: 75, // the highest value for distance as percentage | |
// MOUSE // | |
min_mouse_distance: 250, | |
target_distance_leniency: 40, // mouse can be this far away from target element and still be considered "on it" | |
// CIRCLES // | |
circle_colour: [255, 255, 255, 0.1], | |
circles_json_path: '/data/circles.json', | |
circle_radius: 5, | |
start_position_off_canvas_limit_x: 50, // how far the circle start positions can be off the canvas | |
start_position_off_canvas_limit_y: 50, | |
circle_movement_speed: 0.075, // the speed modifier for circles. 0.05 is a smooth and medium speed value | |
noise_x: 2, // amount of random movement on the x axis to add when circle is in the end_position | |
noise_y: 2, | |
line_width_max: 6, // the maximum line width - used when mouse is on target element | |
line_width_min: 1, | |
// TEXT // | |
text_colour: [255, 255, 255], // array with 3 ints - rgb | |
text_font: 'Droid Sans', | |
text_line_1_size: function(){ | |
if (document.body.clientWidth <= 380) | |
return 20; | |
else if(document.body.clientWidth <= 768) | |
return 30; | |
else | |
return 50; | |
}, | |
text_line_2_size: function(){ | |
if (document.body.clientWidth <= 380) | |
return 12; | |
else if(document.body.clientWidth <= 768) | |
return 20; | |
else | |
return 35; | |
}, | |
text_line_2_margin_top: 15, | |
text_line_1: "Text Line 1", | |
text_line_2: "Text Line 2", | |
text_alpha_delta: 0.05 // the amount of alpha to or add each frame | |
} | |
window.cocept.canvas = new Canvas(); | |
window.cocept.canvas.init(canvas_config); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment