Got the roundtrip working. But the endstate math is off.
This commit is contained in:
parent
764523ed4f
commit
f521b261e6
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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`<div id="fridge" @pointerdown=${this.onPointerDown}>
|
||||||
|
${words.map((word) => html`<fridge-tile style="opacity: 0" word=${word.w}></fridge-tile>`)}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { customElement } from "lit/decorators/custom-element.js";
|
import { customElement } from "lit/decorators/custom-element.js";
|
||||||
import { words } from "./wordlist.js";
|
import { words } from "./wordlist.js";
|
||||||
import "./fridge-tile.js";
|
import { PointerLocationRequest } from "./events.js";
|
||||||
|
|
||||||
|
import "./fridge-board.js";
|
||||||
|
|
||||||
@customElement("fridge-magnets")
|
@customElement("fridge-magnets")
|
||||||
export class FridgeMagnets extends LitElement {
|
export class FridgeMagnets extends LitElement {
|
||||||
|
@ -10,34 +12,51 @@ export class FridgeMagnets extends LitElement {
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
#fridgemagnets {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 100fr 18ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fridge {
|
fridge-board {
|
||||||
overflow: hidden;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 95vw;
|
||||||
background: url("./dist/pingbg.png") repeat;
|
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() {
|
render() {
|
||||||
return html` <div id="fridgemagnets">
|
return html`<div id="body">
|
||||||
<div id="fridge">
|
<fridge-board .words=${this.words} id="fridge"></fridge-broad>
|
||||||
${words.map(word => html`<fridge-tile word=${word.w}></fridge-tile>`)}
|
<p slot="footer">Some Content will go here.</p>
|
||||||
</div>
|
|
||||||
<div id="footer">Footer</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,48 +49,53 @@ export class FridgeTile extends LitElement {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: white;
|
background: white;
|
||||||
z-index: 100;
|
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() {
|
get position() {
|
||||||
super();
|
const { left, top } = this.getBoundingClientRect();
|
||||||
this.dragHandle = new LitDraggable(this);
|
return [left, top];
|
||||||
this.onDragEnd = this.onDragEnd.bind(this);
|
|
||||||
this.onDragStart = this.onDragStart.bind(this);
|
|
||||||
this.addEventListener(LitDragEnd.eventName, this.onDragEnd);
|
|
||||||
this.addEventListener(LitDragStart.eventName, this.onDragStart);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragEnd(ev: LitDragEvent) {
|
get relativePosition() {
|
||||||
this.transform = { scale: 1.0, rotate: Math.random() * 30 - 15 };
|
return [this.offsetLeft, this.offsetTop];
|
||||||
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`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart(_ev: LitDragEvent) {
|
get size() {
|
||||||
this.transform = { scale: 1.3, rotate: Math.random() * 30 - 15 };
|
const { width, height } = this.getBoundingClientRect();
|
||||||
this.handle.value!.style.setProperty(
|
return { width, height };
|
||||||
"transform",
|
}
|
||||||
`scale(${+this.transform.scale}) rotate(${+this.transform.rotate}deg)`
|
|
||||||
);
|
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() {
|
render() {
|
||||||
const styles = {
|
const styles = {
|
||||||
width: `${this.word.length * 1.2}ch`,
|
width: `${this.word.length * 1.2}ch`,
|
||||||
transform: `rotate(${this.transform.rotate}deg)`,
|
|
||||||
};
|
};
|
||||||
return html`<div part="word ${ref(this.handle)} " style="${styleMap(styles)}" class="word">${this.word}</div>`;
|
return html`<div part="word ${ref(this.handle)} " style="${styleMap(styles)}" class="word">${this.word}</div>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,3 +11,8 @@ export interface LitDragEvent extends Event {
|
||||||
offsetY: number;
|
offsetY: number;
|
||||||
node: HTMLElement;
|
node: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Position {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue