<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;
}
This Pen doesn't use any external CSS resources.