Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Windows Tipps & SecurityBeyond the Build – September 2026(22.09.2026 um 00:36 Uhr)
Videos & KonferenzenGoogle for Developers: Understand the Gemma 4 model family(22.09.2026 um 01:00 Uhr)
Unix & Linux ServerSecurity: Zwei Probleme in gstreamer1-plugins-base (Red Hat)(21.09.2026 um 23:23 Uhr)
Unix & Linux ServerSecurity: Pufferüberlauf in corosync (Red Hat)(22.09.2026 um 00:49 Uhr)
Sichere ProgrammierungGitHub Enterprise adds credential inventory exports(21.09.2026 um 23:13 Uhr)
Sichere ProgrammierungYour First Factory: GtkListView and the Bind/Unbind Rhythm(22.09.2026 um 01:00 Uhr)
Windows Tipps & SecurityBeyond the Build – September 2026(22.09.2026 um 00:36 Uhr)
Videos & KonferenzenGoogle for Developers: Understand the Gemma 4 model family(22.09.2026 um 01:00 Uhr)
Unix & Linux ServerSecurity: Zwei Probleme in gstreamer1-plugins-base (Red Hat)(21.09.2026 um 23:23 Uhr)
Unix & Linux ServerSecurity: Pufferüberlauf in corosync (Red Hat)(22.09.2026 um 00:49 Uhr)
Sichere ProgrammierungGitHub Enterprise adds credential inventory exports(21.09.2026 um 23:13 Uhr)
Sichere ProgrammierungYour First Factory: GtkListView and the Bind/Unbind Rhythm(22.09.2026 um 01:00 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Building a collaborative whiteboard app using ReactJS, Socket.io and NodeJS

Hi fellow readers! I hope you’re doing great. In this article, we will build our collaborative whiteboard similar to Google Meet’s whiteboard. We are going to use HTML canvas with ReactJS to create our whiteboard and socket.io to implement …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Hi fellow readers! I hope you’re doing great. In this article, we will build our collaborative whiteboard similar to Google Meet’s whiteboard. We are going to use HTML canvas with ReactJS to create our whiteboard and socket.io to implement real-time communication.



Here’s how the project is gonna look when implemented. Let’s get started.



Project whiteboard



In the project directory, we will create folders, and create the frontend using the command npm create vite@latest and choose ‘react’ and ‘typescript’.






Create Canvas



In the front end, create a component Board. This component will include our Canvas and its functionalities.




import React, { useRef } from 'react';

const Board = () => {

const canvasRef = useRef<HTMLCanvasElement>(null);


return (
<canvas
ref={canvasRef}
width={600}
height={400}
style={{ backgroundColor: 'white' }}
/>
);
};


export default Board;






Here, we create a reference of the current state of the canvas using useRef hook which is now known as canvasRef. We have kept the background color to be white to create a whiteboard environment, it could be any color you want or none at all.






Draw on canvas



Now, we created the canvas, let’s see how we can draw/write on it.




    useEffect(() => {


// Variables to store drawing state
let isDrawing = false;
let lastX = 0;
let lastY = 0;


const startDrawing = (e: { offsetX: number; offsetY: number; }) => {
isDrawing = true;


[lastX, lastY] = [e.offsetX, e.offsetY];
};


// Function to draw
const draw = (e: { offsetX: number; offsetY: number; }) => {
if (!isDrawing) return;


const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}


[lastX, lastY] = [e.offsetX, e.offsetY];
};


// Function to end drawing
const endDrawing = () => {
isDrawing = false;
};


const canvas: HTMLCanvasElement | null = canvasRef.current;
const ctx = canvasRef.current?.getContext('2d');


// Set initial drawing styles
if (ctx) {
ctx.strokeStyle = black;
ctx.lineWidth = 5;


ctx.lineCap = 'round';
ctx.lineJoin = 'round';


}
// Event listeners for drawing
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', endDrawing);
canvas.addEventListener('mouseout', endDrawing);


return () => {
// Clean up event listeners when component unmounts
canvas.removeEventListener('mousedown', startDrawing);
canvas.removeEventListener('mousemove', draw);
canvas.removeEventListener('mouseup', endDrawing);
canvas.removeEventListener('mouseout', endDrawing);
};
}, []);







