In the Cross-Platform Dynamic Light section I describe `data-*` attributes (https://www.tyleo.com/html-glass.html#cross-platform-dynamic...).

The dragging works with another bit of JavaScript--the only other bit on the page--which uses a `data-click-drag-area` to define an element which will contain draggable children and a `data-click-drag-item` attribute to indicate a child can be dragged.

The the parent must be a 'positioned element' (it must have `position` set to something in CSS) the children must have `position: absolute`.

I did this in TypeScript. I'll share the code below. You have to call `initDataClickDrag` from another script... if you want to include this script directly you can just remove the `export` keyword and call `initDataClickDrag()` at the bottom after it is defined:

export const initDataClickDrag = () => {
// Get all of the areas we can drag items in
const dragAreas = document.querySelectorAll("[data-click-drag-area]");
for (const dragArea of dragAreas) {
// Only iterate `HTMLElement`s
if (!(dragArea instanceof HTMLElement)) continue;

// Get all of the items we can drag
const dragItems = dragArea.querySelectorAll("[data-click-drag-item]");
for (const dragItem of dragItems) {
// Only iterate `HTMLElement`s
if (!(dragItem instanceof HTMLElement)) continue;

let isDragging = false;
let lastCursorX: number | undefined = undefined;
let lastCursorY: number | undefined = undefined;

// Mouse down event to start dragging
const downCallback = (obj: {
readonly pageX: number;
readonly pageY: number;
}) => {
isDragging = true;
lastCursorX = obj.pageX;
lastCursorY = obj.pageY;
};

dragItem.addEventListener("mousedown", (e) => {
downCallback(e);
});

dragItem.addEventListener("touchstart", (e) => {
const touches = e.touches;
if (touches.length === 0) return;
downCallback(touches[0]);
});

// Mouse move event to scroll while dragging
const moveCallback = (obj: {
readonly pageX: number;
readonly pageY: number;
}): boolean => {
if (!isDragging) return false;

if (lastCursorX === undefined) return false;
if (lastCursorY === undefined) return false;

const x = lastCursorX - obj.pageX;
const y = lastCursorY - obj.pageY;

const left = dragItem.offsetLeft - x;
const top = dragItem.offsetTop - y;

dragItem.style.left = `${left.toString()}px`;
dragItem.style.top = `${top.toString()}px`;

// Get dragArea dimensions
const dragAreaRect = dragArea.getBoundingClientRect();

// Get element dimensions
const elementRect = dragItem.getBoundingClientRect();

if (dragItem.offsetLeft < 0) dragItem.style.left = "0px";
if (dragItem.offsetTop < 0) dragItem.style.top = "0px";

if (left + elementRect.width > dragAreaRect.width) {
// Right boundary
const left = dragAreaRect.width - elementRect.width;
dragItem.style.left = `${left.toString()}px`;
}

if (top + elementRect.height > dragAreaRect.height) {
// Bottom boundary
const top = dragAreaRect.height - elementRect.height;
dragItem.style.top = `${top.toString()}px`;
}

lastCursorX = obj.pageX;
lastCursorY = obj.pageY;

return true;
};

document.addEventListener("mousemove", (e) => {
moveCallback(e);
});

document.addEventListener(
"touchmove",
(e) => {
const touches = e.touches;
if (touches.length === 0) return;
if (!moveCallback(touches[0])) return;
e.preventDefault();
},
{ passive: false },
);

// Mouse up event to stop dragging
document.addEventListener("mouseup", () => {
isDragging = false;
});

document.addEventListener("touchend", () => {
isDragging = false;
});
}
}
};