<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();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.