-
Notifications
You must be signed in to change notification settings - Fork 1
/
script.js
107 lines (94 loc) · 3.75 KB
/
script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const DEBUG = false;
const REPETITION_COUNT = 3; // number of times each pixel is assigned to a canvas
const NUM_FRAMES = 90;
/**
* Generates the individual subsets of pixels that are animated to create the effect
* @param {HTMLCanvasElement} ctx
* @param {number} count The higher the frame count, the less grouped the pixels will look - Google use 32, but for our elms we use 128 since we have images near the edges
* @return {HTMLCanvasElement[]} Each canvas contains a subset of the original pixels
*/
function generateFrames($canvas, count = 32) {
const { width, height } = $canvas;
const ctx = $canvas.getContext("2d");
const originalData = ctx.getImageData(0, 0, width, height);
const imageDatas = [...Array(count)].map(
(_, i) => ctx.createImageData(width, height));
// assign the pixels to a canvas
// each pixel is assigned to 2 canvas', based on its x-position
for (let x = 0; x < width; ++x) {
for (let y = 0; y < height; ++y) {
for (let i = 0; i < REPETITION_COUNT; ++i) {
const dataIndex = Math.floor(
count * (Math.random() + 2 * x / width) / 3);
const pixelIndex = (y * width + x) * 4;
// copy the pixel over from the original image
for (let offset = 0; offset < 4; ++offset) {
imageDatas[dataIndex].data[pixelIndex + offset] =
originalData.data[pixelIndex + offset];
}
}
}
}
// turn image datas into canvas'
return imageDatas.map(data => {
const $c = $canvas.cloneNode(true);
$c.getContext("2d").putImageData(data, 0, 0);
return $c;
});
}
/**
* Inserts a new element over an old one, hiding the old one
*/
function replaceElementVisually($old, $new) {
const $parent = $old.offsetParent;
$new.style.top = `${$old.offsetTop}px`;
$new.style.left = `${$old.offsetLeft}px`;
$new.style.width = `${$old.offsetWidth}px`;
$new.style.height = `${$old.offsetHeight}px`;
$parent.appendChild($new);
$old.style.visibility = "hidden";
}
/**
* Disintegrates an element
* @param {HTMLElement} $elm
*/
function disintegrate($elm) {
html2canvas($elm).then($canvas => {
// create the container we'll use to replace the element with
const $container = document.createElement("div");
$container.classList.add("disintegration-container");
// setup the frames for animation
const $frames = generateFrames($canvas, NUM_FRAMES);
$frames.forEach(($frame, i) => {
$frame.style.transitionDelay = `${10.35 * i / $frames.length}s`;
$container.appendChild($frame);
});
// then insert them into the DOM over the element
replaceElementVisually($elm, $container);
// then animate them
$container.offsetLeft; // forces reflow, so CSS we apply below does transition
if (!DEBUG) {
// set the values the frame should animate to
// note that this is done after reflow so the transitions trigger
$frames.forEach($frame => {
const randomRadian = 2 * Math.PI * (Math.random() - 0.5);
$frame.style.transform =
`rotate(${15 * (Math.random() - 0.5)}deg) translate(${60 * Math.cos(randomRadian)}px, ${30 * Math.sin(randomRadian)}px)
rotate(${15 * (Math.random() - 0.5)}deg)`;
$frame.style.opacity = 0;
});
} else {
$frames.forEach($frame => {
$frame.style.animation = `debug-pulse 10s ease ${$frame.style.transitionDelay} infinite alternate`;
});
}
});
}
/** === Below is just to bind the module and the DOM == */
[...document.querySelectorAll(".disintegration-target")].forEach($elm => {
$elm.addEventListener("click", () => {
if ($elm.disintegrated) {return;}
$elm.disintegrated = true;
disintegrate($elm);
});
});