You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
4.5 KiB
183 lines
4.5 KiB
import { Vector3 } from "three";
|
|
export const fps = 60;
|
|
export const mspf = 1000 / fps;
|
|
|
|
class El {
|
|
constructor(seed) {
|
|
const { data, row, wall, key, index } = seed;
|
|
const { imgWidth, blockWidth, imgHeight, sphere } = wall;
|
|
const { ratio, y } = row;
|
|
let x = index * blockWidth + blockWidth / 2 - ratio * blockWidth;
|
|
const point = new Vector3(x, y, 0);
|
|
const targetPoint = new Vector3(x, y, 0);
|
|
const spherePoint = sphere ? sphere.point : null;
|
|
if (sphere && point.distanceTo(spherePoint) < sphere.displacement) {
|
|
const direction = point.clone().sub(spherePoint);
|
|
const displacementAmount = sphere.displacement - direction.length();
|
|
direction.setLength(displacementAmount);
|
|
direction.add(point);
|
|
|
|
point.lerp(direction, 1); // ✨ 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,
|
|
zIndex: row.size - Math.floor(Math.abs(row.size / 2 - row.index)),
|
|
};
|
|
Object.assign(this, { seed, data, style, key, point });
|
|
}
|
|
update() {}
|
|
}
|
|
|
|
class Row {
|
|
constructor({ wall, index }) {
|
|
Object.assign(this, {
|
|
wall,
|
|
q: [],
|
|
minSize: wall.colNum + 1,
|
|
index,
|
|
ratio: Math.random(),
|
|
y: index * wall.blockHeight + wall.blockHeight / 2,
|
|
});
|
|
}
|
|
get size() {
|
|
return this.q.length;
|
|
}
|
|
init() {
|
|
while (this.size < this.minSize) {
|
|
this.push();
|
|
}
|
|
}
|
|
push() {
|
|
const { wall } = this;
|
|
const data = wall.getNext();
|
|
const key = wall.getKey();
|
|
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);
|
|
}
|
|
shift() {
|
|
const el = this.q.shift();
|
|
this.wall.elMap.delete(el.key);
|
|
}
|
|
nextFrame(ratioSpeed) {
|
|
this.ratio += ratioSpeed;
|
|
if (this.ratio > 1) {
|
|
this.shift();
|
|
this.push();
|
|
this.q.forEach((el) => {
|
|
el.seed.index--;
|
|
});
|
|
this.ratio -= 1;
|
|
}
|
|
this.q.forEach((el) => {
|
|
this.wall.elMap.set(el.key, new El(el.seed));
|
|
});
|
|
}
|
|
}
|
|
|
|
export default class Wall {
|
|
#rafID;
|
|
#lastTime;
|
|
constructor({
|
|
items,
|
|
imgWidth,
|
|
imgHeight,
|
|
containerWidth,
|
|
containerHeight,
|
|
speed,
|
|
onListChange,
|
|
}) {
|
|
const colNum = Math.floor(containerWidth / imgWidth);
|
|
const rowNum = Math.floor(containerHeight / imgHeight);
|
|
const elNum = items.length;
|
|
const ratioSpeed = speed / (containerWidth / colNum);
|
|
const blockWidth = containerWidth / (colNum - 1);
|
|
const blockHeight = containerHeight / (rowNum - 1);
|
|
const elMap = new Map();
|
|
Object.assign(this, {
|
|
time: 0,
|
|
index: 0,
|
|
items,
|
|
colNum,
|
|
rowNum,
|
|
elNum,
|
|
imgWidth,
|
|
imgHeight,
|
|
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];
|
|
}
|
|
}
|
|
init() {
|
|
const { rowNum } = this;
|
|
|
|
for (let i = 0; i < rowNum; i++) {
|
|
const row = new Row({ wall: this, index: i });
|
|
row.init();
|
|
this.rows.push(row);
|
|
}
|
|
this.#rafID = window.requestAnimationFrame(this.animate);
|
|
this.isRunning = true;
|
|
return this;
|
|
}
|
|
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.getList();
|
|
this.time += dt;
|
|
this.#lastTime = now;
|
|
};
|
|
dispose() {
|
|
if (this.#rafID === null) return;
|
|
window.cancelAnimationFrame(this.#rafID);
|
|
this.#rafID = null;
|
|
this.isRunning = false;
|
|
return this;
|
|
}
|
|
getKey() {
|
|
this.key++;
|
|
return this.key;
|
|
}
|
|
getList() {
|
|
if (this.onListChange) this.onListChange(Array.from(this.elMap.entries()));
|
|
}
|
|
attachSphere(sphere) {
|
|
this.sphere = sphere;
|
|
}
|
|
}
|
|
|