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.
 
 
 
 
 
 

251 lines
6.1 KiB

import { Vector3 } from "three";
import BezierEasing from "bezier-easing";
import Stats from "stats.js";
const stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);
export const easeIn = BezierEasing(0.5, 0.16, 0.74, 0.38);
export const easeOut = BezierEasing(0, 0, 0.58, 1);
const enterDuration = 3000;
const exitDuration = 5000;
const margin = 55;
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;
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, 0.725);
}
if (point.distanceTo(targetPoint) > 0.01) {
point.lerp(targetPoint, 0.27);
}
const top = point.y - imgHeight / 2;
const left = point.x - imgWidth / 2;
const { urlSmall } = data;
const style = {
width: `${imgWidth}px`,
height: `${imgHeight}px`,
top: `${top}px`,
left: `${left}px`,
backgroundImage: `url(${urlSmall})`,
transform: `scale(${scale})`,
opcaity: scale,
zIndex: wall.rowNum - Math.floor(Math.abs(wall.rowNum / 2 - row.index)),
};
Object.assign(this, { seed, data, style, key, point });
}
update() {}
}
class Row {
q = [];
est;
fst;
t1;
t2;
constructor({ wall, index }) {
const initY =
wall.marginTop + index * wall.blockHeight + wall.blockHeight / 2;
const offsetY = initY - wall.containerHeight - wall.blockHeight;
Object.assign(this, {
wall,
minSize: wall.colNum + 2,
index,
ratio: Math.random(),
initY,
y: offsetY,
offsetY,
});
}
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);
}
update() {
this.q.forEach((el) => {
this.wall.elMap.set(el.key, new El(el.seed));
});
}
enter(now) {
if (!this.est) {
this.est = now;
} else {
this.y =
this.offsetY +
(this.wall.containerHeight + this.wall.blockHeight) *
easeOut((now - this.est) / enterDuration);
}
this.update();
}
exit(now) {
if (!this.fst) {
this.fst = now;
} else {
this.y =
this.initY +
this.wall.containerHeight * easeIn((now - this.fst) / exitDuration);
}
this.update();
}
nextFrame(ratioSpeed) {
this.ratio += ratioSpeed;
if (this.ratio > 1) {
this.shift();
this.push();
this.q.forEach((el) => {
el.seed.index--;
});
this.ratio -= 1;
}
this.update();
}
}
export default class Wall {
#rafID = null;
#lastTime;
entering = false;
exiting = false;
rows = [];
key = 0;
time = 0;
index = 0;
maxDeltaTime = 1 / 30;
constructor({
items,
imgWidth,
imgHeight,
containerWidth,
containerHeight,
speed,
onListChange,
}) {
const blockWidth = imgWidth + margin;
const blockHeight = imgHeight + margin;
const colNum = Math.floor(containerWidth / blockWidth) + 1;
const rowNum = Math.floor(containerHeight / blockHeight);
const elNum = items.length;
const ratioSpeed = speed / (containerWidth / colNum);
const marginTop = (containerHeight - rowNum * blockHeight) / 2;
const elMap = new Map();
Object.assign(this, {
items,
colNum,
rowNum,
elNum,
imgWidth,
imgHeight,
containerWidth,
containerHeight,
ratioSpeed,
blockWidth,
blockHeight,
marginTop,
elMap,
onListChange,
});
}
getNext() {
const { items } = this;
const next = items[this.index];
this.index++;
if (this.index === items.length) this.index = 0;
return next;
}
async 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;
this.entering = true;
await new Promise((resolve) => {
setTimeout(() => {
this.entering = false;
resolve();
}, enterDuration);
});
return this;
}
animate = () => {
stats.begin();
if (!this.isRunning) return;
const now = performance.now();
const dt = Math.min(this.maxDeltaTime, (now - this.#lastTime) / 1000);
this.rows.forEach((row) =>
this.entering
? row.enter(now)
: this.exiting
? row.exit(now)
: row.nextFrame(this.ratioSpeed)
);
this.getList();
this.time += dt;
this.#lastTime = now;
this.#rafID = window.requestAnimationFrame(this.animate);
stats.end();
};
async dispose() {
if (this.#rafID === null) return;
this.exiting = true;
await new Promise((resolve) => {
setTimeout(() => {
this.exiting = false;
window.cancelAnimationFrame(this.#rafID);
this.#rafID = null;
this.isRunning = false;
resolve();
}, exitDuration + 500);
});
return this;
}
getKey() {
this.key++;
return this.key;
}
getList() {
if (this.onListChange) this.onListChange(Array.from(this.elMap.entries()));
}
attachSphere(sphere) {
this.sphere = sphere;
}
}