213 lines
5.5 KiB
JavaScript
213 lines
5.5 KiB
JavaScript
|
// toybox.js by Dante Scanline 2022
|
|||
|
// UNIVERSAL PUBLIC DOMAIN LICENSE - This code and everything else in the universe is in the public domain
|
|||
|
|
|||
|
/// ------------
|
|||
|
|
|||
|
// CONFIGURATION VARIABLES, please change these!
|
|||
|
const BUBBLE_IN = true; //makes the toys come in one at a time
|
|||
|
const BUBBLE_DELAY = 250; // if bubbling in, how much time in miliseconds between each new toy. bigger number is slowe
|
|||
|
|
|||
|
const PUSH_SPEED = 3; // base amount that toys push each other, bigger number means quicker stronger pushing
|
|||
|
const RANDOM_START_OFFSET = 200; // random distance from center in pixels where toys are spawned inside off
|
|||
|
|
|||
|
/// ------------
|
|||
|
|
|||
|
let toyContainer;
|
|||
|
let items = []; // array of dom elements
|
|||
|
|
|||
|
// Fisher–Yates knuth shuffle
|
|||
|
function shuffle(array) {
|
|||
|
let currentIndex = array.length,
|
|||
|
randomIndex;
|
|||
|
|
|||
|
// While there remain elements to shuffle.
|
|||
|
while (currentIndex != 0) {
|
|||
|
// Pick a remaining element.
|
|||
|
randomIndex = Math.floor(Math.random() * currentIndex);
|
|||
|
currentIndex--;
|
|||
|
|
|||
|
// And swap it with the current element.
|
|||
|
[array[currentIndex], array[randomIndex]] = [
|
|||
|
array[randomIndex],
|
|||
|
array[currentIndex],
|
|||
|
];
|
|||
|
}
|
|||
|
|
|||
|
return array;
|
|||
|
}
|
|||
|
|
|||
|
// reenable items sequentially for bubbling
|
|||
|
function reEnable(index) {
|
|||
|
if (index > items.length - 1) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
items[index].domElement.style.display = "initial";
|
|||
|
|
|||
|
setTimeout(() => {
|
|||
|
reEnable(index + 1);
|
|||
|
}, BUBBLE_DELAY);
|
|||
|
}
|
|||
|
|
|||
|
// the object, with width height from DOM and then a position we set :)
|
|||
|
class Toy {
|
|||
|
constructor(domElement) {
|
|||
|
this.domElement = domElement;
|
|||
|
this.domElement.style.position = "absolute";
|
|||
|
|
|||
|
let rect = toyContainer.getBoundingClientRect();
|
|||
|
this.x = rect.width / 2 + 100 - Math.random() * 200;
|
|||
|
this.y = rect.height / 2 + 100 - Math.random() * 200;
|
|||
|
|
|||
|
this.visualX = this.x;
|
|||
|
this.visualY = this.y;
|
|||
|
|
|||
|
this.updateSize();
|
|||
|
this.updatePosition();
|
|||
|
}
|
|||
|
|
|||
|
updatePosition() {
|
|||
|
this.visualX += (this.x - this.visualX) * 0.1;
|
|||
|
this.visualY += (this.y - this.visualY) * 0.1;
|
|||
|
this.domElement.style.left = `${this.x}px`;
|
|||
|
this.domElement.style.top = `${this.y}px`;
|
|||
|
}
|
|||
|
|
|||
|
updateSize() {
|
|||
|
this.width = this.domElement.clientWidth;
|
|||
|
if (this.width == 0 || this.width == undefined) {
|
|||
|
console.error("no size for element yet");
|
|||
|
}
|
|||
|
this.height = this.domElement.clientHeight;
|
|||
|
if (this.width < 65 || this.height < 65) {
|
|||
|
this.domElement.style["z-index"] = "20";
|
|||
|
}
|
|||
|
this.domElement.style.marginLeft = `${-this.width / 2}px`;
|
|||
|
this.domElement.style.marginTop = `${-this.height / 2}px`;
|
|||
|
// this.domElement.style.transform = `translate(${-this.width / 2}px, ${-this.height / 2}px)`
|
|||
|
}
|
|||
|
|
|||
|
pointOverlaps(xx, yy) {
|
|||
|
return (
|
|||
|
xx > this.x - this.width / 2 &&
|
|||
|
xx < this.x + this.width / 2 &&
|
|||
|
yy > this.y - this.height / 2 &&
|
|||
|
yy < this.y + this.height / 2
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
toyOverlaps(otherToy) {
|
|||
|
const corners = [
|
|||
|
[otherToy.x - otherToy.width / 2, otherToy.y - otherToy.height / 2],
|
|||
|
[otherToy.x + otherToy.width / 2, otherToy.y - otherToy.height / 2],
|
|||
|
[
|
|||
|
otherToy.x - otherToy.height / 2,
|
|||
|
otherToy.y + otherToy.height / 2,
|
|||
|
],
|
|||
|
[otherToy.x + otherToy.width / 2, otherToy.y + otherToy.height / 2],
|
|||
|
];
|
|||
|
|
|||
|
// check if the other toys corners are inside our rect
|
|||
|
for (let i = 0; i < corners.length; i++) {
|
|||
|
let pair = corners[i];
|
|||
|
if (this.pointOverlaps(pair[0], pair[1])) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// edge case, where we are fit entirely inside the other rect, so check one of our points on them
|
|||
|
return otherToy.pointOverlaps(this.x, this.y);
|
|||
|
}
|
|||
|
|
|||
|
getPushed(otherToy) {
|
|||
|
let distance = PUSH_SPEED;
|
|||
|
|
|||
|
let centerX = this.x;
|
|||
|
let centerY = this.y;
|
|||
|
|
|||
|
let otherCenterX = otherToy.x;
|
|||
|
let otherCenterY = otherToy.y;
|
|||
|
|
|||
|
let angle = Math.atan2(centerY - otherCenterY, centerX - otherCenterX);
|
|||
|
|
|||
|
let finalStrength = distance;
|
|||
|
|
|||
|
// if the toy pushing us is roughly bigger than us, get pushed more to help offset some biasing
|
|||
|
if (
|
|||
|
otherToy.width + otherToy.height >
|
|||
|
(this.width + this.height) * 1.3
|
|||
|
) {
|
|||
|
finalStrength *= 3;
|
|||
|
}
|
|||
|
|
|||
|
this.x += Math.cos(angle) * finalStrength;
|
|||
|
this.y += Math.sin(angle) * finalStrength;
|
|||
|
this.updatePosition();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
document.addEventListener("DOMContentLoaded", init);
|
|||
|
|
|||
|
function init() {
|
|||
|
toyContainer = document.querySelector("#toybox");
|
|||
|
let nodes = Array.from(toyContainer.childNodes);
|
|||
|
nodes = nodes.filter((node) => node.nodeType == 1);
|
|||
|
nodes = shuffle(nodes); // help prevent biases
|
|||
|
|
|||
|
for (let i = 0; i < nodes.length; i++) {
|
|||
|
items.push(new Toy(nodes[i]));
|
|||
|
|
|||
|
if (BUBBLE_IN) {
|
|||
|
if (i > 3) {
|
|||
|
items[i].domElement.style.display = "none";
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (BUBBLE_IN) {
|
|||
|
reEnable(3);
|
|||
|
}
|
|||
|
requestAnimationFrame(main);
|
|||
|
}
|
|||
|
|
|||
|
function main() {
|
|||
|
items.map((item) => item.updateSize());
|
|||
|
|
|||
|
for (let i = 0; i < items.length; i++) {
|
|||
|
const itemA = items[i];
|
|||
|
|
|||
|
for (let k = 0; k < items.length; k++) {
|
|||
|
if (i == k) continue;
|
|||
|
|
|||
|
const itemB = items[k];
|
|||
|
if (itemA.toyOverlaps(itemB)) {
|
|||
|
itemB.getPushed(itemA);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
let rect = toyContainer.getBoundingClientRect();
|
|||
|
|
|||
|
for (let i = 0; i < items.length; i++) {
|
|||
|
const itemA = items[i];
|
|||
|
if (itemA.x - itemA.width / 2 < 0) {
|
|||
|
itemA.x += 10;
|
|||
|
itemA.updatePosition();
|
|||
|
}
|
|||
|
if (itemA.y - itemA.height / 2 < 0) {
|
|||
|
itemA.y += 10;
|
|||
|
itemA.updatePosition();
|
|||
|
}
|
|||
|
if (itemA.x + itemA.width / 2 > rect.width) {
|
|||
|
itemA.x -= 10;
|
|||
|
itemA.updatePosition();
|
|||
|
}
|
|||
|
if (itemA.y + itemA.height / 2 > rect.height) {
|
|||
|
itemA.y -= 5;
|
|||
|
itemA.updatePosition();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
requestAnimationFrame(main);
|
|||
|
}
|