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

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;
}
}