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 { 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` <div id="fridgemagnets">
|
||||
<div id="fridge">
|
||||
${words.map(word => html`<fridge-tile word=${word.w}></fridge-tile>`)}
|
||||
</div>
|
||||
<div id="footer">Footer</div>
|
||||
return html`<div id="body">
|
||||
<fridge-board .words=${this.words} id="fridge"></fridge-broad>
|
||||
<p slot="footer">Some Content will go here.</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`<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;
|
||||
node: HTMLElement;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue