Introduction
In this tutorial, we’re going to build a real-time collaborative whiteboard (blackboard in our case) app for HarmonyOS using ArkTS/ArkUI and WebSockets.
The app will let multiple users draw on a shared canvas at the same time, with every stroke syncing live across devices via a simple WebSocket server.
Here is what we are going to build:
Required Dependencies
For the WebSocket server:
- Node.js
- ws WebSocket library
Install with:
npm init -y
npm install ws
For the HarmonyOS ArkTS app:
- HarmonyOS 4+ SDK
- ArkTS/ArkUI project in DevEco Studio
- Required permission in module.json5:
"requestPermissions": [
{ "name": "ohos.permission.INTERNET"}
]
WebSocket Server Implementation
Our server will listen for drawing events from clients and broadcast them to all other connected clients in real-time.
File: server.js:
// Import the ws library
const WebSocket = require('ws');
// Create WebSocket server listening on port 8000
const wss = new WebSocket.Server({ port: 8000 });
wss.on('connection', (ws) => {
console.log('Client connected');
// Listen for incoming messages from this client
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
// Handle "draw" events by broadcasting to other clients
if (Array.isArray(data) && data[0] === 'draw') {
wss.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
}
// Handle "clear" events by telling everyone to clear their canvas
if (Array.isArray(data) && data[0] === 'clear') {
wss.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(['clear']));
}
});
}
} catch (err) {
console.error('Invalid JSON:', err);
}
});
});
// Server ready event
wss.on('listening', () => {
console.log('WebSocket is listening on port 8000');
});
// Handle errors
wss.on('error', (error) => {
console.error('WebSocket server error:', error);
});
How it works:
- Clients send either [“draw”, strokeData] or [“clear”] messages.
- Server relays them to all other connected clients in real time. Start the server with:
node server.js
ArkTS Project
Next, let’s build the HarmonyOS app that:
✅ Connects to the WebSocket server
✅ Lets the user draw on a canvas
✅ Sends strokes to the server
✅ Receives strokes from others and draws them
We’ll use:
- ArkUI’s Canvas component to render strokes.
- @kit.NetworkKit to manage WebSocket connections.
WebSocket Configuration
import { webSocket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
// Emulator localhost address
const SOCKET_URL = "ws://10.0.2.2:8000";
💡 Note: If app is on emulator we need to use 10.0.2.2 instead of localhost to connect to your development machine.
State and WebSocket Connection
@State strokes: Stroke[][] = [[]]; // Collection of all drawn strokes
private socket: webSocket.WebSocket | undefined;
aboutToAppear(): void {
this.connectWebSocket();
}
connectWebSocket() {
this.socket = webSocket.createWebSocket();
// When the connection opens
this.socket.on('open', (err, value) => {
console.log("[SOCKET] Connected to server");
});
// Handle incoming messages
this.socket.on('message', (err, value) => {
if (typeof value === 'string') {
const response = JSON.parse(value);
if (response[0] === 'draw') {
const newStroke: Stroke = response[1];
this.strokes[this.strokes.length - 1].push(newStroke);
this.redraw();
}
if (response[0] === 'clear') {
this.strokes = [[]];
this.redraw();
}
}
});
// Connect to the WebSocket server
this.socket.connect(SOCKET_URL, (err, value) => {
console.log(err ? "[SOCKET] Connection failed" : "[SOCKET] Connected successfully");
});
}
What this does:
- Opens a WebSocket connection on app start.
- Receives draw or clear events and updates the local canvas.
Canvas Setup and Touch Handling
// Prepare the canvas background and border
onCanvasReady() {
this.context.fillStyle = '#000'; // Black background
this.context.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
this.context.strokeStyle = '#888';
this.context.lineWidth = 2;
this.context.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
this.context.strokeStyle = '#FFFFFF'; // Drawing color
this.context.lineWidth = 2;
}
// Handle user touch input
onCanvasTouch(event: TouchEvent) {
const newStroke: Stroke = {
x: event.touches[0].x,
y: event.touches[0].y
};
// Save stroke locally
this.strokes[this.strokes.length - 1].push(newStroke);
// Send stroke to other clients
this.sendDraw(newStroke);
// Update canvas
this.redraw();
}
Sending Drawing Events
// Send the user's stroke to the server
sendDraw(stroke: Stroke) {
if (this.socket) {
const payload = JSON.stringify(["draw", stroke]);
this.socket.send(payload, (err) => {
if (!err) {
console.log("[SOCKET] Stroke sent successfully");
}
});
}
}
Clearing the Canvas
// Clear the local canvas and notify others
clearCanvas() {
this.strokes = [[]];
if (this.socket) {
const payload = JSON.stringify(['clear']);
this.socket.send(payload, (err) => {
if (!err) {
console.log("[SOCKET] Cleared canvas");
}
});
}
this.redraw();
}
Redrawing the Canvas
// Redraw all strokes from history
redraw() {
this.context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
this.onCanvasReady();
this.strokes.forEach(path => {
if (path.length < 2) return;
this.context.beginPath();
this.context.moveTo(path[0].x, path[0].y);
for (let i = 1; i < path.length; i++) {
this.context.lineTo(path[i].x, path[i].y);
}
this.context.stroke();
});
}
Building the UI
build() {
Column() {
// Canvas area for drawing
Canvas(this.context)
.onTouch((event) => this.onCanvasTouch(event))
.onReady(() => this.onCanvasReady())
.width(CANVAS_WIDTH)
.height(CANVAS_HEIGHT)
Blank()
// Button to clear the canvas
Button('Clear')
.onClick(() => this.clearCanvas())
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
Pro Tip
✨ Emulator Networking Tip:
When testing in DevEco Studio’s Android-based emulator, always use 10.0.2.2 instead of localhost or 127.0.0.1 to access your development machine’s localhost.
On physical devices, ensure both your server and the device are on the same local network.
Conclusion
And there you have it — a complete real-time collaborative drawing app for HarmonyOS built with ArkTS and ArkUI!
You learned to:
✅ Create a Node.js WebSocket server.
✅ Handle drawing events on a canvas in ArkTS.
✅ Sync drawing data across devices in real-time.
✨ Check out the GIF above to see how it all works in action!
Mastering WebSockets and Canvas in HarmonyOS unlocks tons of collaborative app ideas. Happy coding!
Top comments (0)