Skip to content

Repository files navigation

📸 Selfie

Your HTML takes its own picture. No server, no cameraman.

Selfie is a powerful, 100% client-side sandbox designed to break down your styled HTML and export it into versatile, high-quality image formats. Built entirely on WebAssembly (WASM), your code and designs never leave your browser—guaranteeing complete privacy, zero queue times, and no server dependency.

✨ Features

  • 100% Client-Side Architecture: Everything from CSS parsing to frame rendering and video encoding happens right in your browser's memory.
  • Three Powerful Export Formats:
    • Dynamic SVGs: Retains CSS animations by automatically healing and embedding your HTML directly inside an SVG <foreignObject> tag.
    • Animated WebP: Powered by a multi-threaded wasm-webp worker, outputting 100% lossless, ultra-sharp UI animations.
    • Animated GIF: Powered by gifski-wasm to provide best-in-class cross-frame delta compression, ensuring crisp text and borders without the typical mosquito noise.
  • Dual-Engine CSS Pipeline:
    • UnoCSS (Wind3 Preset): Write instant, drop-in utility classes (Tailwind syntax) processed via a JIT engine.
    • LightningCSS: Automatically transpiles and injects vendor prefixes into standard CSS to ensure modern browser compatibility.
  • Shadow DOM Isolation: Your components are rendered in a perfectly isolated Shadow DOM, preventing any style leakage into or out of the app environment.
  • Smart Sizing & Scaling: Automatically calculates your component's dimensions or strictly enforces user-defined Target/Base resolutions.

🛠️ Architecture

  • Framework: React + TypeScript (Vite)
  • State Management: Jotai
  • Concurrency: Dedicated Web Workers handle all WASM processing (GIF/WebP) to ensure the main UI thread never blocks during heavy encoding
  • Styling: Shadcn UI
  • Linting: Oxlint

🚀 Local Development

To run the project locally, ensure you have Bun installed.

# Install dependencies
bun install

# Start the development server
bun run dev

# Run the strict typechecker
bun run typecheck

# Run the linter (oxlint)
bun run lint

# Build for production
bun run build

⚠️ Architectural Limits

Because Selfie is built purely on client-side WebAssembly, it is subject to the browser's strict memory limits (usually capped around 2GB-4GB). For animated exports, rendering engines require massive memory overhead to analyze pixel deltas.

To avoid Out-Of-Memory (OOM) browser crashes, animations are capped at a maximum of 15 seconds (or 900 frames). If you push the resolution, framerate, and duration to the maximum simultaneously, the WASM engines may crash.

🌐 CORS Proxy (Cloudflare Worker)

This project uses a Cloudflare Worker template and includes a simple worker endpoint for proxying external images. While this proxy is not enabled on the public demo page to prevent abuse, you can easily self-host it! The worker is already pre-configured out-of-the-box to support both source domain whitelisting (restricting which image URLs can be proxied) and CORS origin whitelisting (restricting which domains can call the API).

To configure the proxy, simply update the vars object in your wrangler.jsonc file to set the allowlists (ALLOWED_SOURCE_ORIGINS and ALLOWED_CORS_ORIGINS). Leave them as empty arrays [] to allow all origins and sources.

MIME Type Override Magic: The proxy natively accepts any image/* MIME type. Crucially, it also accepts text/plain and will automatically serve it back by rewriting the response header to image/svg+xml. Most traditional image hosting platforms will not allow you to host raw SVG files, let alone SVGs that heavily utilize <foreignObject>. However, there are endless simple plain text hosting services (like Pastebin, GitHub Gists, etc.). We take advantage of this by allowing you to host your raw SVG code on those simple text platforms, and the proxy seamlessly serves them to the browser as perfectly valid images!

High-Performance Caching: The proxy is deeply optimized for speed and bandwidth efficiency. It heavily leverages both the Cloudflare Edge Cache and the user's Browser Cache, caching image responses for lightning-fast subsequent loads.

Quick Usage Example

You can check out the worker's implementation in worker/index.ts. Once deployed, you simply append your target URL as a query parameter:

const proxyUrl = "https://your-worker.workers.dev/proxy-image?url=";
const targetImage = encodeURIComponent("https://example.com/image.txt");

// The proxy fetches the image.txt and serves it back as a valid image/svg+xml with proper CORS headers!
<img src={`${proxyUrl}${targetImage}`} />;

Note: The worker is completely optional! If you don't want to include the proxy when self-hosting, you can simply ignore it and deploy the static output generated by bun run build.

🤝 Contributing

Contributions are welcome! Feel free to open issues or submit Pull Requests for improvements or bug fixes.

About

Your HTML takes its own picture. No server, no cameraman.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages