Type to have the input generate pixie dust.
Happy happy joy joy input.
A Pen by Rik Schennink on CodePen.
<input type="text" placeholder="Type for pixie dust..."/> |
Type to have the input generate pixie dust.
Happy happy joy joy input.
A Pen by Rik Schennink on CodePen.
console.clear(); | |
var MAX_LIFE = 50; | |
var canvas = document.querySelector('canvas'); | |
var input = document.querySelector('input'); | |
var field = {} | |
var hasFocus = false; | |
var caret = document.createElement('span'); | |
caret.style.position = 'absolute'; | |
caret.style.left = 0; | |
caret.style.top = 0; | |
caret.style.margin = 0; | |
caret.style.width = 'auto'; | |
caret.style.visibility = 'hidden'; | |
document.body.appendChild(caret); | |
function reposition() { | |
field = input.getBoundingClientRect(); | |
} | |
window.onload = reposition; | |
window.onresize = reposition; | |
reposition(); | |
input.onfocus = function() {hasFocus = true} | |
input.onblur = function() {hasFocus = false} | |
var keys = [8,9,13,16,17,18,27,32,33,34,35,36,37,38,39,40,46,91,93,112,113,114,115,116,117,118,119,120,121,122,123]; | |
function spawnsCharacter(keyCode) { | |
return keys.indexOf(keyCode) === -1; | |
} | |
function burst(intensity) { | |
var behavior = [ | |
this.behavior.force(-.015,-.015), | |
this.behavior.cohesion(50), | |
this.behavior.move() | |
]; | |
var size = 1.25; | |
var force = .7; | |
var lifeMin = 0; | |
var progress = Math.min(field.width, caret.offsetWidth) / field.width; | |
var offset = field.left + (field.width * progress); | |
var rangeMin = Math.max(field.left, offset - 30); | |
var rangeMax = Math.min(field.right, offset + 10); | |
this.spray(intensity,function(){ return [ | |
null,null, | |
Vector.create( | |
Random.between(rangeMin + 10, rangeMax - 20), | |
Random.between(field.top + 15, field.bottom - 15) | |
), | |
Vector.random(force), | |
size + Math.random(), | |
Random.between(lifeMin,0),behavior | |
]}); | |
// top edge | |
this.spray(intensity * .5,function(){ return [ | |
null,null, | |
Vector.create( | |
Random.between(rangeMin, rangeMax), | |
field.top | |
), | |
Vector.random(force), | |
size + Math.random(), | |
Random.between(lifeMin,0),behavior | |
]}); | |
// bottom edge | |
this.spray(intensity * .5,function(){ return [ | |
null,null, | |
Vector.create( | |
Random.between(rangeMin, rangeMax), | |
field.top + field.height | |
), | |
Vector.random(force), | |
size + Math.random(), | |
Random.between(lifeMin,0) | |
,behavior | |
]}); | |
// left edge | |
if (input.value.length === 1) { | |
this.spray(intensity * 2,function(){ return [ | |
null,null, | |
Vector.create( | |
field.left + (Math.random() * 20), | |
Random.between(field.top,field.bottom) | |
), | |
Vector.random(force), | |
size + Math.random(), | |
Random.between(lifeMin,0),behavior | |
]}); | |
} | |
// right edge | |
if (rangeMax == field.right) { | |
this.spray(intensity * 2,function(){ return [ | |
null,null, | |
Vector.create( | |
field.right, | |
Random.between(field.top,field.bottom) | |
), | |
Vector.random(force), | |
size + Math.random(), | |
Random.between(lifeMin,0),behavior | |
]}); | |
} | |
} | |
// start particle simulation | |
simulate( | |
'2d', { | |
init: function() { | |
}, | |
tick: function(particles) { | |
if (!particles){ return; } | |
particles.forEach(function(p){ | |
if (p.life > MAX_LIFE) { | |
this.destroy(p); | |
} | |
}); | |
}, | |
beforePaint: function() { | |
this.clear(); | |
}, | |
paint: function(particle) { | |
var p = particle.position; | |
var s = particle.size; | |
var o = 1 - (particle.life / MAX_LIFE); | |
this.paint.circle(p.x, p.y, s, 'rgba(255,255,255,' + o +')'); | |
this.paint.circle(p.x, p.y, s + 1.5, 'rgba(231,244,255,' + (o * .25) + ')'); | |
// extra | |
var w = 2; | |
var wh = w * .5; | |
var h = 35; | |
var hh = h * .5; | |
this.context.rect(p.x -wh, p.y - hh, w, h); | |
this.context.fillStyle = 'rgba(231,244,255,' + (o * .025) + ')'; | |
this.context.fill(); | |
this.context.closePath(); | |
}, | |
afterPaint: function() { | |
// nothing | |
}, | |
action: function(e) { | |
if (!spawnsCharacter(e.keyCode)) { | |
return; | |
} | |
caret.textContent = input.value; | |
burst.call(this,12); | |
input.classList.add('keyup'); | |
setTimeout(function(){input.classList.remove('keyup')},100); | |
} | |
} | |
); | |
// "simulate" particle simulation logic | |
/** | |
* Constants | |
*/ | |
PI_2 = Math.PI / 2; | |
PI_180 = Math.PI / 180; | |
/** | |
* Random | |
*/ | |
var Random = { | |
between: function(min, max) { | |
return min + (Math.random() * (max - min)); | |
} | |
} | |
/** | |
* 2D Vector Class | |
*/ | |
function Vector(x, y) { | |
this._x = x || 0; | |
this._y = y || 0; | |
} | |
Vector.create = function(x, y) { | |
return new Vector(x, y); | |
}; | |
Vector.add = function(a, b) { | |
return new Vector(a.x + b.x, a.y + b.y); | |
}; | |
Vector.subtract = function(a, b) { | |
return new Vector(a.x - b.x, a.y - b.y); | |
}; | |
Vector.random = function(range) { | |
var v = new Vector(); | |
v.randomize(range); | |
return v; | |
}; | |
Vector.distanceSquared = function(a, b) { | |
var dx = a.x - b.x; | |
var dy = a.y - b.y; | |
return dx * dx + dy * dy; | |
}; | |
Vector.distance = function(a, b) { | |
var dx = a.x - b.x; | |
var dy = a.y - b.y; | |
return Math.sqrt(dx * dx + dy * dy); | |
}; | |
Vector.prototype = { | |
get x() { | |
return this._x; | |
}, | |
get y() { | |
return this._y; | |
}, | |
set x(value) { | |
this._x = value; | |
}, | |
set y(value) { | |
this._y = value; | |
}, | |
get magnitudeSquared() { | |
return this._x * this._x + this._y * this._y; | |
}, | |
get magnitude() { | |
return Math.sqrt(this.magnitudeSquared); | |
}, | |
get angle() { | |
return Math.atan2(this._y, this._x) * 180 / Math.PI; | |
}, | |
clone: function() { | |
return new Vector(this._x, this._y); | |
}, | |
add: function(v) { | |
this._x += v.x; | |
this._y += v.y; | |
}, | |
subtract: function(v) { | |
this._x -= v.x; | |
this._y -= v.y; | |
}, | |
multiply: function(value) { | |
this._x *= value; | |
this._y *= value; | |
}, | |
divide: function(value) { | |
this._x /= value; | |
this._y /= value; | |
}, | |
normalize: function() { | |
var magnitude = this.magnitude; | |
if (magnitude > 0) { | |
this.divide(magnitude); | |
} | |
}, | |
limit: function(treshold) { | |
if (this.magnitude > treshold) { | |
this.normalize(); | |
this.multiply(treshold); | |
} | |
}, | |
randomize: function(amount) { | |
amount = amount || 1; | |
this._x = amount * 2 * (-.5 + Math.random()); | |
this._y = amount * 2 * (-.5 + Math.random()); | |
}, | |
rotate: function(degrees) { | |
var magnitude = this.magnitude; | |
var angle = ((Math.atan2(this._x, this._y) * PI_HALF) + degrees) * PI_180; | |
this._x = magnitude * Math.cos(angle); | |
this._y = magnitude * Math.sin(angle); | |
}, | |
flip: function() { | |
var temp = this._y; | |
this._y = this._x; | |
this._x = temp; | |
}, | |
invert: function() { | |
this._x = -this._x; | |
this._y = -this._y; | |
}, | |
toString: function() { | |
return this._x + ', ' + this._y; | |
} | |
} | |
/** | |
* Particle Class | |
*/ | |
function Particle(id, group, position, velocity, size, life, behavior) { | |
this._id = id || 'default'; | |
this._group = group || 'default'; | |
this._position = position || new Vector(); | |
this._velocity = velocity || new Vector(); | |
this._size = size || 1; | |
this._life = Math.round(life || 0); | |
this._behavior = behavior || []; | |
} | |
Particle.prototype = { | |
get id() { | |
return this._id; | |
}, | |
get group() { | |
return this._group; | |
}, | |
get life() { | |
return this._life; | |
}, | |
get size() { | |
return this._size; | |
}, | |
set size(size) { | |
this._size = size; | |
}, | |
get position() { | |
return this._position; | |
}, | |
get velocity() { | |
return this._velocity; | |
}, | |
update: function(stage) { | |
this._life++; | |
var i = 0; | |
var l = this._behavior.length; | |
for (; i < l; i++) { | |
this._behavior[i].call(stage, this); | |
} | |
}, | |
toString: function() { | |
return 'Particle(' + this._id + ') ' + this._life + ' pos: ' + this._position + ' vec: ' + this._velocity; | |
} | |
} | |
// setup DOM | |
function simulate(dimensions, options) { | |
// private vars | |
var particles = []; | |
var destroyed = []; | |
var update = update || function() {}; | |
var stage = stage || function() {}; | |
var canvas; | |
var context; | |
if (!options) { | |
console.error('"options" object must be defined'); | |
return; | |
} | |
if (!options.init) { | |
console.error('"init" function must be defined'); | |
return; | |
} | |
if (!options.paint) { | |
console.error('"paint" function must be defined'); | |
return; | |
} | |
if (!options.tick) { | |
options.tick = function() {}; | |
} | |
if (!options.beforePaint) { | |
options.beforePaint = function() {}; | |
} | |
if (!options.afterPaint) { | |
options.afterPaint = function() {}; | |
} | |
if (!options.action) { | |
options.action = function() {}; | |
} | |
if (document.readyState === 'interactive') { | |
setup(); | |
} else { | |
document.addEventListener('DOMContentLoaded', setup); | |
} | |
// resizes canvas to fit window dimensions | |
function fitCanvas() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
} | |
// create canvas for drawing | |
function setup() { | |
// create | |
canvas = document.createElement('canvas'); | |
document.body.appendChild(canvas); | |
// correct canvas size on window resize | |
window.addEventListener('resize', fitCanvas); | |
// go | |
go(); | |
} | |
// canvas has been attached, let's go! | |
function go() { | |
// set initial canvas size | |
fitCanvas(); | |
// get context for drawing | |
context = canvas.getContext(dimensions); | |
// simulation update loop | |
function act() { | |
// update particle states | |
var i = 0; | |
var l = particles.length; | |
var p; | |
for (; i < l; i++) { | |
particles[i].update(this); | |
} | |
// clean destroyed particles | |
while (p = destroyed.pop()) { | |
do { | |
// has not been found in destroyed array? | |
if (p !== particles[i]) { | |
continue; | |
} | |
// remove particle | |
particles.splice(i, 1); | |
} while (i-- >= 0) | |
} | |
// repaint context | |
options.beforePaint.call(this); | |
// repaint particles | |
i = 0; | |
l = particles.length; | |
for (; i < l; i++) { | |
options.paint.call(this, particles[i]); | |
} | |
// after particles have been painted | |
options.afterPaint.call(this); | |
} | |
function tick() { | |
// call update method, this allows for inserting particles later on | |
options.tick.call(this, particles); | |
// update particles here | |
act(); | |
// on to the next frame | |
window.requestAnimationFrame(tick); | |
} | |
/** | |
* API | |
**/ | |
function clear() { | |
context.clearRect(0, 0, canvas.width, canvas.height); | |
} | |
function destroy(particle) { | |
destroyed.push(particle); | |
} | |
function add(id, group, position, velocity, size, life, behavior) { | |
particles.push(new Particle(id, group, position, velocity, size, life, behavior)); | |
} | |
function spray(amount, config) { | |
var i = 0; | |
for (; i < amount; i++) { | |
add.apply(this, config()); | |
} | |
} | |
function debug(particle) { | |
this.paint.circle( | |
particle.position.x, | |
particle.position.y, | |
particle.size, | |
'rgba(255,0,0,.75)' | |
); | |
context.beginPath(); | |
context.moveTo(particle.position.x, particle.position.y); | |
context.lineTo(particle.position.x + (particle.velocity.x * 10), particle.position.y + (particle.velocity.y * 10)); | |
context.strokeStyle = 'rgba(255,0,0,.1)'; | |
context.stroke(); | |
context.closePath(); | |
}; | |
this.clear = clear; | |
this.destroy = destroy; | |
this.add = add; | |
this.spray = spray; | |
this.debug = debug; | |
this.paint = { | |
circle: function(x, y, size, color) { | |
context.beginPath(); | |
context.arc(x, y, size, 0, 2 * Math.PI, false); | |
context.fillStyle = color; | |
context.fill(); | |
}, | |
square: function(x, y, size, color) { | |
context.beginPath(); | |
context.rect(x - (size * .5), y - (size * .5), size, size); | |
context.fillStyle = color; | |
context.fill(); | |
} | |
} | |
this.behavior = { | |
cohesion: function(range, speed) { | |
range = Math.pow(range || 100, 2); | |
speed = speed || .001; | |
return function(particle) { | |
var center = new Vector(); | |
var i = 0; | |
var l = particles.length; | |
var count = 0; | |
if (l <= 1) { | |
return; | |
} | |
for (; i < l; i++) { | |
// don't use self in group | |
if (particles[i] === particle || Vector.distanceSquared(particles[i].position, particle.position) > range) { | |
continue; | |
} | |
center.add(Vector.subtract(particles[i].position, particle.position)); | |
count++; | |
} | |
if (count > 0) { | |
center.divide(count); | |
center.normalize(); | |
center.multiply(particle.velocity.magnitude); | |
center.multiply(.05); | |
} | |
particle.velocity.add(center); | |
} | |
}, | |
separation: function(distance) { | |
var distance = Math.pow(distance || 25, 2); | |
return function(particle) { | |
var heading = new Vector(); | |
var i = 0; | |
var l = particles.length; | |
var count = 0; | |
var diff; | |
if (l <= 1) { | |
return; | |
} | |
for (; i < l; i++) { | |
// don't use self in group | |
if (particles[i] === particle || Vector.distanceSquared(particles[i].position, particle.position) > distance) { | |
continue; | |
} | |
// stay away from neighbours | |
diff = Vector.subtract(particle.position, particles[i].position); | |
diff.normalize(); | |
heading.add(diff); | |
count++; | |
} | |
if (count > 0) { | |
// get average | |
heading.divide(count); | |
// make same length as current velocity (so particle won't speed up) | |
heading.normalize(); | |
heading.multiply(particle.velocity.magnitude); | |
// limit force to make particle movement smoother | |
heading.limit(.1); | |
} | |
particle.velocity.add(heading); | |
} | |
}, | |
alignment: function(range) { | |
range = Math.pow(range || 100, 2); | |
return function(particle) { | |
var i = 0; | |
var l = particles.length; | |
var count = 0; | |
var heading = new Vector(); | |
if (l <= 1) { | |
return; | |
} | |
for (; i < l; i++) { | |
// don't use self in group also don't align when out of range | |
if (particles[i] === particle || Vector.distanceSquared(particles[i].position, particle.position) > range) { | |
continue; | |
} | |
heading.add(particles[i].velocity); | |
count++; | |
} | |
if (count > 0) { | |
heading.divide(count); | |
heading.normalize(); | |
heading.multiply(particle.velocity.magnitude); | |
// limit | |
heading.multiply(.1); | |
} | |
particle.velocity.add(heading); | |
} | |
}, | |
move: function() { | |
return function(particle) { | |
particle.position.add(particle.velocity); | |
// handle collisions? | |
} | |
}, | |
eat: function(food) { | |
food = food || []; | |
return function(particle) { | |
var i = 0; | |
var l = particles.length; | |
var prey; | |
for (; i < l; i++) { | |
prey = particles[i]; | |
// can't eat itself, also, needs to be tasty | |
if (prey === particle || food.indexOf(prey.group) === -1) { | |
continue; | |
} | |
// calculate force vector | |
if (Vector.distanceSquared(particle.position, neighbour.position) < 2 && particle.size >= neighbour.size) { | |
particle.size += neighbour.size; | |
destroy(neighbour); | |
} | |
} | |
} | |
}, | |
force: function(x, y) { | |
return function(particle) { | |
particle.velocity.x += x; | |
particle.velocity.y += y; | |
} | |
}, | |
limit: function(treshold) { | |
return function(particle) { | |
particle.velocity.limit(treshold); | |
} | |
}, | |
attract: function(forceMultiplier, groups) { | |
forceMultiplier = forceMultiplier || 1; | |
groups = groups || []; | |
return function(particle) { | |
// attract other particles | |
var totalForce = new Vector(0, 0); | |
var force = new Vector(0, 0); | |
var i = 0; | |
var l = particles.length; | |
var distance; | |
var pull; | |
var attractor; | |
var grouping = groups.length; | |
for (; i < l; i++) { | |
attractor = particles[i]; | |
// can't be attracted by itself or mismatched groups | |
if (attractor === particle || (grouping && groups.indexOf(attractor.group) === -1)) { | |
continue; | |
} | |
// calculate force vector | |
force.x = attractor.position.x - particle.position.x; | |
force.y = attractor.position.y - particle.position.y; | |
distance = force.magnitude; | |
force.normalize(); | |
// the bigger the attractor the more force | |
force.multiply(attractor.size / distance); | |
totalForce.add(force); | |
} | |
totalForce.multiply(forceMultiplier); | |
particle.velocity.add(totalForce); | |
} | |
}, | |
wrap: function(margin) { | |
return function(particle) { | |
// move around when particle reaches edge of screen | |
var position = particle.position; | |
var radius = particle.size * .5; | |
if (position.x + radius > canvas.width + margin) { | |
position.x = radius; | |
} | |
if (position.y + radius > canvas.height + margin) { | |
position.y = radius; | |
} | |
if (position.x - radius < -margin) { | |
position.x = canvas.width - radius; | |
} | |
if (position.y - radius < -margin) { | |
position.y = canvas.height - radius; | |
} | |
} | |
}, | |
reflect: function() { | |
return function(particle) { | |
// bounce from edges | |
var position = particle.position; | |
var velocity = particle.velocity; | |
var radius = particle.size * .5; | |
if (position.x + radius > canvas.width) { | |
velocity.x = -velocity.x; | |
} | |
if (position.y + radius > canvas.height) { | |
velocity.y = -velocity.y; | |
} | |
if (position.x - radius < 0) { | |
velocity.x = -velocity.x; | |
} | |
if (position.y - radius < 0) { | |
velocity.y = -velocity.y; | |
} | |
} | |
}, | |
edge: function(action) { | |
return function(particle) { | |
var position = particle.position; | |
var velocity = particle.velocity; | |
var radius = particle.size * .5; | |
if (position.x + radius > canvas.width) { | |
action(particle); | |
} | |
if (position.y + radius > canvas.height) { | |
action(particle); | |
} | |
if (position.x - radius < 0) { | |
action(particle); | |
} | |
if (position.y - radius < 0) { | |
action(particle); | |
} | |
} | |
} | |
} | |
// public | |
Object.defineProperties(this, { | |
'particles': { | |
get: function() { | |
return particles; | |
} | |
}, | |
'width': { | |
get: function() { | |
return canvas.width; | |
} | |
}, | |
'height': { | |
get: function() { | |
return canvas.height; | |
} | |
}, | |
'context': { | |
get: function() { | |
return context; | |
} | |
} | |
}); | |
// call init method so the scene can be setup | |
options.init.call(this) | |
// start ticking | |
tick(); | |
// start listening to events | |
var self = this; | |
document.addEventListener('keyup', function(e) { | |
options.action.call(self, e); | |
}); | |
} | |
}; |
<script src="https://codepen.io/rikschennink/pen/amxZqR"></script> |
span, | |
input { | |
border:2px solid rgba(255,255,255,.5); | |
font-size:1.75em; | |
padding:.25em .5em .3125em; | |
color:rgba(255,255,255,.5); | |
border-radius:.25em; | |
background:transparent; | |
transition:all .100s; | |
&:focus{ | |
outline:none; | |
color:rgba(255,255,255,.75); | |
border-color:rgba(255,255,255,.75); | |
} | |
&.keyup { | |
color:white; | |
border-color:white; | |
text-shadow:0 0 .125em white; | |
box-shadow:0 0 .25em white,inset 0 0 .25em white; | |
} | |
} | |
canvas { | |
position:absolute; | |
left:0; | |
right:0; | |
top:0; | |
bottom:0; | |
pointer-events:none; | |
} | |
input { | |
font-family: "Arial Rounded MT Bold","Helvetica Rounded",Arial,sans-serif; | |
} | |
$placeholder-color:rgba(255,255,255,.5); | |
::-webkit-input-placeholder { | |
color:$placeholder-color; | |
text-shadow:0 0 .125em transparent; | |
transition:all .25s; | |
} | |
input:focus::-webkit-input-placeholder { | |
opacity:.5; | |
} | |
::-moz-placeholder { | |
color:$placeholder-color; | |
text-shadow:0 0 .125em transparent; | |
transition:all .25s; | |
} | |
input:focus::-moz-placeholder { | |
opacity:.5; | |
} | |
:-ms-input-placeholder { | |
color:$placeholder-color; | |
text-shadow:0 0 .125em transparent; | |
transition:all .25s; | |
} | |
input:focus:-ms-input-placeholder { | |
opacity:.5; | |
} | |
html,body { | |
height:100%; | |
overflow:hidden; | |
} | |
html { | |
background:#333 url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/68397/forest-fly-agaric-fog-moss-fliegenpilz.jpg) no-repeat center bottom; | |
background-size:cover; | |
} | |
body { | |
box-shadow:inset 0 0 5em #000; | |
} | |
input { | |
position:absolute; | |
box-sizing:border-box; | |
bottom: 4em; | |
left:50%; | |
width:11em; | |
margin-left:-5.5em; | |
} |