WebAssemblyRustPerformanceImage ProcessingHybrid Apps

WebAssembly + JS: Running Photoshop-Level Apps in the Browser

S
Systems Engineer
Featured Guide 30 min read

JavaScript is Fast.
But Wasm is Metal.

V8 is a miracle of engineering, but it still has garbage collection pauses and JIT overhead.

WebAssembly (Wasm) runs binary code at near-native speeds. It's the secret weapon behind Figma, Photoshop Web, and Google Earth.

02. The Hybrid Model

Don't rewrite your React app in Rust (Yew/Leptos) unless you really need to. The winning strategy in 2026 is Hybrid:

  • UI / State / Network: JavaScript (React/Angular)
  • Heavy Compute (Image/Video/Crypto): WebAssembly (Rust/C++)
JS Main Thread
(UI Updates)
⬇️ ArrayBuffer ⬆️
Wasm Worker
(Number Crunching)

03. Rust to Wasm in 3 Steps

  1. // 1. Write Rust Function
    #[wasm_bindgen]
    pub fn
    grayscale_image
    (data: &mut [u8]) {'{'} ... {'}'}
  2. // 2. Build
    wasm-pack build --target web
  3. // 3. Import in JS
    import
    init, {'{'} grayscale_image {'}'}
    from
    './pkg/image_lib.js';

05. The Senior Engineer's Take

When NOT to use Wasm

Wasm has a "boundary cost." Marshalling data (copying strings/objects) between JS memory and Wasm memory can be slower than just doing the math in JS for small tasks.

Rule of Thumb: Only use Wasm if the computation takes >100ms in JavaScript or involves complex binary data processing (parsers, encoders, crypto).

Interactive Playground

import React, { useState, useEffect } from 'react';

// 🦀 Wasm Transformation Playground

