typescriptnext.jsreact-flowliveblocks

How to create a live cursor in ReactFlow using Liveblocks for accurate zooming, panning & different screen resolutions?


The issue is that the live cursor's position on other users' screens is not accurately represented due to differences in screen resolutions, zoom levels, or the positioning of ReactFlow elements caused by individual user interactions. These disparities result in an improper alignment of the live cursor on remote displays, making it appear misplaced or misaligned. To achieve seamless collaboration, it is essential to ensure that the live cursor's position is precisely adjusted and synchronized across all users' screens, considering various factors such as resolution, zoom levels, and user interactions with ReactFlow elements.

I have successfully resolved this issue by adjusting the live cursor's position based on the zoom level and the ReactFlow's current position on other users' screens.


Solution

  • // Implement the live cursor component as follows:

    import { FC, useEffect } from "react";
    import { useReactFlow } from "reactflow"
    import useLiveBlocksStore from "@/stores/useLiveBlocksStore";
    interface LiveCursorProps {
        cursorPos: { x: number, y: number };
    }
    const LiveCursor: FC<LiveCursorProps> = ({cursorPos }) => {
        const reactFlowInstance = useReactFlow()
        const {
            updateMyPresence,
            liveblocks: { others },
          } = useLiveBlocksStore()
    
        useEffect(() => {
            const position = reactFlowInstance.project({ x: cursorPos.x, y: cursorPos.y });
            //Liveblocks method to set the curren cursor positon
            updateMyPresence({
                cursor: { x: Math.round(position.x), y: Math.round(position.y) }
            })
        }, [cursorPos])
        return (
            <>
                {
                    others.map((other) => {
                        if (other.presence.cursor == null) {
                            return null
                        }
                    // get current Reactflow screen viewPort
                            const otherViewPort = reactFlowInstance.getViewport();
    
                        //fixing the x, y position of cursor in other screen inside of react flow
                        let xPos = other.presence.cursor.x * otherViewPort.zoom + otherViewPort.x;
                        let yPos = other.presence.cursor.y * otherViewPort.zoom + otherViewPort.y;
    
                        return (
                            /* The cursor component takes the x and y coordinates of the cursor 
                            and accurately displays it in the correct position on     other users' screens. */
                        <Cursor key={other.id} x={xPos} y={yPos} />
                        )
                    })
                }
            </>
    }
    export default LiveCursor
    

    Pass your component as a child component to the ReactFlow component, and ensure that the position of this component is set to absolute.

    const [cursorPos, setCurosrPos] = useState({ x: 0, y: 0 })
    const handleMouseMove = (event: React.MouseEvent) => {
        setCurosrPos({ x: event.clientX, y: event.clientY });
    };
    return <div style={{position:"absolute",left:0, top:0, overflowY:"auto", width:"100vw" height:"100vh"}}  >
        <ReactFlow
            fitView
            nodes={nodes}
            edges={edges}
            onMouseMove={handleMouseMove}
        >
            <div style={{zIndex:10,position:"absolute"}} >
                <LiveCursors cursorPos={cursorPos} />
            </div>
        </ReactFlow>
    </div>