<p>Stroke it with your mouse or finger</p>
<div class="wrapper"></div>
<button class="grid">Toggle grid</button>
<!-- <button class="jelly">Jellyfish</button> -->
<button class="water">Water</button>
<button class="boris">Boris</button>
<button class="jelly">Jelly</button>
body{
background: #000500;
color: whitesmoke;
font-family: sans-serif;
text-align: center;
}
canvas{
cursor: pointer;
display:block;
margin: 0 auto 10px;
}
button{
cursor: pointer;
padding: 5px 15px;
}
View Compiled
let canvas,
ctx,
system,
gridSize = 40,
spacing = 10,
width = height = (gridSize - 1) * spacing,
mouseX = 215, // Positioning mouse values so they affect interesting portion of image at start :) 191
mouseY = 139, // 180
imgSrc = new Image(),
imgURL,
drawVertices = false,
first = true;
const msqrt = Math.sqrt;
let jelly = 'https://t4.ftcdn.net/jpg/01/18/42/79/360_F_118427998_5Lgdv3WJdDRZBw5fSxihutyszl6IFPfB.jpg';
let boris = 'https://pyxis.nymag.com/v1/imgs/fab/8e4/02bc278f315ad3b549ea9c62c9d423ee18-boris-johnson-brexit.rsquare.w700.jpg';
let water = 'https://cdn.pixabay.com/photo/2015/11/02/18/32/water-1018808_960_720.jpg';
imgURL = boris;
const ParticleSystem = function(){
this.friction = .98; // 1 MAX
this.mouseRepelDist = 50; // Size of repel circle
this.mouseForce = .02; // Strength of mouse influence
this.springForce = .2; // speed of return to spring point. Tightness
this.numParticles = gridSize * gridSize;
this.particles = [];
this.faces = [];
}
ParticleSystem.prototype.generate = function(){
for(let i=0; i<this.numParticles; i++){
this.particles.push(new Vertices(((i % gridSize) * spacing), (Math.floor(i / gridSize) * spacing), this));
}
}
ParticleSystem.prototype.update = function(){
for(let i=0; i<this.numParticles; i++){
this.particles[i].update();
}
}
const Vertices = function(x, y, parentSystem){
this.x = x;
this.y = y;
this.sx = this.x;
this.sy = this.y;
this.vx = 0;
this.vy = 0;
this.parentSystem = parentSystem;
return this;
}
Vertices.prototype.update = function(){
// apply friction
this.vx *= this.parentSystem.friction;
this.vy *= this.parentSystem.friction;
// stay attached to point
this.vx += (this.sx - this.x) * this.parentSystem.springForce;
this.vy += (this.sy - this.y) * this.parentSystem.springForce;
// avoid the mouse
let dx = mouseX - this.x;
let dy = mouseY - this.y;
let dist = msqrt(dx*dx + dy*dy);
if (dist < this.parentSystem.mouseRepelDist) {
let tx = mouseX - this.parentSystem.mouseRepelDist * dx / dist;
let ty = mouseY - this.parentSystem.mouseRepelDist * dy / dist;
this.vx += (tx - this.x) * this.parentSystem.mouseForce;
this.vy += (ty - this.y) * this.parentSystem.mouseForce;
}
// update position
this.x += this.vx;
this.y += this.vy;
}
const Face = function(vertices, sData){
this.vertices = vertices;
this.sData = sData;
}
Face.prototype.draw = function(){
ctx.drawImage(imgSrc,
this.sData.x, this.sData.y, this.sData.w, this.sData.h,
this.vertices[0].x, this.vertices[0].y,
this.vertices[1].x - this.vertices[0].x, this.vertices[2].y - this.vertices[0].y);
}
function createCanvas(w, h){
let tCanvas = document.createElement('canvas');
tCanvas.width = w;
tCanvas.height = h;
return tCanvas;
}
function handleImageLoad(){
let imgw = (imgSrc.naturalWidth / (gridSize-1));
let imgh = (imgSrc.naturalHeight / (gridSize-1));
for(let i=0; i<system.numParticles - gridSize; i++){
if((gridSize-1) !== i % gridSize){
let verts = [
system.particles[i],
system.particles[i + 1],
system.particles[i + gridSize]
];
let imgData = {
x: (i % gridSize) * imgw,
y: (Math.floor(i / gridSize)) * imgh,
w: imgw,
h: imgh
};
system.faces.push(new Face(verts, imgData));
}
}
if(first){
setUpEvents();
animate();
first = false;
}
}
function setUpEvents(){
/* MOUSE EVENTS */
function getMousePos(canvas, evt) {
let rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
document.addEventListener('mousemove', function(evt) {
let mousePos = getMousePos(canvas, evt);
mouseX = mousePos.x;
mouseY = mousePos.y;
}, false);
/* TOUCH EVENTS */
document.addEventListener('touchstart', function(e){
let touchobj = e.changedTouches[0];
mouseX = touchobj.pageX;
mouseY = touchobj.pageY;
e.preventDefault();
}, false);
document.addEventListener('touchmove', function(e){
let touchobj = e.changedTouches[0];
mouseX = touchobj.pageX;
mouseY = touchobj.pageY;
e.preventDefault();
}, false);
document.querySelector('.grid').addEventListener('click', () => { drawVertices = !drawVertices });
document.querySelector('.water').addEventListener('click', () => { newImage(water) });
document.querySelector('.boris').addEventListener('click', () => { newImage(boris) });
document.querySelector('.jelly').addEventListener('click', () => { newImage(jelly) });
}
function newImage(newImg){
system.faces = [];
imgSrc.src = newImg;
}
function getShareable(){
let hash = window.location.hash;
if(hash !== ''){
if(hash.indexOf('=') === -1){
return;
}
let name = hash.split('=')[0];
if(name === '#imgURL'){
imgURL = hash.split('=')[1];
}
}
}
function init(){
// getShareable(); // Only works in debug
canvas = createCanvas(width, height);
document.querySelector('.wrapper').appendChild(canvas);
ctx = canvas.getContext('2d');
ctx.fillStyle = '#fa4';
/* Define new ParticleSystem and set values */
system = new ParticleSystem();
system.generate();
/* Load image */
imgSrc.onload = handleImageLoad;
imgSrc.src = imgURL;
}
function draw(){
ctx.fillStyle = 'rgba(0,5,0,.1)'; // Smooths out the effect on FireFox
ctx.fillRect(0, 0, width, height);
for(let loop = system.faces.length, i=0; i<loop; i++) {
system.faces[i].draw();
}
if(drawVertices){
ctx.fillStyle = '#fa4';
for(let i=0; i<system.numParticles; i++) {
ctx.beginPath();
ctx.fillRect(system.particles[i].x, system.particles[i].y, 1, 1);
}
}
}
function animate(){
system.update();
draw();
requestAnimationFrame(animate);
}
init();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.