export default function WasmDemo() {
    const [isProcessing, setIsProcessing] = useState(false);
    const [progress, setProgress] = useState(0);
    const [method, setMethod] = useState('js'); // 'js' or 'wasm'
    const [metrics, setMetrics] = useState({ time: 0, fps: 60 });
    
    // Simulate processing
    const processImage = () => {
        setIsProcessing(true);
        setProgress(0);
        setMetrics({ time: 0, fps: 60 });
        
        const startTime = performance.now();
        const duration = method === 'js' ? 2000 : 300; // JS is slow, Wasm is fast
        const intervalTime = 20;
        
        const sim = setInterval(() => {
            setProgress(old => {
                const step = 100 / (duration / intervalTime);
                const next = old + step;
                
                // Simulate Main Thread blocking for JS
                if (method === 'js') {
                     // Random frame drops
                     setMetrics(m => ({ ...m, fps: Math.max(5, 60 - Math.random() * 50) }));
                } else {
                     // Wasm runs on worker, smooth FPS
                     setMetrics(m => ({ ...m, fps: 60 }));
                }

                if (next >= 100) {
                    clearInterval(sim);
                    setIsProcessing(false);
                    setMetrics(m => ({ ...m, time: method === 'js' ? 2.1 : 0.32, fps: 60 }));
                    return 100;
                }
                return next;
            });
        }, intervalTime);
    };

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
            <h3 className="text-3xl font-black text-gray-900 dark:text-white mb-8 flex items-center gap-3">
                <span className="text-orange-500">⚙️</span> Wasm Image Processing
            </h3>
            
            <div className="flex bg-slate-200 dark:bg-slate-900 p-1 rounded-xl mb-8 w-fit">
                    <button 
                        onClick={() => setMethod('js')}
                        className={`px-6 py-2 rounded-lg font-bold text-sm transition-all ${method === 'js' ? 'bg-white dark:bg-slate-800 shadow text-yellow-600' : 'text-slate-500'}`}
                    >
                        JavaScript (Main Thread)
                    </button>
                    <button 
                        onClick={() => setMethod('wasm')}
                        className={`px-6 py-2 rounded-lg font-bold text-sm transition-all ${method === 'wasm' ? 'bg-white dark:bg-slate-800 shadow text-orange-600' : 'text-slate-500'}`}
                    >
                        Rust Wasm (Worker)
                    </button>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-12">
            
                {/* Visualizer Area */}
                <div className="relative group">
                     <div className={`absolute -inset-1 bg-gradient-to-r from-orange-400 to-pink-600 rounded-2xl blur opacity-25 group-hover:opacity-75 transition duration-1000 group-hover:duration-200 ${isProcessing ? 'animate-pulse' : ''}`}></div>
                     <div className="relative bg-black rounded-xl p-1 overflow-hidden h-[300px] flex items-center justify-center">
                         {/* Fake "Image" */}
                         <div className={`w-full h-full bg-[url('https://images.unsplash.com/photo-1550740558-d9bc3c5a2c2c?q=80&w=800')] bg-cover bg-center transition-all duration-300 ${progress === 100 ? 'grayscale' : 'grayscale-0'}`} 
                            style={{ filter: `grayscale(${progress}%) blur(${method === 'js' && isProcessing ? '5px' : '0'})` }}
                         ></div>
                         
                         {/* Loading Overlay */}
                         {isProcessing && (
                             <div className="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white">
                                <div className="text-3xl font-black mb-2">{Math.round(progress)}%</div>
                                {method === 'js' && <div className="text-red-400 text-sm font-bold animate-bounce">⚠️ Blocking UI Thread</div>}
                                {method === 'wasm' && <div className="text-green-400 text-sm font-bold flex items-center gap-1"><span></span> Hardware Accelerated</div>}
                             </div>
                         )}
                         
                         {!isProcessing && progress !== 100 && (
                             <button
                                onClick={processImage}
                                className="absolute bg-white/10 backdrop-blur-md hover:bg-white/20 border border-white/30 text-white rounded-full p-6 transition-all transform hover:scale-110 active:scale-95"
                            >
                                <span>▶️</span>
                            </button>
                         )}
                         
                         {!isProcessing && progress === 100 && (
                            <button
                                onClick={() => setProgress(0)}
                                className="absolute bottom-4 right-4 bg-white text-black px-4 py-2 rounded-lg font-bold text-sm shadow-lg hover:bg-gray-100"
                            >
                                Reset
                            </button>
                         )}
                     </div>
                </div>

                {/* Metrics Area */}
                <div className="space-y-6">
                    <div className="grid grid-cols-2 gap-4">
                        <div className="p-6 bg-white dark:bg-black/20 rounded-2xl border border-slate-200 dark:border-slate-800">
                             <div className="text-xs font-bold text-gray-400 uppercase mb-2">Execution Time</div>
                             <div className={`text-3xl font-black ${method === 'wasm' ? 'text-green-500' : 'text-red-500'}`}>
                                 {metrics.time}s
                             </div>
                             <div className="text-xs text-gray-500 mt-1">{method === 'wasm' ? '🚀 7x Faster' : '🐢 Slow'}</div>
                        </div>
                         <div className="p-6 bg-white dark:bg-black/20 rounded-2xl border border-slate-200 dark:border-slate-800">
                             <div className="text-xs font-bold text-gray-400 uppercase mb-2">Frame Rate (UI)</div>
                             <div className={`text-3xl font-black transition-all ${metrics.fps < 30 ? 'text-red-500' : 'text-green-500'}`}>
                                 {Math.round(metrics.fps)} <span className="text-sm text-gray-500">fps</span>
                             </div>
                             <div className="text-xs text-gray-500 mt-1">{metrics.fps < 30 ? 'Janky / Stuttering' : 'Buttery Smooth'}</div>
                        </div>
                    </div>
                    
                    <div className="p-6 bg-slate-900 rounded-2xl text-slate-300 font-mono text-sm leading-6 border border-slate-800">
                         <div className="mb-2 text-xs font-bold text-slate-500 uppercase flex items-center gap-2">
                             <span>💾</span> Thread Activity
                         </div>
                         {method === 'js' ? (
                             <>
                                <span className="text-red-400">[Main]</span> Starting Grayscale loop...<br/>
                                <span className="text-red-400">[Main]</span> Blocked. 99% CPU.<br/>
                                <span className="text-red-400">[Main]</span> Dropped frame.<br/>
                                <span className="text-red-400">[Main]</span> Dropped frame.<br/>
                                <span className="text-green-400">[Main]</span> Done.
                             </>
                         ) : (
                             <>
                                <span className="text-blue-400">[Main]</span> Spawning Wasm Worker...<br/>
                                <span className="text-orange-400">[Worker]</span> Rust::process_image()<br/>
                                <span className="text-green-400">[Main]</span> UI rendering @ 60fps...<br/>
                                <span className="text-green-400">[Main]</span> UI rendering @ 60fps...<br/>
                                <span className="text-blue-400">[Main]</span> Received buffer. Displaying.
                             </>
                         )}
                    </div>
                </div>

            </div>
        </div>
    );
}