<div class="content">
  <button id="start-btn" data-target="#image">Snap!</button>
  <div id="image">
    <img src="https://s3.us-east-2.amazonaws.com/codepen-frali/ironman.png" height="479">
  </div>
</div>
:root {
  --quickFade-time: 1.5s;
  --fade-time: 2s;
  --blur-time: 2s;
  --blur-amount: 0.188em;
}
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
}

.content {
  position: relative;
}
#image {
  min-width: 470px;
  min-height: 479px;
}

#start-btn {
  cursor: pointer;
  position: absolute;
  top: 0;
  left: -250px;
  font-size: 36px;
  padding: 20px 40px 20px 80px ;
  margin-top: 30px;
  border-radius: 10px;
  background:#eee url("https://s3.us-east-2.amazonaws.com/codepen-frali/thanos-snap.png") 15px no-repeat;
  background-size: 50px;
}

.dust {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  filter: blur(.05em);
}

.quickFade{
  animation: fadeout var(--quickFade-time) linear forwards;
}

.fade {
  animation: fadeout var(--fade-time) linear forwards;
}

@keyframes fadeout {
  0% { opacity: 1}
  100% { opacity: 0; }
}

.blur {
  animation: fadeblur var(--blur-time) ease-in forwards;
}

@keyframes fadeblur {
  0% { 
    opacity: 1;
    filter: blur(0.05em);
  }
  80% {
    filter: blur(var(--blur-amount));
  }
  100% { 
    opacity: 0;
  }
}
// GUI CONTROLS
const controls = {
  duration: 2,
  blur: 3,
  windX: 200,
  windY: -100,
  scatter: 25,
  detail: 20
}
const gui = new dat.GUI()
const durationGUI = gui.add(controls,"duration",1,10).step(.1);
const blurGUI = gui.add(controls,"blur",1,16).step(0.1);
gui.add(controls,"windX",-600,600).step(10);
gui.add(controls,"windY",-600,600).step(10);
gui.add(controls,"scatter",0,100).step(5);
gui.add(controls,"detail",10,70).step(1);

durationGUI.onChange((e) => {
  updateCSSvars('fade-time', round(e,2), 's');
  updateCSSvars('blur-time', round(e,2), 's');
  updateCSSvars('quickFade-time', round(e-e*(e*.1),2), 's');
});

blurGUI.onChange((e) => {
  updateCSSvars('blur-amount', round(e/16,3), 'em');
})


const updateCSSvars = (key, value, units) => {
  document.documentElement.style.setProperty(`--${key}`, `${round(value,2)}${units}`);
}

const round = (value, precision) => {
  const multiplier = Math.pow(10, precision);
  return Math.round(value * multiplier) / multiplier;
}
// END: GUI CONTROLS

document.getElementById('start-btn').addEventListener('click',(e) => {
  const target = document.querySelector(e.target.dataset.target);

  if(target.style.display === "none") {
    target.style.display = "";
  };

  Array.from(target.children).map(el => {
    el.classList.remove('quickFade');
  });

  snap(target);
});

const snap = (target) => {
  html2canvas(target,{
    allowTaint: false,
    useCORS: true,
    backgroundColor: 'transparent',
    scale: 1
  }).then(canvas => {
    //capture all div data as image
    const canvasCount = controls.detail;
    const ctx = canvas.getContext("2d");
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const pixelArr = imageData.data;
    const data = imageData.data.slice(0).fill(0);
    let imageDataArray = Array.from({length: canvasCount}, e => data.slice(0));

    //put pixel info to imageDataArray (Weighted Distributed)
    for (let i = 0; i < pixelArr.length; i+=4) {
      //find the highest probability canvas the pixel should be in
      const p = Math.floor((i/pixelArr.length) * canvasCount);
      const a = imageDataArray[weightedRandomDistrib(p,canvasCount)];
      
      // assign RGBA values from image to dust canvas
      a[i] = pixelArr[i];
      a[i+1] = pixelArr[i+1];
      a[i+2] = pixelArr[i+2];
      a[i+3] = pixelArr[i+3];
    }

    //create canvas for each imageData and append to target element
    for (let i = 0; i < canvasCount; i++) {
      const c = newCanvasFromImageData(imageDataArray[i], canvas.width, canvas.height);
      c.classList.add("dust");
      // c.style.zIndex = canvasCount - i;
      
      const d = controls.duration * 1000
      
      //apply animation
      setTimeout(() => {
        animateTransform(c,controls.windX,controls.windY,chance.integer({ min: -controls.scatter, max: controls.scatter }),d);
        c.classList.add('blur');
        setTimeout(() => {
          c.remove();
        }, d + 50);
      }, 65 * i + 4 * controls.duration);
      
      //append dust to target
      target.appendChild(c);
      
    }
    
    Array.from(target.querySelectorAll(':not(.dust)')).map(el => {
      el.classList.add('quickFade');
    });
      
  }).catch(function(error) {
    console.log(error);
  });
}

const weightedRandomDistrib = (peak,count) => {
  const prob = [], seq = [];
  for(let i=0;i<count;i++) {
    prob.push(Math.pow(count-Math.abs(peak-i),controls.detail/2));
    seq.push(i);
  }
  return chance.weighted(seq, prob);
}

const animateTransform = (elem,sx,sy,angle,duration) => {
  const td = tx = ty =0;
  elem.animate([
    // keyframes
    { transform: 'rotate(0) translate(0, 0)' },
    { transform: 'rotate(' + angle + 'deg) translate(' + sx + 'px,'+ sy +'px)' }
  ], { 
    // timing options
    duration: duration,
    easing: 'ease-in'
  });
}

const newCanvasFromImageData = (imageDataArray ,w , h) => {
  const canvas = document.createElement('canvas');
  canvas.width = w;
  canvas.height = h;
  tempCtx = canvas.getContext("2d");
  tempCtx.putImageData(new ImageData(imageDataArray, w , h), 0, 0);
      
  return canvas;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://html2canvas.hertzen.com/dist/html2canvas.min.js
  2. https://chancejs.com/chance.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.1/dat.gui.js