gigachad80/Word-Diff
Word, line, char & image diff : 100% in your browser. No server, no upload, no tracking.
Word-Diff
Private Text and image diff that runs entirely in your browser. No server. No account. No upload. One HTML file.
๐ Live Demo ๐ Live Demo
Table of Contents
- What Makes It Different
- Live Demo
- Features at a Glance
- Syntax Highlighting Languages
- How It Works
- Size & Performance Limits
- Deployment
- Privacy
- License
What Makes It Different
| Feature | Word-Diff | diffchecker.com | Mergely |
|---|---|---|---|
| ๐ฐ Cost | Forever free & open source | Free + $6โ15/mo Pro | Free + $9/mo Pro |
| ๐ Privacy | 100% local, zero server | Web version may send data | Web version may send data |
| Always | Desktop only ($49) | โ | |
| ๐จ Syntax highlighting | 16 languages, free | Pro only | โ |
| ๐ผ๏ธ Image diff | โ | Pro only | โ |
| ๐ Share via URL | Hash-based, no server | Server-stored | Server-stored |
| ๐ Similarity score | โ | โ | โ |
| ๐ค Export | Self-contained HTML | PDF (Pro) | โ |
| ๐ฆ Self-hostable | Yes, one file | โ | Paid plan only |
| ๐ Interactive merge | โ | โ | โ |
Live Demo
open index.html
๐ Live Demo ๐ Live Demo
Or
Deploy to GitHub Pages in under 2 minutes . See Deployment.
Features at a Glance
- 3 diff modes - Word, Line, Character
- 2 view modes - Inline unified and Side-by-Side
- Syntax highlighting - 16 languages via Prism.js, applied after diffing so you see both diff color and language color simultaneously
- Image diff - pixel-by-pixel canvas comparison with diff overlay, blend, and individual views
- Share via URL - both texts encoded into the URL hash, no server involved
- File upload - drag-and-drop or file picker, auto-detects language from extension
- Ignore whitespace / Ignore case - toggle before comparing
- Changed-only mode - hides unchanged lines
- Navigation arrows - jump between changes with a counter
- Similarity score - percentage bar showing how close the two texts are
- Per-panel live counter - lines / words / characters updated as you type
- Export - saves the rendered diff as a self-contained HTML file
- Swap / Clear - utility actions
Syntax Highlighting Languages
| Language | Mode |
|---|---|
| JavaScript | Full syntax |
| TypeScript | Full syntax |
| Python | Full syntax |
| Go | Full syntax |
| Rust | Full syntax |
| C | Full syntax |
| C++ | Full syntax |
| Java | Full syntax |
| Bash / Shell | Full syntax |
| CSS | Full syntax |
| HTML / XML | Markup |
| JSON | Full syntax |
| YAML | Full syntax |
| SQL | Full syntax |
| Markdown | Full syntax |
Language auto-detects from file extension on drag-and-drop (.py โ Python, .go โ Go, .ts โ TypeScript, etc). Set manually from the dropdown otherwise. Plain text mode skips highlighting entirely.
How It Works
Text Diff
Word-Diff tokenizes both inputs into words, lines, or characters depending on the selected mode. It then runs an LCS (Longest Common Subsequence) algorithm - the same underlying algorithm used by git diff - which finds the minimum set of insertions and deletions that transforms one sequence into the other.
The result is grouped back into line-level records. Changed lines get a secondary token-level diff applied inline: deleted tokens are struck through in red, added tokens highlighted in green. Unchanged lines are dimmed and collapsed in groups when there are more than 10 in a row.
Syntax highlighting via Prism.js is applied per-line after diffing. The highlighted HTML is injected into the diff output, so both the diff color (which line changed) and the language color (what kind of token it is) are visible at the same time.
Image Diff
- Both images are drawn onto separate off-screen
<canvas>elements sized to the larger of the two. Smaller images are padded with transparent pixels. ctx.getImageData()extracts the raw RGBA pixel array from each canvas. Each pixel is 4 bytes: red, green, blue, alpha.- The two arrays are iterated in a single loop. For each pixel, the absolute difference of R, G, and B channels is averaged. Pixels with an average delta above 10 (on a 0โ255 scale) are counted as different.
- An output canvas is written: different pixels are painted red, unchanged pixels are rendered at 30% brightness to give context without distraction.
- Stats calculated: total pixels, different pixel count, percentage different, similarity percentage.
Four view modes render to the same canvas by re-drawing from stored ImageData objects: diff overlay, 50/50 blend, image A only, image B only.
Share via URL
- Raw text from both panels, selected mode, and selected language are wrapped in a JSON object:
{ a, b, mode, lang }. - The JSON string is encoded to base64 via
btoa()after URI-encoding for Unicode safety. - The base64 string is appended to the current page URL as a hash fragment:
#d=<base64>.
No network request is made. The URL is generated locally.
When someone opens the link, the page reads location.hash, strips #d=, decodes the base64 back to JSON, populates both panels, and auto-runs the comparison.
PDF Diff
Not supported. Word-Diff does not process PDF files.
Workarounds:
- Text-based PDF - copy text out of the PDF, paste into Word-Diff as plain text.
- Scanned or layout-sensitive PDF - screenshot each page, use Word-Diff image diff per page.
True static PDF diff would require bundling PDF.js (~400KB), rendering pages to canvas, then running the image diff pipeline on each render. Technically possible, not currently implemented.
Size & Performance Limits
| Limit | Value |
|---|---|
| Text input size | No limit (browser memory only) |
| LCS algorithm cap | 6,000 tokens - positional diff fallback above this |
| Accurate diff - word mode | ~50,000 characters |
| Accurate diff - line mode | ~6,000 lines |
| Share URL - safe cross-browser | ~700KB combined text |
| Share URL - Chrome max | ~2MB |
| Share URL - Firefox reliable max | ~65KB |
| Image - safe limit | 3,000ร3,000px / under 8MB file size |
| Image - hard limit | Browser RAM (no code-level cap) |
| Not supported |
LCS fallback explained: Above 6,000 tokens, Word-Diff falls back to a positional zip diff - it compares token at index N in A against token at index N in B. This runs in O(n) time regardless of size, so it never freezes. It is less accurate than LCS for content where lines or words were inserted (which shifts all subsequent positions), but it handles large files without any browser slowdown.
Image memory cost: width ร height ร 4 bytes ร 3 (canvas A + canvas B + output canvas). A 4K image (3840ร2160) costs approximately 100MB RAM. A 12MP photo costs approximately 150MB. Word-Diff applies no resolution downscaling before diffing.
Share URL - no compression: Base64 adds ~33% overhead. 1MB of raw combined text becomes ~1.35MB in the URL. No LZ-string or deflate compression is applied. Adding it would raise the practical ceiling 3โ5x for typical code or prose.
CORS on images: Images must be loaded from your local filesystem via drag-and-drop or file picker. Cross-origin image URLs cannot be read by canvas without CORS headers - this is a browser security rule, not a Word-Diff limitation.
Deployment
# Netlify / Vercel
# drag the file into the deploy UI - done
# local
open index.html
# or
python3 -m http.server 8080No build step. No npm. No config. Prism.js and fonts load from CDN on first use. The diff engine and image processing work offline after that.
Multiple simultaneous users on GitHub Pages: no issue. Each visitor runs their own isolated copy in their own browser tab. No shared state, no backend, nothing to bottleneck. GitHub Pages has a 100GB/month bandwidth soft limit - essentially impossible to hit with a text tool.
Note
Offline vs Online - what actually works when?
Once the page is loaded in your browser, all core features work with no internet:
diff engine, image diff, share URL generation, file upload, beautify, and export.
Internet is only needed for:
- First load - to fetch Prism.js (syntax highlighting) and Google Fonts from CDN
- URL import (๐ button) - fetching remote files always needs internet
- Reloading the page if hosted on Vercel/Netlify/GitHub Pages
Fonts fall back to monospace / sans-serif automatically if CDN is unreachable.
Syntax highlighting silently falls back to plain text if Prism fails to load.
Tip
To make Word-Diff fully offline (no internet ever needed):
- Download the 4 Prism files from cdnjs - open each URL in your browser and Save As:
https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.csshttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js- All the
prism-*.min.jslanguage components you need
- Save them next to
index.htmlin a folder, e.g.prism/ - In
index.html, replace the CDN<link>and<script>tags with local paths:<!-- BEFORE --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> <!-- AFTER --> <link rel="stylesheet" href="prism/prism-tomorrow.min.css"> <script src="prism/prism.min.js"></script>
- Remove the two Google Fonts
<link>tags at the top of<head>and replace the font variables in:root:/* BEFORE */ --mono: 'IBM Plex Mono', monospace; --sans: 'Space Grotesk', sans-serif; /* AFTER */ --mono: 'Courier New', monospace; --sans: system-ui, sans-serif;
After these 4 steps, index.html works by just double-clicking the file - no server, no internet ever needed.
Privacy
Two external requests on first load: Google Fonts (Space Grotesk + IBM Plex Mono) and Prism.js from cdnjs. Neither receives your text or images. After those assets load, the page functions without any network access.
No text, no images, no diffs, no metadata leave your machine at any point. The Share URL feature encodes data into the URL hash fragment - hash fragments are never sent to servers by browsers.
License
MIT