DEV Community

Mike Chen
Mike Chen

Posted on

How I Built a Zero-Server Image Editor for 0¥/Month

How I Built a Zero-Server Image Editor for 0¥/Month

I needed a simple tool to make stickers for WeChat. Existing online editors either upload your images to their servers (privacy nightmare), require registration, or watermark your output.

So I built 靓图 (LiangTu) — a pure client-side image editor that runs entirely in the browser. No server, no uploads, no account. Just drag, edit, download.

Here's the architecture.

The Stack: Zero Dependencies on the Server

Browser (100% of logic)
├── Canvas API       — rendering pipeline
├── gif.js           — GIF encoding (Web Workers)
├── FileReader API   — local image loading
└── No backend. None.
Enter fullscreen mode Exit fullscreen mode

That's it. The entire app is a static HTML/CSS/JS page served by Nginx. No Node.js, no database, no API routes. The server's job is to ship bytes once and shut up.

What It Does

Three tools, one domain:

Tool What it does Tech
Sticker Editor Add text layers, filters, watermarks, crop, compress Canvas 2D
GIF Maker Drag multiple images → reorder → adjust speed → export animated GIF gif.js + Web Workers
Image Compressor Quality slider with live preview Canvas toDataURL

The sticker editor has 8 tools (crop, text with 14 Chinese fonts, background fill, flip, filters, adjustments, watermark, compress) plus a full layer system with z-ordering, rotation, and drag-to-reposition.

The Layer System

This was the hardest part. Each text element is an independent layer with:

layer = {
  text: '你好',
  x: canvasW / 2,
  y: canvasH - 40,
  fontSize: 24,
  fontFamily: 'Microsoft YaHei',
  textColor: '#FFFFFF',
  strokeColor: '#000000',
  strokeWidth: 2,
  rotation: 0
}
Enter fullscreen mode Exit fullscreen mode

The render loop draws background → layers (top to bottom) → watermark. Hit testing for drag uses ctx.measureText() and rotation-aware bounding boxes. Selection shows a dashed outline with rotation handles.

GIF Encoding in the Browser

For the GIF maker, I use gif.js — a pure JavaScript GIF encoder that runs in Web Workers. Users drag in multiple images, reorder them, set the delay per frame, and hit export.

const gif = new GIF({
  workers: 2,
  quality: 10,
  width: maxWidth,
  height: maxHeight,
  workerScript: 'gif.worker.js'
});

frames.forEach((frame, i) => {
  gif.addFrame(canvas, { delay: speeds[i] * 100 });
});

gif.on('finished', blob => {
  const url = URL.createObjectURL(blob);
  // show preview + auto-download
});
gif.render();
Enter fullscreen mode Exit fullscreen mode

The trick is normalizing all frames to the same dimensions (largest width × largest height), centering smaller images on a white background.

Why "100% Local" Matters

Every image stays in the browser. The <input type="file"> loads directly into a FileReader, then to an Image object, then to Canvas. The download is canvas.toBlob(). At no point does any pixel leave the user's machine.

For Chinese users especially, uploading photos to unknown servers is a hard no. Making "no upload" the core feature was the right call.

Cost: ~16¥/Month

  • Domain: liangtu.cc — 29¥/year
  • Server: Tencent Cloud Lighthouse (2 vCPU, 2GB RAM, 4Mbps) — 192¥/year
  • Total: ~16¥/month (~$2.20 USD)

No serverless functions, no CDN tiers, no database. A single Nginx server serving static files. The 4Mbps bandwidth handles ~3,000 page views/month with plenty of headroom.

The Traffic Problem

Here's where I'm honest: I built the product. The SEO is decent. But nobody's finding it (~5 real visitors/day).

If you've successfully grown a free tool site from zero, I'd love to hear how. Drop a comment or find me at github.com/fengloulai/liangtu.


Try it: liangtu.cc

Source: github.com/fengloulai/liangtu

Top comments (0)