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