I'm facing an issue in my real-time drawing application built using Next js, Socket.IO, and HTML5 canvas. The problem arises when a new client joins the room; it receives the canvas state, but all existing clients also get repainted, which I want to avoid. I'm aiming for only the new client to receive the canvas state and updates in real-time, without affecting the existing clients' canvas.
Problem : When a new client joins the drawing room, it receives the current canvas state as expected. However, this triggers updates for all existing clients, causing their canvases to be repainted. This behavior disrupts the drawing experience for existing users and can lead to confusion.
Steps to Reproduce: Open the drawing application in multiple browser tabs or devices. Join the same room with one client and start drawing on the canvas. Join the same room with another client (new client) and observe that not only does the new client receive the canvas state, but all existing clients also receive canvas updates.
Expected Behavior: When a new client joins the room, only the new client's canvas should receive updates, allowing them to see the ongoing drawing activity in real-time. Existing clients should not be affected by the arrival of a new client and should continue drawing without interruption.
server side:
io.on('connection', (socket) => {
console.log( socket.id ,' connected');
socket.on('client-ready', (roomName) => {
console.log("done jaa rha")
io.to(roomName).emit('get-canvas-state',roomName)
// socket.emit('get-canvas-state',roomName)
// io.to(roomName).emit('get-canvas-state')
})
socket.emit('sid',socket.id);
socket.on('join-room',roomname=>{
socket.join(roomname)
io.to(roomname).emit('get-canvas-state',roomname)
console.log(socket.id,"joined",roomname)
})
socket.on('canvas-state', (state) => {
console.log('received canvas state')
console.log("line 29",state.roomName)
if(state.roomName){
// socket.emit('canvas-state-from-server',state.url);
io.to(state.roomName).emit('canvas-state-from-server',state.url)
}
})
socket.on('leave-room',roomName=>{
socket.leave(roomName)
console.log(socket.id,"leaves",roomName)
})
socket.on('draw', (data) => {
// console.log("line 34",data)
// socket.broadcast.emit('draw', data); // Broadcast to other clients
console.log("the roomanme is : ", data.roomName)
io.to(data.roomName).emit('draw',data);
});
socket.on('write',(data)=>{
console.log(data)
// io.to(data.roomName).emit('write',data);
io.to(data.roomName).emit('write',data)
})
socket.on('clear', (roomName) => io.to(roomName).emit('clear'))
socket.on('disconnect', () => {
console.log( socket.id, ' disconnected');
});
});
page.js /draw
const Page = () => {
const [color, setColor] = useColor("#561ecb");
const [socket, setSocket] = useState(null);
const [isEraser, setIsEraser] = useState(false);
const [points, setpoints] = useState({});
const [lineWidth, setLineWidth] = useState(5);
const [currentCanvasState, setCurrentCanvasState] = useState(null)
const [id, setId] = useState("");
const [text, settext] = useState('')
// const [roomname, setroomname] = useState('')
const { roomCreated, setRoomCreated, roomName, setRoomName, name, setName } =
useRoomContext();
const { canvasRef, clear, isDrawing } = UseDraw({
color,
socket,
isEraser,
lineWidth,text
});
const { roomJoined, setRoomJoined } = useRoomContext();
useEffect(() => {
const newSocket = io("http://localhost:3001");
const ctx = canvasRef.current?.getContext("2d");
newSocket.on("connect", () => {
setSocket(newSocket);
});
newSocket.emit("client-ready", roomName);
newSocket.on("sid", (sid) => {
setId(sid);
});
newSocket.on("get-canvas-state", (rname) => {
if (!canvasRef.current?.toDataURL()) return;
newSocket.emit("canvas-state", {
roomName: rname,
url: canvasRef.current.toDataURL(),
});
});
newSocket.on("canvas-state-from-server", (state) => {
if (state !== currentCanvasState) {
setCurrentCanvasState(state);
const img = new Image();
img.src = state;
img.onload = () => {
ctx?.drawImage(img, 0, 0);
};
}
});
newSocket.on("draw", (data) => {
if (!ctx) return;
redraw(data, ctx);
});
newSocket.on('write',data=>{
if(!ctx) return
ctx.fillText(data.text,data.x,data.y)
})
newSocket.on("clear", clear);
return () => {
newSocket.disconnect();
newSocket.off("draw-line");
newSocket.off("get-canvas-state");
newSocket.off("canvas-state-from-server");
newSocket.off("draw");
newSocket.off("clear");
newSocket.off('write')
};
}, [canvasRef]);
useEffect(() => {
if (socket && roomJoined) {
socket.emit("join-room", roomName);
}
}, [roomJoined, socket, roomName]);
useEffect(() => {
if (socket && roomCreated) {
setRoomName(id);
// console.log(roomName);
socket.emit("join-room", roomName);
}
}, [roomCreated, socket]);
useEffect(() => {
console.log(isDrawing)
settext('')
}, [isDrawing])
const handlePoints = (e) => {
const points = ComputePoints(e, canvasRef);
setpoints(points);
};
const handleClearCanvas = () => {
socket.emit("clear", roomName);
};
const leaveRoom = () => {
socket.emit("leave-room", roomName);
socket.disconnect();
};}
useDraw.js
[![useEffect(() => {
// console.log(roomName)
const prevPoints = { x: null, y: null };
const startWrite = (e) => {
if(action=='writing') return;
setaction('writing');
setisDrawing(true);
}
const stopWrite = (e) => {
if(action=='writing'){
const ctx = canvasRef.current?.getContext('2d');
const currentPoints = computePointsinCanvas(e);
ctx.font = "30px Arial"
console.log(text)
// ctx.fillText(text,currentPoints.x,currentPoints.y)
emitWriteData(text,currentPoints.x,currentPoints.y,socket);
setaction('');
setisDrawing(false);
}
}
const emitWriteData = (text,x,y,socket)=>{
if(socket){
socket.emit('write',{text,x,y,roomName});
}
}
const canvas = canvasRef.current;
const computePointsinCanvas = (e) => {
const canvas = canvasRef.current;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return { x, y };
};
if (canvas) {
canvas.addEventListener('mousedown', startDraw);
canvas.addEventListener('click', stopWrite);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('click',startWrite)
canvas.addEventListener('mouseup', stopDraw);
canvas.addEventListener('mouseout', stopDraw);
return () => {
canvas.removeEventListener('mousedown', startDraw);
canvas.removeEventListener('click', stopWrite);
canvas.removeEventListener('mousemove', draw);
canvas.removeEventListener('mouseup', stopDraw);
canvas.removeEventListener('mouseout', stopDraw);
canvas.removeEventListener('click',startWrite)
// socket.off('draw')
};
}
}, \[color,socket,roomName,isEraser,lineWidth,text,action\]);
return {
canvasRef,clear,isDrawing
};
I'm seeking guidance on how to modify the client-side and server-side code to achieve the desired behavior, where only the new client receives canvas updates upon joining the room. Any insights, suggestions, or code modifications would be greatly appreciated to resolve this issue and ensure smooth real-time communication while drawing.
