<div class="fof">
<canvas></canvas>
</div>
body {
background: #000;
overflow: hidden;
}
.fof {
margin: 10px 0;
}
.fof canvas,
.fof img {
position: relative;
width: 100%;
height: 100%;
margin-bottom: 20px;
z-index: 1;
background: #000;
}
// The 404 page from my site http://hakim.se/404
(function(){
var DISPLAY_WIDTH = window.innerWidth,
DISPLAY_HEIGHT = window.innerHeight,
DISPLAY_DURATION = 10;
var mouse = { x: 0, y: 0 },
container,
canvas,
context,
startTime,
eyes;
function initialize() {
container = document.querySelector( '.fof' );
canvas = document.querySelector( '.fof>canvas' );
if( canvas ) {
canvas.width = DISPLAY_WIDTH;
canvas.height = DISPLAY_HEIGHT;
context = canvas.getContext( '2d' );
window.addEventListener( 'mousemove', function( event ) {
mouse.x = event.clientX;
mouse.y = event.clientY;
}, false );
eyes = [
new Eye( canvas, 0.19, 0.80, 0.88, 0.31 ),
new Eye( canvas, 0.10, 0.54, 0.84, 0.32 ),
new Eye( canvas, 0.81, 0.13, 0.63, 0.33 ),
new Eye( canvas, 0.89, 0.19, 0.58, 0.34 ),
new Eye( canvas, 0.40, 0.08, 0.97, 0.35 ),
new Eye( canvas, 0.64, 0.74, 0.57, 0.36 ),
new Eye( canvas, 0.41, 0.89, 0.56, 0.37 ),
new Eye( canvas, 0.92, 0.89, 0.75, 0.38 ),
new Eye( canvas, 0.27, 0.20, 0.87, 0.39 ),
new Eye( canvas, 0.17, 0.46, 0.68, 0.41 ),
new Eye( canvas, 0.71, 0.29, 0.93, 0.42 ),
new Eye( canvas, 0.84, 0.46, 0.54, 0.43 ),
new Eye( canvas, 0.93, 0.35, 0.63, 0.44 ),
new Eye( canvas, 0.77, 0.82, 0.85, 0.45 ),
new Eye( canvas, 0.36, 0.74, 0.90, 0.46 ),
new Eye( canvas, 0.13, 0.24, 0.85, 0.47 ),
new Eye( canvas, 0.58, 0.20, 0.77, 0.48 ),
new Eye( canvas, 0.55, 0.84, 0.87, 0.50 ),
new Eye( canvas, 0.50, 0.50, 5.00, 0.10 ),
];
startTime = Date.now();
animate();
}
}
function animate() {
// The number of seconds that have passed since initialization
var seconds = ( Date.now() - startTime ) / 1000;
// Out with the old ...
context.clearRect( 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT );
// ... in with the new
for( var i = 0, len = eyes.length; i < len; i++ ) {
var eye = eyes[i];
if( seconds > eye.activationTime * DISPLAY_DURATION ) {
eye.activate();
};
eye.update( mouse );
}
requestAnimFrame( animate );
}
setTimeout( initialize, 1 );
})();
function Eye( canvas, x, y, scale, time ) {
this.canvas = canvas;
this.context = this.canvas.getContext( '2d' )
// The time at which this eye will come alive
this.activationTime = time;
// The speed at which the iris follows the mouse
this.irisSpeed = 0.01 + ( Math.random() * 0.2 ) / scale;
// The speed at which the eye opens and closes
this.blinkSpeed = 0.2 + ( Math.random() * 0.2 );
this.blinkInterval = 5000 + 5000 * ( Math.random() );
// Timestamp of the last blink
this.blinkTime = Date.now();
this.scale = scale;
this.size = 70 * scale;
this.x = x * canvas.width;
this.y = y * canvas.height + ( this.size * 0.15 );
this.iris = {
x: this.x,
y: this.y - ( this.size * 0.1 ),
size: this.size * 0.2
};
this.pupil = {
width: 2 * scale,
height: this.iris.size * 0.75
};
this.exposure = {
top: 0.1 + ( Math.random() * 0.3 ),
bottom: 0.5 + ( Math.random() * 0.3 ),
current: 0,
target: 1
};
// Affects the amount of inner shadow
this.tiredness = ( 0.5 - this.exposure.top ) + 0.1;
this.isActive = false;
this.activate = function() {
this.isActive = true;
}
this.update = function( mouse ) {
if( this.isActive === true ) {
this.render( mouse );
}
}
this.render = function( mouse ) {
var time = Date.now();
if( this.exposure.current < 0.012 ) {
this.exposure.target = 1;
}
else if( time - this.blinkTime > this.blinkInterval ) {
this.exposure.target = 0;
this.blinkTime = time;
}
this.exposure.current += ( this.exposure.target - this.exposure.current ) * this.blinkSpeed;
// Eye left/right
var el = { x: this.x - ( this.size * 0.8 ), y: this.y - ( this.size * 0.1 ) };
var er = { x: this.x + ( this.size * 0.8 ), y: this.y - ( this.size * 0.1 ) };
// Eye top/bottom
var et = { x: this.x, y: this.y - ( this.size * ( 0.5 + ( this.exposure.top * this.exposure.current ) ) ) };
var eb = { x: this.x, y: this.y - ( this.size * ( 0.5 - ( this.exposure.bottom * this.exposure.current ) ) ) };
// Eye inner shadow top
var eit = { x: this.x, y: this.y - ( this.size * ( 0.5 + ( ( 0.5 - this.tiredness ) * this.exposure.current ) ) ) };
// Eye iris
var ei = { x: this.x, y: this.y - ( this.iris.size ) };
// Offset the iris depending on mouse position
var eio = {
x: ( mouse.x - ei.x ) / ( window.innerWidth - ei.x ),
y: ( mouse.y ) / ( window.innerHeight )
};
// Apply the iris offset
ei.x += eio.x * 16 * Math.max( 1, this.scale * 0.4 );
ei.y += eio.y * 10 * Math.max( 1, this.scale * 0.4 );
this.iris.x += ( ei.x - this.iris.x ) * this.irisSpeed;
this.iris.y += ( ei.y - this.iris.y ) * this.irisSpeed;
// Eye fill drawing
this.context.fillStyle = 'rgba(255,255,255,1.0)';
this.context.strokeStyle = 'rgba(100,100,100,1.0)';
this.context.beginPath();
this.context.lineWidth = 3;
this.context.lineJoin = 'round';
this.context.moveTo( el.x, el.y );
this.context.quadraticCurveTo( et.x, et.y, er.x, er.y );
this.context.quadraticCurveTo( eb.x, eb.y, el.x, el.y );
this.context.closePath();
this.context.stroke();
this.context.fill();
// Iris
this.context.save();
this.context.globalCompositeOperation = 'source-atop';
this.context.translate(this.iris.x*0.1,0);
this.context.scale(0.9,1);
this.context.strokeStyle = 'rgba(0,0,0,0.5)';
this.context.fillStyle = 'rgba(130,50,90,0.9)';
this.context.lineWidth = 2;
this.context.beginPath();
this.context.arc(this.iris.x, this.iris.y, this.iris.size, 0, Math.PI*2, true);
this.context.fill();
this.context.stroke();
this.context.restore();
// Iris inner
this.context.save();
this.context.shadowColor = 'rgba(255,255,255,0.5)';
this.context.shadowOffsetX = 0;
this.context.shadowOffsetY = 0;
this.context.shadowBlur = 2 * this.scale;
this.context.globalCompositeOperation = 'source-atop';
this.context.translate(this.iris.x*0.1,0);
this.context.scale(0.9,1);
this.context.fillStyle = 'rgba(255,255,255,0.2)';
this.context.beginPath();
this.context.arc(this.iris.x, this.iris.y, this.iris.size * 0.7, 0, Math.PI*2, true);
this.context.fill();
this.context.restore();
// Pupil
this.context.save();
this.context.globalCompositeOperation = 'source-atop';
this.context.fillStyle = 'rgba(0,0,0,0.9)';
this.context.beginPath();
this.context.moveTo( this.iris.x, this.iris.y - ( this.pupil.height * 0.5 ) );
this.context.quadraticCurveTo( this.iris.x + ( this.pupil.width * 0.5 ), this.iris.y, this.iris.x, this.iris.y + ( this.pupil.height * 0.5 ) );
this.context.quadraticCurveTo( this.iris.x - ( this.pupil.width * 0.5 ), this.iris.y, this.iris.x, this.iris.y - ( this.pupil.height * 0.5 ) );
this.context.fill();
this.context.restore();
this.context.save();
this.context.shadowColor = 'rgba(0,0,0,0.9)';
this.context.shadowOffsetX = 0;
this.context.shadowOffsetY = 0;
this.context.shadowBlur = 10;
// Eye top inner shadow
this.context.fillStyle = 'rgba(120,120,120,0.2)';
this.context.beginPath();
this.context.moveTo( el.x, el.y );
this.context.quadraticCurveTo( et.x, et.y, er.x, er.y );
this.context.quadraticCurveTo( eit.x, eit.y, el.x, el.y );
this.context.closePath();
this.context.fill();
this.context.restore();
}
}
// shim with setTimeout fallback from http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.