We are managing different mouse events on our canvas using JavaScript event listeners. As soon as the mouse is right-clicked on canvas, we perform the startDrawing() function, and when the mouse moves, draw() is invoked, and as soon as we leave the mouse click, the endDrawing() function is called. Let’s see this in detail.



We use the ctx variable to get the current canvas reference and perform actions on it. We have kept some default properties of our ctx variable, such as strokeStyle to be black, lineWidth is 5, lineCap, and lineJoin to be round.



We setup the event listeners on Canvas and also clean up the event listeners when the component unmounts or runs before the next effect. We are using isDrawing, lastX, and lastY variables to control the drawing state. Initially, isDrawing is set to false, until mousedown occurs. It changes the state of isDrawing to true. When the mouse moves, the ctx variable creates strokes according to the position of our cursor on the canvas. When we stop the right click, or move the cursor out of the canvas, the endDrawing() function is called which sets the isDrawing to false. With this, we can freely draw on canvas :)






Backend



Now, let’s add the feature to allow multiple users to use the whiteboard at the same time. Go to the backend directory, first, execute the command

npm install socket.io, and code the following in an index.js file.




const { Server } = require('socket.io');
const io = new Server({
cors: "http://localhost:5173/"
})
io.on('connection', function (socket) {


socket.on('canvasImage', (data) => {
socket.broadcast.emit('canvasImage', data);
});
});


io.listen(5000);






Here, we have initialized a new Socket.IO server instance named io. The cors option is set to allow connections from the specified URL (http://localhost:5173/), which is our front-end URL. If we don’t put this, our frontend will not be able to share resources with the backend.



The io.on(‘connection’) sets up an event listener for whenever a client connects to the Socket.IO server. Inside this, we have another event listener listening to the event ‘canvasImage’ from the front end via users.

When a user emits an event from the front end, it will contain the most recent copy of the canvas. We will receive the data using this event and then socket.broadcast.emit(‘canvasImage’, data) broadcasts the received canvasImage data to all connected clients except the client that sent the original 'canvasImage' event. Last, but not least, we start our server using the command node index.js which runs on port 5000.





Client Socket Connection



In Board.tsx, we will now make some changes. Before that, we’ll install 'socket.io-client' package in the frontend directory.




import React, { useRef, useEffect, useState } from 'react';
import io from 'socket.io-client';

const [socket, setSocket] = useState(null);

useEffect(() => {
const newSocket = io('http://localhost:5000');
console.log(newSocket, "Connected to socket");
setSocket(newSocket);
}, []);


useEffect(() => {

if (socket) {
// Event listener for receiving canvas data from the socket
socket.on('canvasImage', (data) => {
// Create an image object from the data URL
const image = new Image();
image.src = data;


const canvas = canvasRef.current;
// eslint-disable-next-line react-hooks/exhaustive-deps
const ctx = canvas.getContext('2d');
// Draw the image onto the canvas
image.onload = () => {
ctx.drawImage(image, 0, 0);
};
});
}
}, [socket]);






We’ll create a new useState hook named socket for managing sockets.

const newSocket = io('http://localhost:5000') establishes a connection with the server and client.



To get updates from all users using the whiteboard, we are listening for the ‘canvasImage’ event from the server using the useEffect hook. If we receive an event, we are going to draw that image on our current canvas.



So, shall we send the image to the server? Every time we draw? No, we send the image to the server when we are drawn i.e. once, we lift our right-click. Here’s the updated endDrawing() function.




        const endDrawing = () => {
const canvas = canvasRef.current;
const dataURL = canvas.toDataURL(); // Get the data URL of the canvas content


// Send the dataURL or image data to the socket
// console.log('drawing ended')
if (socket) {
socket.emit('canvasImage', dataURL);
console.log('drawing ended')
}
isDrawing = false;
};






Simple! With this, we are done with a collaborative whiteboard. But, if you want an option to adjust the color and size of the brush. Hold on, we are going to do it next.






Adjust Color and Size



We are going to use two states for controlling these two brush properties. In App.tsx, we are going to use onChange() function on the inputs to update each state.




import { useEffect, useState } from 'react';
import './App.css';
import Board from './component/Board';

const CanvasDrawing = () => {

const [brushColor, setBrushColor] = useState('black');
const [brushSize, setBrushSize] = useState<number>(5);

return (
<div className="App" >
<h1>Collaborative Whiteboard</h1>
<div>
<Board brushColor={brushColor} brushSize={brushSize} />
<div className='tools' >
<div>
<span>Color: </span>
<input type="color" value={brushColor} onChange={(e) => setBrushColor(e.target.value)} />
</div>
<div>
<span>Size: </span>
<input type="range" color='#fac176'
min="1" max="100" value={brushSize} onChange={(e) => setBrushSize(Number(e.target.value))} />
<span>{brushSize}</span>
</div>
</div>
</div>
</div>
);
};

export default CanvasDrawing;






Let’s add some CSS in App.css to make it look a bit more pretty.




.App {
background-color: #4158D0;
background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%);
height: 100vh;
width: 100%;
padding: 3em 1em;
display: flex;
flex-direction: column;
align-items: center;
}


