Skip to content

Drag with can-move

can-move makes an element draggable, persistent, and shared. Add a stable id, then use can-move-bounds when the element must stay inside a container.

Live exampleRoom connecting…
Remix
Test it with another person
  1. Keep this page open in your normal window.
  2. Copy the private-window link, then paste it into a private or incognito window.
  3. Interact in either window and watch the other one update.

Copy the code

Both versions use the same shared data and behavior. The live playground runs the Vanilla HTML version.

Save this as index.html, or open it in the playground to test and change it.

Remix

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Drag with can-move</title>
  <style>
    :root {
      color: #1c1c1c;
      background: #f4efe5;
      font-family: ui-sans-serif, system-ui, sans-serif;
    }
    * { box-sizing: border-box; }
    body { display: grid; min-height: 100vh; margin: 0; padding: 1.5rem; place-items: center; }
    main { width: min(36rem, 100%); }
    h1 { margin: 0 0 0.35rem; font-size: clamp(2rem, 8vw, 3.5rem); line-height: 1; }
    .intro { margin: 0 0 1.25rem; line-height: 1.5; }
    .arena {
      position: relative;
      height: 20rem;
      overflow: hidden;
      border: 2px solid #1c1c1c;
      background: #dce8cf;
      box-shadow: 5px 5px 0 #1c1c1c;
    }
    .piece {
      position: absolute;
      display: grid;
      width: 8rem;
      height: 8rem;
      place-items: center;
      font-size: 6rem;
      touch-action: none;
      user-select: none;
    }
    .hat { top: 2rem; left: 2rem; }
    .cat { right: 2rem; bottom: 1.5rem; }
  </style>
</head>
<body>
  <main>
    <h1>Drag together</h1>
    <p class="intro">Move either sticker. Its position updates in every connected browser.</p>
    <div id="move-arena" class="arena">
      <div
        id="move-hat"
        class="piece hat"
        can-move
        can-move-bounds="move-arena"
        role="img"
        aria-label="A draggable baseball cap"
      >🧢</div>
      <div
        id="move-cat"
        class="piece cat"
        can-move
        can-move-bounds="move-arena"
        role="img"
        aria-label="A draggable cat"
      >🐈</div>
    </div>
  </main>

  <script type="module">
    import { playhtml } from "playhtml";

    // Built-in capabilities need no custom handlers.

    await playhtml.init({ developmentMode: true });
  </script>
</body>
</html>

Set can-move-bounds to the container’s id:

<div id="move-arena">
  <div
    id="move-hat"
    can-move
    can-move-bounds="move-arena"
    role="img"
    aria-label="A draggable baseball cap"
  >🧢</div>
</div>

PlayHTML saves each element’s position under its own id. Reloading the page or opening the same room in another browser restores that position.

Drag either sticker, then open the private-window link. Both windows should show the same positions. Dragging in either window updates the other.

See can-move for partial-overhang controls and the complete attribute reference.