Nudity and the WebSocket
Developer Tutorial · Personal Project
How to stream large SVG life-drawing illustrations over WebSockets in real time, line by line — with a custom UTF-8 compression scheme and a Node.js server.
You will learn more from your own labour of love than a search engine answer.
One teaches you to "learn", the other teaches you to copy. Do both in equal measure.
This project started with a question: can I stream a life-drawing SVG illustration to a browser, one pen stroke at a time, so that the sketch recreates itself in real time?
The answer is yes — and it is far more satisfying to watch than a static image load.
How did you make the SVG sketches?
You enrol in a life-drawing class, and instead of pencil and paper, you use a digital pen that records your pen strokes. Have you ever heard of Wacom Inkling? It is (unfortunately) a dead product — a great sadness, because that is what I use. It is an ink pen with a clip-on transceiver which you attach to a page; it records the pen strokes. When you are done, you use the Wacom software to convert the raw data into SVG illustrations, which you can then manipulate with a vector drawing program like Inkscape or Illustrator.
The successor to the Inkling appears to be the Neo Smartpen N2. Whatever product you use, the vital point is that it exports vector illustrations, and if it does SVG natively, that's awesome.
After many awkward months staring at naked people and desperately trying to improve your eye-hand coordination, you should have a body of work to use for this proof of concept. Alternatively, I have some SVG you could borrow. I highly recommend the naked people route first.
In principle, this technique will work for any SVG, provided the XML-DOM conforms to a specific structure — and we get to determine that schema.
Before we go technical, there is one thing to say about drawing with a ballpoint pen: not many people do it, for one straightforward reason — it is unforgiving. With a pencil you can smudge or erase mistakes; not with pen. Oh, no, siree. You see everything.
As an artist, I like this discipline; it forces you to commit to the stroke. And when the mistakes pile up, they also show how you searched for the line of the form and suggest how your eye explored the shapes and undulations as it struggled to transpose them onto the page. Almost as if a fly, dipped in ink, was walking around.
The great thing about an SVG pen is that you can edit your drawing afterwards. And I did exactly that — I could not remove a pen mark from the paper, but I could remove the path from the canvas, change fill and stroke and composition, and so on.

Full round trip — TL;DR
The pipeline goes: SVG document → JSON objects → compressed UTF-8 string → WebSocket → browser DOM.
Step 1 — SVG source
From the server, we start with a standard SVG document:
<svg width="598px" height="697px" viewBox="0 0 598 697"
xmlns="http://www.w3.org/2000/svg">
<title>portrait in 60 seconds</title>
<desc>(c) 2014 Bruce Thomas — Life drawing</desc>
<g id="Page-1" stroke="none" stroke-width="1"
fill="none" fill-rule="evenodd">
<g id="Sketch166" stroke="#9B9B9B"
stroke-width="0.0966" fill="#4A4A4A">
<path id="Shape"
d="M328.624,374.943 L329.252,373.2 ...very long..." />
</g>
</g>
</svg>
Step 2 — Parse into JavaScript object literals
Traverse the XML-DOM and turn each element into a flat JavaScript object:
{ type: "svg", attributes: { width: "598px", height: "697px" } }
{ type: "title", value: "portrait in 60 seconds" }
{ type: "desc", value: "(c) 2014 Bruce Thomas — Life drawing" }
{ type: "defs", value: "" }
{ type: "g", attributes: { id: "Page-1", stroke: "none",
"stroke-width": "1", fill: "none",
"fill-rule": "evenodd" } }
{ type: "g", attributes: { id: "Sketch166" } }
{ type: "path", attributes: { id: "Shape",
d: "M328.624,374.943 L329.252..." } }
Each element becomes a simple descriptor. The flat structure means you can JSON.stringify() each one and send them individually.
Step 3 — Compress the path data
The d attribute on <path> elements is the expensive part — a long string of coordinates. We apply a custom UTF-8 character substitution to compress repeated patterns. A number like 328.624 becomes a much shorter multi-byte character.
After compression, a path like:
M328.624,374.943 L329.252,373.2 L329.881,371.648 ...
becomes something like:
M328.624,374.943 LĂ9.252ĉ73ĕđē.881ę1ą48ĝ30.56ĈĊĪĂ...ɌȺțƝȯǜĠɧʮ͜ȱ
The browser "unzips" the string, reversing the substitution before parsing.
Step 4 — Stream over WebSocket
The Node.js server sends one path object per WebSocket message. For each sketch, it:
- Sends the
<svg>and container<g>elements first - Streams each
<path>as a compressed message - The browser decodes the message, creates the DOM element, and appends it
This produces the stroke-by-stroke animation effect: the illustration builds itself in real time, exactly as it was drawn.
The Node.js server
The server reads SVG files from disk, parses them into the flat object format, compresses path data, and pushes messages down the WebSocket connection. A simple loop with a configurable delay between strokes controls the animation speed.
const WebSocket = require('ws');
const fs = require('fs');
const { parseSVG, compress } = require('./svg-utils');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
const svgData = fs.readFileSync('./sketch.svg', 'utf8');
const elements = parseSVG(svgData);
elements.forEach((el, i) => {
setTimeout(() => {
const message = JSON.stringify({
type: el.type,
attributes: compress(el.attributes),
});
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
}, i * 30); // 30ms between strokes
});
});
The browser client
On the receiving end, a switch statement identifies the element type and constructs the corresponding DOM node:
const ws = new WebSocket('wss://your-server.example.com');
const svg = document.querySelector('svg#canvas');
ws.onmessage = ({ data }) => {
const { type, attributes } = JSON.parse(data);
switch (type) {
case 'path': {
const el = document.createElementNS(
'http://www.w3.org/2000/svg', 'path'
);
const decompressed = decompress(attributes);
Object.entries(decompressed).forEach(([k, v]) =>
el.setAttribute(k, v)
);
svg.appendChild(el);
break;
}
case 'g': {
// handle group elements
break;
}
// ... other cases
}
};
Each incoming message recreates one pen stroke. The result is a real-time playback of the original drawing session — the sketch rebuilding itself, line by line.
Why this is interesting
SVG is text. Text compresses well. WebSockets are fast. Life drawings have many short, similar path segments — ideal for dictionary-style compression. This combination makes streaming a large illustration feel instantaneous: the first stroke arrives before a traditional image would have finished loading.
The technique generalises to any structured SVG — diagrams, data visualisations, architectural drawings — anywhere you want to add a cinematic "building" effect without a video file.