.tools {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
width: 100%;
padding: 1em;
background-color: black;
color: white;
}


h1 {
margin-bottom: 1rem;
text-align: center;
}


input {
margin: 0 5px;
}


span {
font-size: 1.3em;
}




@media screen and (max-width: 600px) {
.tools {
flex-direction: column;
align-items: start;
}


}






Index.css




@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');


* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Roboto', sans-serif;
}






We are passing brushSize and brushColor states as props to the Board component.




interface MyBoard {
brushColor: string;
brushSize: number;
}


const Board: React.FC<MyBoard> = (props) => {

const { brushColor, brushSize } = props;

useEffect(() => {

// Variables to store drawing state
let isDrawing = false;
let lastX = 0;
let lastY = 0;
const startDrawing = (e: { offsetX: number; offsetY: number; }) => {
isDrawing = true;


console.log(`drawing started`, brushColor, brushSize);
[lastX, lastY] = [e.offsetX, e.offsetY];
};


// Function to draw
const draw = (e: { offsetX: number; offsetY: number; }) => {
if (!isDrawing) return;


const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}


[lastX, lastY] = [e.offsetX, e.offsetY];
};


// Function to end drawing
const endDrawing = () => {
const canvas = canvasRef.current;
const dataURL = canvas.toDataURL(); // Get the data URL of the canvas content


// Send the dataURL or image data to the socket
// console.log('drawing ended')
if (socket) {
socket.emit('canvasImage', dataURL);
console.log('drawing ended')
}
isDrawing = false;
};


const canvas: HTMLCanvasElement | null = canvasRef.current;
const ctx = canvasRef.current?.getContext('2d');


// Set initial drawing styles
if (ctx) {
ctx.strokeStyle = brushColor;
ctx.lineWidth = brushSize;


ctx.lineCap = 'round';
ctx.lineJoin = 'round';


}
// Event listeners for drawing
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', endDrawing);
canvas.addEventListener('mouseout', endDrawing);


return () => {
// Clean up event listeners when component unmounts
canvas.removeEventListener('mousedown', startDrawing);
canvas.removeEventListener('mousemove', draw);
canvas.removeEventListener('mouseup', endDrawing);
canvas.removeEventListener('mouseout', endDrawing);
};
}, [brushColor, brushSize, socket]);






In the useEffect, we are making changes ctx.strokeStyle = brushColor; and ctx.lineWidth = brushSize; and adding brushColor, brushSize, socket in the dependency array.






Responsive Canvas



We are going to make our canvas responsive with a bit of JavaScript. The window object to fetch the width and height of the device and accordingly, we will show the canvas.




    const [windowSize, setWindowSize] = useState([
window.innerWidth,
window.innerHeight,
]);


useEffect(() => {
const handleWindowResize = () => {
setWindowSize([window.innerWidth, window.innerHeight]);
};


window.addEventListener('resize', handleWindowResize);


return () => {
window.removeEventListener('resize', handleWindowResize);
};
}, []);




return (
<canvas
ref={canvasRef}
width={windowSize[0] > 600 ? 600 : 300}
height={windowSize[1] > 400 ? 400 : 200}
style={{ backgroundColor: 'white' }}
/>
);






We are using ‘resize’ event listener to update the state of the width and height of the windowSize state. And finally, we have completed our web app.



I hope you liked this project.

Thank you for reading!



If you have any doubts or wanna get a peek at the source code. You can check it out here.



Connect with me on-



Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building a collaborative whiteboard app using ReactJS, Socket.io and NodeJS

Thematisch verwandte Begriffe: Building, collaborative, whiteboard, using · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-49449 | Joplin is an open source note-taking and to-do application that organise…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick