import { createMachine, assign, interpret } from "xstate";
const elBox = document.querySelector("#box");
const elBody = document.body;
const assignPoint = assign({
px: (context, event) => event.clientX,
py: (context, event) => event.clientY,
});
const assignPosition = assign({
x: (context, event) => {
return context.x + context.dx;
},
y: (context, event) => {
return context.y + context.dy;
},
dx: 0,
dy: 0,
px: 0,
py: 0,
});
const assignDelta = assign({
dx: (context, event) => {
return event.clientX - context.px;
},
dy: (context, event) => {
return event.clientY - context.py;
},
});
const showDelta = (context) => {
elBox.dataset.delta = `delta: ${context.dx}, ${context.dy}`;
};
const resetPosition = assign({
dx: 0,
dy: 0,
px: 0,
py: 0,
});
const machine = createMachine({
initial: "idle",
context: {
x: 0,
y: 0,
dx: 0,
dy: 0,
px: 0,
py: 0,
},
states: {
idle: {
on: {
mousedown: {
actions: assignPoint,
target: "dragging",
},
},
},
dragging: {
on: {
mousemove: {
actions: [assignDelta, showDelta],
// no target!
},
mouseup: {
actions: assignPosition,
target: "idle",
},
},
},
},
});
const service = interpret(machine);
service.onTransition((state) => {
if (state.changed) {
console.log(state.context);
elBox.dataset.state = state.value;
elBox.style.setProperty("--dx", state.context.dx);
elBox.style.setProperty("--dy", state.context.dy);
elBox.style.setProperty("--x", state.context.x);
elBox.style.setProperty("--y", state.context.y);
}
});
service.start();
elBox.addEventListener("mousedown", (event) => {
service.send(event);
});
elBody.addEventListener("mousemove", (event) => {
service.send(event);
});
elBody.addEventListener("mouseup", (event) => {
service.send(event);
});
Css:
It uses many [data-state] selector
#box[data-state^='dragging'] { opacity: 0.8; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.4); } #box[data-state='idle'] { transition: all 0.3s ease-in-out; } @import '../../styles/index.scss'; #box { opacity: 0.5; &[data-state='active'] { opacity: 1; } &:before { content: attr(data-delta); position: absolute; bottom: 100%; margin-bottom: 0.5rem; left: 0; background: black; padding: 0.25rem; color: white; font-family: monospace; border-radius: inherit; white-space: nowrap; } }