-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathanimTest.html
182 lines (146 loc) · 4.97 KB
/
animTest.html
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<html>
<style>
body {
margin: 0;
overflow: clip;
width: 100vw;
height: 100vh;
}
</style>
<body>
<script>
// animate a bunch of divs to random positions and scales with spring physics, leveraging JS keyframes api to generate frames
const msPerAnimationStep = 1000 / 120
const BASE_WIDTH = 1536
const BASE_HEIGHT = 768
const SCALE = 0.5
const WIDTH = BASE_WIDTH * SCALE
const HEIGHT = BASE_HEIGHT * SCALE
function springStep2(config) {
const t = msPerAnimationStep / 1000 // convert to seconds for the physics equation
const { pos, dest, v, k, b } = config
// Calculate the initial force (acceleration)
const Fspring = -k * (pos - dest) // Spring stiffness, in kg / s^2
const Fdamper = -b * v // Damping, in kg / s
const a = Fspring + Fdamper // a needs to be divided by mass, but we'll assume mass of 1. Adjust k and b to change spring curve instead
// Perform the first half of the velocity Verlet integration
const newV_half = v + 0.5 * a * t
// Update the position using the half-step velocity
const newPos = pos + newV_half * t
// Calculate the new force (acceleration) at the new position
const newFspring = -k * (newPos - dest)
const newFdamper = -b * newV_half
const newA = newFspring + newFdamper
// Complete the velocity Verlet integration
const newV = newV_half + 0.5 * newA * t
config.pos = newPos
config.v = newV
}
function isDone(s) {
return Math.abs(s.pos - s.dest) < 0.01 && Math.abs(s.v) < 0.01
}
function mix(a, b, pct) {
return a + (b - a) * pct
}
function makeScheduler(render) {
let scheduledRender = false
const scheduleRender = () => {
if (scheduledRender) return
scheduledRender = true
requestAnimationFrame(function renderAndMaybeScheduleAnotherRender(now) { // eye-grabbing name. No "(anonymous)" function in the debugger & profiler
scheduledRender = false
render(now)
})
}
return scheduleRender
}
function getInterpolationFrames(animation, time) {
const keyframes = animation.effect.getKeyframes();
const duration = animation.effect.getTiming().duration;
const progress = time / duration;
// Find the surrounding keyframes
const frameIndex = Math.floor(progress * (keyframes.length - 1));
const nextFrameIndex = Math.min(frameIndex + 1, keyframes.length - 1);
return {
frame1: keyframes[frameIndex],
frame2: keyframes[nextFrameIndex],
subProgress: (progress * (keyframes.length - 1)) % 1
};
}
// === end generic utils
const springs = Array.from({ length: 40 }, () => {
const div = document.createElement('div')
document.body.appendChild(div)
div.style.width = `${WIDTH}px`;
div.style.height = `${HEIGHT}px`;
div.style.position = "absolute";
div.style.backgroundSize = "cover";
div.style.transformOrigin = 'top left'
return {
node: div,
x: { pos: 0, dest: 0, v: 0, k: 160, b: 12 },
y: { pos: 0, dest: 0, v: 0, k: 160, b: 12 },
scale: { pos: 1, dest: 1, v: 0, k: 160, b: 12 }
};
});
function animateToNewPosition() {
const containerW = document.documentElement.clientWidth
const containerH = document.documentElement.clientHeight
springs.forEach(spring => {
const frames = []
// Get current values from active animation
const activeAnimation = spring.node.getAnimations()[0];
if (activeAnimation && activeAnimation.currentTime !== null) {
// Get the current interpolated values from the animation
const { frame1, frame2, subProgress } = getInterpolationFrames(activeAnimation, activeAnimation.currentTime);
// Update spring state to match the current interpolated position
const currentTransform = frame1.transform; // or interpolate between frame1 and frame2 if needed
// TODO: Parse the transform to update spring.x.pos, spring.y.pos, spring.scale.pos
activeAnimation.cancel();
}
spring.x.dest = Math.random() * containerW;
spring.y.dest = Math.random() * containerH;
spring.scale.dest = 0.5 + Math.random() * 0.5;
for (let i = 0; i < 240; i++) {
springStep2(spring.x)
springStep2(spring.y)
springStep2(spring.scale)
frames.push({
transform: `translate3d(${spring.x.pos}px, ${spring.y.pos}px, 0) scale3d(${spring.scale.pos}, ${spring.scale.pos}, 1)`
})
}
const options = {
duration: 2000,
fill: 'forwards'
};
spring.node.animate(frames, options);
});
}
// Initial animation
const bgImage = new Image();
bgImage.onload = () => {
console.log('loaded')
springs.forEach(spring => {
spring.node.style.backgroundImage = "url('https://i.mj.run/81d2bba0-2b79-435d-b38f-043635bb2b96/0_1.jpeg')"
});
animateToNewPosition();
};
bgImage.src = 'https://i.mj.run/81d2bba0-2b79-435d-b38f-043635bb2b96/0_1.jpeg';
const events = {
pointerdown: null,
}
window.addEventListener('pointerdown', (e) => {
pointerdown = e
scheduleRender()
});
const scheduleRender = makeScheduler(render)
function render(now) {
if (pointerdown != null) {
animateToNewPosition()
}
pointerdown = null
scheduleRender()
}
</script>
</body>
</html>