|
|
|
@ -1,7 +1,5 @@ |
|
|
|
import { Vector3 } from "three"; |
|
|
|
export const fps = 60; |
|
|
|
export const mspf = 1000 / fps; |
|
|
|
|
|
|
|
const g = 0.55 / 1000; |
|
|
|
class El { |
|
|
|
constructor(seed) { |
|
|
|
const { data, row, wall, key, index } = seed; |
|
|
|
@ -11,32 +9,28 @@ class El { |
|
|
|
const point = new Vector3(x, y, 0); |
|
|
|
const targetPoint = new Vector3(x, y, 0); |
|
|
|
const spherePoint = sphere ? sphere.point : null; |
|
|
|
let scale = 1; |
|
|
|
if (sphere && point.distanceTo(spherePoint) < sphere.displacement) { |
|
|
|
const direction = point.clone().sub(spherePoint); |
|
|
|
const displacementAmount = sphere.displacement - direction.length(); |
|
|
|
scale = ((1 - displacementAmount / sphere.displacement) / 1) * 0.3 + 0.7; |
|
|
|
direction.setLength(displacementAmount); |
|
|
|
direction.add(point); |
|
|
|
|
|
|
|
point.lerp(direction, 1); // ✨ magic number
|
|
|
|
point.lerp(direction, 0.725); // ✨ magic number
|
|
|
|
} |
|
|
|
|
|
|
|
// and move them back to their original position
|
|
|
|
if (point.distanceTo(targetPoint) > 0.01) { |
|
|
|
point.lerp(targetPoint, 0.27); // ✨ magic number
|
|
|
|
} |
|
|
|
const top = point.y - imgHeight / 2; |
|
|
|
const left = point.x - imgWidth / 2; |
|
|
|
const { url } = data; |
|
|
|
|
|
|
|
const style = { |
|
|
|
position: "absolute", |
|
|
|
width: `${imgWidth}px`, |
|
|
|
height: `${imgHeight}px`, |
|
|
|
top: `${top}px`, |
|
|
|
left: `${left}px`, |
|
|
|
backgroundSize: "cover", |
|
|
|
backgroundImage: `url(${url})`, |
|
|
|
opacity: 1, |
|
|
|
transform: `scale(${scale})`, |
|
|
|
zIndex: row.size - Math.floor(Math.abs(row.size / 2 - row.index)), |
|
|
|
}; |
|
|
|
Object.assign(this, { seed, data, style, key, point }); |
|
|
|
@ -45,10 +39,14 @@ class El { |
|
|
|
} |
|
|
|
|
|
|
|
class Row { |
|
|
|
falled = false; |
|
|
|
q = []; |
|
|
|
fst; |
|
|
|
t1; |
|
|
|
t2; |
|
|
|
constructor({ wall, index }) { |
|
|
|
Object.assign(this, { |
|
|
|
wall, |
|
|
|
q: [], |
|
|
|
minSize: wall.colNum + 1, |
|
|
|
index, |
|
|
|
ratio: Math.random(), |
|
|
|
@ -67,7 +65,13 @@ class Row { |
|
|
|
const { wall } = this; |
|
|
|
const data = wall.getNext(); |
|
|
|
const key = wall.getKey(); |
|
|
|
const seed = { data, row: this, wall, key, index: this.q.length }; |
|
|
|
const seed = { |
|
|
|
data, |
|
|
|
row: this, |
|
|
|
wall, |
|
|
|
key, |
|
|
|
index: this.q.length, |
|
|
|
}; |
|
|
|
const el = new El(seed); |
|
|
|
this.wall.elMap.set(key, el); |
|
|
|
this.q.push(el); |
|
|
|
@ -76,6 +80,22 @@ class Row { |
|
|
|
const el = this.q.shift(); |
|
|
|
this.wall.elMap.delete(el.key); |
|
|
|
} |
|
|
|
update() { |
|
|
|
this.q.forEach((el) => { |
|
|
|
this.wall.elMap.set(el.key, new El(el.seed)); |
|
|
|
}); |
|
|
|
} |
|
|
|
fall(now) { |
|
|
|
if (!this.fst) { |
|
|
|
this.fst = now; |
|
|
|
this.t1 = 0; |
|
|
|
} else { |
|
|
|
this.t2 = now - this.fst; |
|
|
|
this.y += (g * (this.t2 * this.t2 - this.t1 * this.t1)) / 2; |
|
|
|
this.t1 = this.t2; |
|
|
|
} |
|
|
|
this.update(); |
|
|
|
} |
|
|
|
nextFrame(ratioSpeed) { |
|
|
|
this.ratio += ratioSpeed; |
|
|
|
if (this.ratio > 1) { |
|
|
|
@ -86,15 +106,19 @@ class Row { |
|
|
|
}); |
|
|
|
this.ratio -= 1; |
|
|
|
} |
|
|
|
this.q.forEach((el) => { |
|
|
|
this.wall.elMap.set(el.key, new El(el.seed)); |
|
|
|
}); |
|
|
|
this.update(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
export default class Wall { |
|
|
|
#rafID; |
|
|
|
#rafID = null; |
|
|
|
#lastTime; |
|
|
|
falling = false; |
|
|
|
rows = []; |
|
|
|
key = 0; |
|
|
|
time = 0; |
|
|
|
index = 0; |
|
|
|
maxDeltaTime = 1 / 30; |
|
|
|
constructor({ |
|
|
|
items, |
|
|
|
imgWidth, |
|
|
|
@ -112,37 +136,27 @@ export default class Wall { |
|
|
|
const blockHeight = containerHeight / (rowNum - 1); |
|
|
|
const elMap = new Map(); |
|
|
|
Object.assign(this, { |
|
|
|
time: 0, |
|
|
|
index: 0, |
|
|
|
items, |
|
|
|
colNum, |
|
|
|
rowNum, |
|
|
|
elNum, |
|
|
|
imgWidth, |
|
|
|
imgHeight, |
|
|
|
containerWidth, |
|
|
|
containerHeight, |
|
|
|
ratioSpeed, |
|
|
|
rows: [], |
|
|
|
key: 0, |
|
|
|
blockWidth, |
|
|
|
blockHeight, |
|
|
|
elMap, |
|
|
|
onListChange, |
|
|
|
maxDeltaTime: 1 / 30, |
|
|
|
}); |
|
|
|
} |
|
|
|
getNext() { |
|
|
|
const { index, elNum, items } = this; |
|
|
|
if (index === elNum) { |
|
|
|
this.index = 1; |
|
|
|
return items[0]; |
|
|
|
} else { |
|
|
|
this.index++; |
|
|
|
return items[index]; |
|
|
|
} |
|
|
|
const { items } = this; |
|
|
|
return items[Math.floor(Math.random() * items.length)]; |
|
|
|
} |
|
|
|
init() { |
|
|
|
const { rowNum } = this; |
|
|
|
|
|
|
|
for (let i = 0; i < rowNum; i++) { |
|
|
|
const row = new Row({ wall: this, index: i }); |
|
|
|
row.init(); |
|
|
|
@ -154,20 +168,29 @@ export default class Wall { |
|
|
|
} |
|
|
|
animate = () => { |
|
|
|
if (!this.isRunning) return; |
|
|
|
window.requestAnimationFrame(this.animate); |
|
|
|
|
|
|
|
const now = performance.now(); |
|
|
|
const dt = Math.min(this.maxDeltaTime, (now - this.#lastTime) / 1000); |
|
|
|
this.rows.forEach((row) => row.nextFrame(this.ratioSpeed)); |
|
|
|
this.rows.forEach((row) => |
|
|
|
this.falling ? row.fall(now) : row.nextFrame(this.ratioSpeed) |
|
|
|
); |
|
|
|
this.getList(); |
|
|
|
this.time += dt; |
|
|
|
this.#lastTime = now; |
|
|
|
this.#rafID = window.requestAnimationFrame(this.animate); |
|
|
|
}; |
|
|
|
dispose() { |
|
|
|
async dispose() { |
|
|
|
if (this.#rafID === null) return; |
|
|
|
window.cancelAnimationFrame(this.#rafID); |
|
|
|
this.#rafID = null; |
|
|
|
this.isRunning = false; |
|
|
|
this.falling = true; |
|
|
|
await new Promise((resolve) => { |
|
|
|
setTimeout(() => { |
|
|
|
this.falling = false; |
|
|
|
window.cancelAnimationFrame(this.#rafID); |
|
|
|
this.#rafID = null; |
|
|
|
this.isRunning = false; |
|
|
|
resolve(); |
|
|
|
}, Math.sqrt((this.containerHeight * 2) / g) + 500); |
|
|
|
}); |
|
|
|
|
|
|
|
return this; |
|
|
|
} |
|
|
|
getKey() { |
|
|
|
|