Skip to content

Live reactions

Click an emoji and it floats up for everyone on the page. Reactions are events, so they show up only for people here right now and are never stored.

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>Live reactions</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; }
    .reactions {
      position: relative;
      width: min(24rem, 100%);
      height: 180px;
      overflow: hidden;
    }
    .burst {
      position: absolute;
      bottom: 0;
      font-size: 1.65rem;
      pointer-events: none;
      animation: float-up 2.5s ease-out forwards;
    }
    @keyframes float-up {
      to { opacity: 0; transform: translateY(-140px) scale(0.8); }
    }
  </style>
</head>
<body>
  <main id="reactions" class="reactions">
    <button data-emoji="❤️">❤️</button>
    <button data-emoji="✨">✨</button>
    <button data-emoji="🔥">🔥</button>
    <button data-emoji="🎉">🎉</button>
  </main>

  <script type="module">
    import { playhtml } from "https://unpkg.com/playhtml@latest";

    const stage = document.getElementById("reactions");

    // Float one emoji up from a random spot, then remove it.
    function burst({ emoji }) {
      const el = document.createElement("span");
      el.className = "burst";
      el.textContent = emoji;
      el.style.left = `${Math.random() * 90}%`;
      el.addEventListener("animationend", () => el.remove());
      stage.append(el);
    }

    for (const button of stage.querySelectorAll("button")) {
      button.addEventListener("click", () => {
        playhtml.dispatchPlayEvent({
          type: "reaction",
          eventPayload: { emoji: button.dataset.emoji },
        });
      });
    }

    await playhtml.init({
      developmentMode: true,
      events: {
        reaction: { type: "reaction", onEvent: burst },
      },
    });
  </script>
</body>
</html>

eventPayload travels with the event, and onEvent receives it:

playhtml.dispatchPlayEvent({
  type: "reaction",
  eventPayload: { emoji: "🎉" },
});

// Every browser on the page, including yours, runs this.
function burst({ emoji }) {
  // float the emoji up
}

playhtml.init({
  events: {
    reaction: { type: "reaction", onEvent: burst },
  },
});

Each burst is a CSS animation that removes itself when it ends, so nothing piles up.

See Events for when to use events instead of shared data.