import { createClient } from "https://cdn.skypack.dev/@liveblocks/client"; let PUBLIC_KEY = "pk_test_L8JkCoBm0bYACwp5oOJQsj2n"; let roomId = "javascript-live-cursors"; overrideApiKeyAndRoomId(); if (!/^pk_(live|test)/.test(PUBLIC_KEY)) { console.warn( `Replace "${PUBLIC_KEY}" by your public key from https://liveblocks.io/dashboard/apikeys.\n` + `Learn more: https://github.com/liveblocks/liveblocks/tree/main/examples/javascript-live-cursors#getting-started.` ); } const client = createClient({ publicApiKey: PUBLIC_KEY, }); const room = client.enter(roomId, { initialPresence: { cursor: null } }); const cursorsContainer = document.getElementById("cursors-container"); const text = document.getElementById("text"); room.subscribe("my-presence", (presence) => { const cursor = presence?.cursor ?? null; text.innerHTML = cursor ? `${cursor.x} × ${cursor.y}` : "Move your cursor to broadcast its position to other people in the room."; }); /** * Subscribe to every others presence updates. * The callback will be called if you or someone else enters or leaves the room * or when someone presence is updated */ room.subscribe("others", (others, event) => { switch (event.type) { case "reset": { // Clear all cursors cursorsContainer.innerHTML = ""; for (const user of others.toArray()) { updateCursor(user); } break; } case "leave": { deleteCursor(event.user); break; } case "enter": case "update": { updateCursor(event.user); break; } } }); // get mouse position relative to an element function getMousePosition(event, element) { const rect = element.getBoundingClientRect(); return { x: event.clientX - rect.left, y: event.clientY - rect.top, }; } // Get mouse position related to a transform and a scale // function getMousePosition(event, transform, scale) { // console.log(scale); // const rect = event.target.getBoundingClientRect(); // const x = (event.offsetX - transform.x) / scale; // const y = (event.offsetY - transform.y) / scale; // return { x, y }; // } document.addEventListener("pointermove", (e) => { e.preventDefault(); // const { offsetX, offsetY, clientX, clientY } = e; // console.log(offsetX, offsetY, clientX, clientY); // console.log(getMousePosition(e, board)); room.updatePresence({ cursor: getMousePosition(e, board), }); }); document.addEventListener("pointerleave", (e) => { room.updatePresence({ cursor: null }); }); const COLORS = ["#DC2626", "#D97706", "#059669", "#7C3AED", "#DB2777"]; // Update cursor position based on user presence function updateCursor(user) { const cursor = getCursorOrCreate(user.connectionId); if (user.presence?.cursor) { cursor.style.transform = `translateX(${user.presence.cursor.x}px) translateY(${user.presence.cursor.y}px)`; cursor.style.opacity = "1"; } else { cursor.style.opacity = "0"; } } function getCursorOrCreate(connectionId) { let cursor = document.getElementById(`cursor-${connectionId}`); if (cursor == null) { cursor = document.getElementById("cursor-template").cloneNode(true); cursor.id = `cursor-${connectionId}`; cursor.style.fill = COLORS[connectionId % COLORS.length]; cursorsContainer.appendChild(cursor); } return cursor; } function deleteCursor(user) { const cursor = document.getElementById(`cursor-${user.connectionId}`); if (cursor) { cursor.parentNode.removeChild(cursor); } } /** * This function is used when deploying an example on liveblocks.io. * You can ignore it completely if you run the example locally. */ function overrideApiKeyAndRoomId() { const query = new URLSearchParams(window?.location?.search); const apiKey = query.get("apiKey"); const roomIdSuffix = query.get("roomId"); if (apiKey) { PUBLIC_KEY = apiKey; } if (roomIdSuffix) { roomId = `${roomId}-${roomIdSuffix}`; } }