diff --git a/src/events.ts b/src/events.ts
new file mode 100644
index 0000000..6d629ab
--- /dev/null
+++ b/src/events.ts
@@ -0,0 +1,22 @@
+import type { Position } from "./types";
+
+export class PointerLocationRequest extends Event {
+ static readonly eventName = "fridge-pointer-location";
+ position!: Position;
+ constructor() {
+ super(PointerLocationRequest.eventName, { bubbles: true, composed: true });
+ }
+}
+
+export function pointerLocation(self?: HTMLElement): Position {
+ const node = self ?? window;
+ const ev = new PointerLocationRequest();
+ node.dispatchEvent(ev);
+ return ev.position;
+}
+
+declare global {
+ interface GlobalEventHandlersEventMap {
+ [PointerLocationRequest.eventName]: PointerLocationRequest;
+ }
+}
diff --git a/src/fridge-board.ts b/src/fridge-board.ts
new file mode 100644
index 0000000..9f962a4
--- /dev/null
+++ b/src/fridge-board.ts
@@ -0,0 +1,212 @@
+import { LitElement, html, css } from "lit";
+import { customElement } from "lit/decorators/custom-element.js";
+import { queryAll } from "lit/decorators/query-all.js";
+import { query } from "lit/decorators/query.js";
+import { words } from "./wordlist.js";
+import "./fridge-tile.js";
+import { property } from "lit/decorators/property.js";
+import { FridgeTile } from "./fridge-tile.js";
+import { pointerLocation } from "./events.js";
+
+function* pointsIterator(source: FridgeTile[]) {
+ for (const tile of source) {
+ for (const point of tile.points) {
+ yield point;
+ }
+ }
+}
+
+type Point = [number, number];
+
+const isHTMLElement = (v: unknown): v is HTMLElement =>
+ typeof v === "object" && v !== null && "nodeType" in v && v.nodeType === Node.ELEMENT_NODE;
+
+@customElement("fridge-board")
+export class FridgeBoard extends LitElement {
+ static get styles() {
+ return css`
+ :host {
+ display: block;
+ position: relative;
+ }
+
+ #fridge {
+ overflow: hidden;
+ position: relative;
+ top: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ }
+
+ fridge-tile {
+ position: absolute;
+ }
+
+ .ready-to-slide {
+ transition: transform 1500ms cubic-bezier(0.4, 0, 0.2, 1);
+ will-change: transform;
+ }
+ `;
+ }
+
+ @property({ type: Array })
+ words: (typeof words)[] = [];
+
+ @queryAll("fridge-tile")
+ tiles!: FridgeTile[];
+
+ @query("#fridge")
+ fridge!: HTMLDivElement;
+
+ assignNewPositions() {
+ const pointsAlreadyPositioned: Point[] = [];
+ const unpositionable: FridgeTile[] = [];
+ const { width: boardWidth, height: boardHeight } = this.getBoundingClientRect();
+
+ const setNewPosition = (tile: FridgeTile) => {
+ const { height: tileHeight, width: tileWidth } = tile.size;
+
+ for (let i = 0; i < 30; i++) {
+ // Where we will put the new tile
+ const newXPos = Math.random() * (boardHeight - tileHeight) * 0.985;
+ const newYPos = Math.random() * (boardWidth - tileWidth) * 0.98;
+
+ // The box defining the new tile.
+ const box = {
+ xl: newXPos,
+ xr: newXPos + tileWidth,
+ yt: newYPos,
+ yb: newYPos + tileHeight,
+ };
+
+ const isPointInBox = (point: Point) =>
+ point[0] >= box.xl && point[0] <= box.xr && point[1] >= box.yt && point[1] <= box.yb;
+
+ if (pointsAlreadyPositioned.find((p) => isPointInBox(p)) === undefined) {
+ tile.style.top = `${box.yt}px`;
+ tile.style.left = `${box.xl}px`;
+ return true;
+ }
+ }
+ return false;
+ };
+
+ for (const tile of this.tiles) {
+ if (!setNewPosition(tile)) {
+ unpositionable.push(tile);
+ }
+ }
+
+ for (const tile of unpositionable) {
+ tile.remove();
+ }
+ }
+
+ onPointerDown(ev: PointerEvent) {
+ const node = ev.target;
+
+ if (!(isHTMLElement(node) && node.tagName.toLowerCase() === "fridge-tile")) {
+ return;
+ }
+
+ // The position of the board with respect to the viewport;
+ const { left: fridgeLeft, top: fridgeTop } = this.getBoundingClientRect();
+
+ // The position of the node with respect to the viewport:
+ const { left: nodeLeft, top: nodeTop } = node.getBoundingClientRect();
+
+ // Where the pointer was when the event started with respect to the viewport;
+ const { x: pointerStartX, y: pointerStartY } = pointerLocation();
+
+ // Starting position of the *node* with respect to the board;
+ const tileStart = {
+ x: nodeLeft - fridgeLeft,
+ y: nodeTop - fridgeTop,
+ };
+
+ const controller = new AbortController();
+ const { signal } = controller;
+
+ let tracking = true;
+
+ const stop = () => {
+ console.log("Stop called?");
+ controller.abort();
+ tracking = false;
+ const cursorPosition = pointerLocation();
+ let newX = tileStart.x - (cursorPosition.x - pointerStartX);
+ let newY = tileStart.y - (cursorPosition.y - pointerStartY);
+ node.style.setProperty("transform", "");
+ node.style.setProperty("top", `${newY}px`);
+ node.style.setProperty("left", `${newX}px`);
+ };
+
+ let animationFrame: number = -1;
+
+ const move = () => {
+ const cursorPosition = pointerLocation();
+ let delX = cursorPosition.x - pointerStartX;
+ let delY = cursorPosition.y - pointerStartY;
+ node.style.setProperty("transform", `translate3d(${+delX}px, ${+delY}px, 0)`);
+
+ if (tracking) {
+ animationFrame = requestAnimationFrame(move);
+ } else {
+ cancelAnimationFrame(animationFrame);
+ }
+ };
+
+ window.addEventListener("pointerup", stop, { signal });
+ window.addEventListener("pointercancel", stop, { signal });
+ animationFrame = requestAnimationFrame(move);
+ }
+
+ render() {
+ return html`
+ ${words.map((word) => html``)}
+
`;
+ }
+
+ updated() {
+ const { width: boardWidth, height: boardHeight } = this.getBoundingClientRect();
+ this.assignNewPositions();
+
+ const fd = (size: number) => {
+ const newDelta = 40 * Math.random();
+ return Math.random() < 0.5 ? newDelta + size : newDelta * -1;
+ };
+
+ requestAnimationFrame(() =>
+ this.tiles.forEach((tile) => {
+ const outerX = fd(boardWidth);
+ const outerY = fd(boardHeight);
+ const [tileX, tileY] = tile.relativePosition;
+ tile.style.transform = `translate(${outerX - tileX}px, ${outerY - tileY}px) rotate(${Math.random() * 30 - 15}deg) scale(1.5)`;
+ tile.style.opacity = "100%";
+ })
+ );
+
+ requestAnimationFrame(() =>
+ this.tiles.forEach((tile) => {
+ tile.classList.add("ready-to-slide");
+ tile.style.transform = `translate(0, 0) rotate(${Math.random() * 30}deg) scale(1.0)`;
+ })
+ );
+
+ setTimeout(
+ () =>
+ this.tiles.forEach((tile) => {
+ tile.classList.remove("ready-to-slide");
+ }),
+ 1525
+ );
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "fridge-board": FridgeBoard;
+ }
+}
diff --git a/src/fridge-magnets.ts b/src/fridge-magnets.ts
index 9a046dd..2f11f13 100644
--- a/src/fridge-magnets.ts
+++ b/src/fridge-magnets.ts
@@ -1,7 +1,9 @@
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators/custom-element.js";
import { words } from "./wordlist.js";
-import "./fridge-tile.js";
+import { PointerLocationRequest } from "./events.js";
+
+import "./fridge-board.js";
@customElement("fridge-magnets")
export class FridgeMagnets extends LitElement {
@@ -10,34 +12,51 @@ export class FridgeMagnets extends LitElement {
:host {
display: block;
}
- #fridgemagnets {
- width: 100%;
- height: 100%;
- display: grid;
- grid-template-rows: 100fr 18ex;
- }
- #fridge {
- overflow: hidden;
+ fridge-board {
+ display: block;
position: relative;
width: 100%;
+ height: 95vw;
background: url("./dist/pingbg.png") repeat;
}
-
- #footer {
- background-color: #32cd32;
- width: 100%;
- height: 18ex;
- }
`;
}
+ words = words;
+
+ pointerLocation: { x: number; y: number } = { x: -1, y: -1 };
+
+ constructor() {
+ super();
+ this.updatePointerPosition = this.updatePointerPosition.bind(this);
+ this.onPointerLocation = this.onPointerLocation.bind(this);
+ }
+
+ updatePointerPosition(ev: PointerEvent) {
+ this.pointerLocation = { x: ev.clientX, y: ev.clientY };
+ }
+
+ onPointerLocation(ev: PointerLocationRequest) {
+ ev.position = this.pointerLocation;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ window.addEventListener("pointermove", this.updatePointerPosition);
+ window.addEventListener(PointerLocationRequest.eventName, this.onPointerLocation);
+ }
+
+ disconnectedCallback() {
+ window.removeEventListener("pointermove", this.updatePointerPosition);
+ window.removeEventListener(PointerLocationRequest.eventName, this.onPointerLocation);
+ super.disconnectedCallback();
+ }
+
render() {
- return html`
-
- ${words.map(word => html``)}
-
-
+ return html`
+
+ Some Content will go here.
`;
}
}
diff --git a/src/fridge-tile.ts b/src/fridge-tile.ts
index 124ecbc..ad4f77a 100644
--- a/src/fridge-tile.ts
+++ b/src/fridge-tile.ts
@@ -49,48 +49,53 @@ export class FridgeTile extends LitElement {
position: relative;
background: white;
z-index: 100;
- transition-property: transform;
- transition-duration: 200ms;
- transition-timing-function: ease-in-out; //other options are ease
- }
-
- .word.dragging {
- font-size: 1.1875rem;
}
`;
}
- constructor() {
- super();
- this.dragHandle = new LitDraggable(this);
- this.onDragEnd = this.onDragEnd.bind(this);
- this.onDragStart = this.onDragStart.bind(this);
- this.addEventListener(LitDragEnd.eventName, this.onDragEnd);
- this.addEventListener(LitDragStart.eventName, this.onDragStart);
+ get position() {
+ const { left, top } = this.getBoundingClientRect();
+ return [left, top];
}
- onDragEnd(ev: LitDragEvent) {
- this.transform = { scale: 1.0, rotate: Math.random() * 30 - 15 };
- this.handle.value!.style.setProperty(
- "transform",
- `scale(${+this.transform.scale}) rotate(${+this.transform.rotate}deg)`
- );
- this.style.setProperty("top", `${+ev.offsetY}px`);
- this.style.setProperty("left", `${+ev.offsetX}px`);
+ get relativePosition() {
+ return [this.offsetLeft, this.offsetTop];
}
- onDragStart(_ev: LitDragEvent) {
- this.transform = { scale: 1.3, rotate: Math.random() * 30 - 15 };
- this.handle.value!.style.setProperty(
- "transform",
- `scale(${+this.transform.scale}) rotate(${+this.transform.rotate}deg)`
- );
+ get size() {
+ const { width, height } = this.getBoundingClientRect();
+ return { width, height };
+ }
+
+ get tl() {
+ return this.relativePosition;
+ }
+
+ get tr() {
+ const [x, y] = this.relativePosition;
+ const { width } = this.size;
+ return [x + width, y];
+ }
+
+ get bl() {
+ const [x, y] = this.relativePosition;
+ const { height } = this.size;
+ return [x + height, y];
+ }
+
+ get br() {
+ const [x, y] = this.relativePosition;
+ const { width, height } = this.size;
+ return [x + width, y + height];
+ }
+
+ get points() {
+ return [this.tl, this.tr, this.bl, this.br];
}
render() {
const styles = {
width: `${this.word.length * 1.2}ch`,
- transform: `rotate(${this.transform.rotate}deg)`,
};
return html`
${this.word}
`;
}
diff --git a/src/types.ts b/src/types.ts
index a053696..b28ac3d 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -11,3 +11,8 @@ export interface LitDragEvent extends Event {
offsetY: number;
node: HTMLElement;
}
+
+export interface Position {
+ x: number;
+ y: number;
+}