Protocol
strokeStart (mousedown): { type: 'strokeStart', id, color, width } — other peers create a draft for this id.
strokeDraft (mousemove, throttled): { type: 'strokeDraft', id, points } — peers update that draft’s points and redraw so the line grows as you move.
stroke (mouseup): unchanged — final stroke; peers remove the draft and add the stroke to the main list.
Implementation
drafts: Map of id → { color, width, points } for in-progress strokes from others.
currentStrokeId: Set on mousedown and used for all messages for that stroke.
Throttling: strokeDraft is sent at most every 32ms (~30 fps) to limit traffic while keeping motion smooth.
Redraw: Draws committed strokes, then all drafts, then your own current stroke.
Clear: Clears both strokes and drafts.
finishStroke(): Sends one last strokeDraft with the full points before sending stroke, so the final segment isn’t missing on other peers.
This commit is contained in:
+55
-45
@@ -124,10 +124,14 @@
|
||||
let swarm = null;
|
||||
const connections = [];
|
||||
const strokes = [];
|
||||
const drafts = {}; // id -> { color, width, points } for peer strokes in progress
|
||||
let drawing = false;
|
||||
let currentPoints = [];
|
||||
let currentStrokeId = null;
|
||||
let currentColor = DEFAULT_COLOR;
|
||||
let currentWidth = DEFAULT_WIDTH;
|
||||
let lastDraftSend = 0;
|
||||
const DRAFT_THROTTLE_MS = 32;
|
||||
|
||||
function updatePeers() {
|
||||
peersEl.textContent = 'Peers: ' + connections.length;
|
||||
@@ -142,33 +146,26 @@
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
function drawStroke(s) {
|
||||
if (!s.points || s.points.length < 2) return;
|
||||
ctx.strokeStyle = s.color || DEFAULT_COLOR;
|
||||
ctx.lineWidth = s.width || DEFAULT_WIDTH;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(s.points[0][0], s.points[0][1]);
|
||||
for (let j = 1; j < s.points.length; j++) {
|
||||
ctx.lineTo(s.points[j][0], s.points[j][1]);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function redraw() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (let i = 0; i < strokes.length; i++) {
|
||||
const s = strokes[i];
|
||||
if (!s.points || s.points.length < 2) continue;
|
||||
ctx.strokeStyle = s.color || DEFAULT_COLOR;
|
||||
ctx.lineWidth = s.width || DEFAULT_WIDTH;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(s.points[0][0], s.points[0][1]);
|
||||
for (let j = 1; j < s.points.length; j++) {
|
||||
ctx.lineTo(s.points[j][0], s.points[j][1]);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let i = 0; i < strokes.length; i++) drawStroke(strokes[i]);
|
||||
for (const id in drafts) drawStroke(drafts[id]);
|
||||
if (drawing && currentPoints.length >= 2) {
|
||||
ctx.strokeStyle = currentColor;
|
||||
ctx.lineWidth = currentWidth;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(currentPoints[0][0], currentPoints[0][1]);
|
||||
for (let j = 1; j < currentPoints.length; j++) {
|
||||
ctx.lineTo(currentPoints[j][0], currentPoints[j][1]);
|
||||
}
|
||||
ctx.stroke();
|
||||
drawStroke({ points: currentPoints, color: currentColor, width: currentWidth });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +180,20 @@
|
||||
function handleMessage(data) {
|
||||
try {
|
||||
const msg = JSON.parse(new TextDecoder().decode(data));
|
||||
if (msg.type === 'stroke' && Array.isArray(msg.points) && msg.points.length >= 2) {
|
||||
if (msg.type === 'strokeStart' && msg.id) {
|
||||
drafts[msg.id] = {
|
||||
color: msg.color || DEFAULT_COLOR,
|
||||
width: msg.width || DEFAULT_WIDTH,
|
||||
points: []
|
||||
};
|
||||
redraw();
|
||||
} else if (msg.type === 'strokeDraft' && msg.id && Array.isArray(msg.points)) {
|
||||
if (drafts[msg.id]) {
|
||||
drafts[msg.id].points = msg.points;
|
||||
redraw();
|
||||
}
|
||||
} else if (msg.type === 'stroke' && Array.isArray(msg.points) && msg.points.length >= 2) {
|
||||
delete drafts[msg.id];
|
||||
strokes.push({
|
||||
id: msg.id,
|
||||
points: msg.points,
|
||||
@@ -193,6 +203,7 @@
|
||||
redraw();
|
||||
} else if (msg.type === 'clear') {
|
||||
strokes.length = 0;
|
||||
for (const id in drafts) delete drafts[id];
|
||||
redraw();
|
||||
}
|
||||
} catch (e) {}
|
||||
@@ -202,7 +213,9 @@
|
||||
if (!swarm) return;
|
||||
e.preventDefault();
|
||||
drawing = true;
|
||||
currentStrokeId = Date.now() + '-' + Math.random();
|
||||
currentPoints = [getCanvasCoords(e)];
|
||||
broadcast({ type: 'strokeStart', id: currentStrokeId, color: currentColor, width: currentWidth });
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
@@ -210,16 +223,21 @@
|
||||
e.preventDefault();
|
||||
currentPoints.push(getCanvasCoords(e));
|
||||
redraw();
|
||||
const now = Date.now();
|
||||
if (now - lastDraftSend >= DRAFT_THROTTLE_MS) {
|
||||
lastDraftSend = now;
|
||||
broadcast({ type: 'strokeDraft', id: currentStrokeId, points: currentPoints });
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', (e) => {
|
||||
if (!drawing) return;
|
||||
e.preventDefault();
|
||||
function finishStroke() {
|
||||
if (!drawing || !currentStrokeId) return;
|
||||
drawing = false;
|
||||
if (currentPoints.length >= 2) {
|
||||
broadcast({ type: 'strokeDraft', id: currentStrokeId, points: currentPoints });
|
||||
const stroke = {
|
||||
type: 'stroke',
|
||||
id: Date.now() + '-' + Math.random(),
|
||||
id: currentStrokeId,
|
||||
points: currentPoints,
|
||||
color: currentColor,
|
||||
width: currentWidth
|
||||
@@ -227,27 +245,19 @@
|
||||
strokes.push(stroke);
|
||||
broadcast(stroke);
|
||||
}
|
||||
currentStrokeId = null;
|
||||
currentPoints = [];
|
||||
redraw();
|
||||
}
|
||||
|
||||
canvas.addEventListener('mouseup', (e) => {
|
||||
if (!drawing) return;
|
||||
e.preventDefault();
|
||||
finishStroke();
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseleave', () => {
|
||||
if (drawing) {
|
||||
drawing = false;
|
||||
if (currentPoints.length >= 2) {
|
||||
const stroke = {
|
||||
type: 'stroke',
|
||||
id: Date.now() + '-' + Math.random(),
|
||||
points: currentPoints,
|
||||
color: currentColor,
|
||||
width: currentWidth
|
||||
};
|
||||
strokes.push(stroke);
|
||||
broadcast(stroke);
|
||||
}
|
||||
currentPoints = [];
|
||||
redraw();
|
||||
}
|
||||
if (drawing) finishStroke();
|
||||
});
|
||||
|
||||
btnClear.addEventListener('click', () => {
|
||||
|
||||
Reference in New Issue
Block a user