Bringing Skia to the Web
If you do not know what Skia is — read this article first.
The Problem
You want Skia's power. In the browser. Without plugins.
Historically, impossible. Browsers give you HTML Canvas—a simple 2D drawing API that's fine for basic graphics but lacks advanced features. No complex path operations. Limited text shaping. No hardware-accelerated effects.
Then WebAssembly arrived.
What CanvasKit Is
CanvasKit is Skia compiled to WebAssembly (WASM). The entire C++ graphics library, running in your browser at near-native speed.
It exposes Skia's API through JavaScript bindings. You get:
— Full path operations
— Advanced text layout (HarfBuzz, paragraph layout)
— Hardware-accelerated effects
— All blend modes
— Shader support (RuntimeEffect)
— Everything else Skia provides
Download size: ~2.9MB gzipped. That's the entire graphics engine.
Who Uses CanvasKit
Flutter Web: Switched exclusively to CanvasKit renderer. Every Flutter web app uses it.
Shopify: Built react-native-skia with CanvasKit for web support. React Native apps can now use Skia across mobile and web.
AntV (Ant Group): Created @antv/g-canvaskit renderer for their graphics library.
Figma (partially): Uses custom WebGL code primarily, but leverages Skia for specific algorithms.
43+ npm packages: Various frameworks and libraries building on CanvasKit.
The First Failed Attempt: The Fiddle
Official documentation points to https://jsfiddle.skia.org/canvaskit for an online playground.
Problem: It doesn't work. As of 2024, users get 403 Forbidden after Google login. The CanvasKit fiddle has been down intermittently.
The main Skia Fiddle (fiddle.skia.org) works for C++ code, but that's not helpful for web developers trying to learn CanvasKit.
Reality: No working online playground means learning from docs and examples alone. The docs exist, but...
The Documentation Problem
CanvasKit documentation exists at https://skia.org/docs/user/modules/canvaskit/
Here's what's actually there:
- Installation instructions (accurate)
- TypeScript definitions (in the npm package)
- API overview (basic)
- A few code examples (incomplete)
Here's what's missing:
- Memory management best practices (critical!)
- Bundler integration guides (Webpack/Vite have issues)
- Performance considerations (GPU vs CPU paths)
- Complete API reference (you need TypeScript definitions)
- Real-world examples (complex shapes, animations)
The documentation assumes you know Skia's C++ API and can translate. If you don't, good luck.
What the Docs Don't Tell You: Memory Management
Critical rule: Objects created with new or Make* methods must be deleted manually.
const paint = new CanvasKit.Paint();
// ... use paint ...
paint.delete(); // MUST call this
JavaScript's garbage collector does not clean up WASM memory. Forget to delete, memory leaks forever (until page reload).
The mistake everyone makes: Creating paths/paints inside render loops without deleting them. 100 frames = 100 leaked objects.
We'll cover proper patterns in the interactive examples.
Bundler Integration Hell
Webpack Issues
- WASM support is experimental
- Need to copy
canvaskit.wasmto build directory manually - "Can't resolve 'fs'" errors require config changes
- No official guide for Webpack 5
Vite Issues
- Development mode renames
CanvasKitInittoCanvasKitInit2(completely wrong) - Importing WASM from node_modules fails
- Need custom import solutions
- Build vs dev mode behave differently
The Reality
Most CanvasKit projects include a webpack.config.js or vite.config.js with cryptic workarounds. Copy-paste and pray.
React Native Skia: Shopify's Approach
Shopify built react-native-skia to bring Skia to React Native. For web support, it uses CanvasKit.
Key insight: They wrapped CanvasKit in a declarative API. Instead of imperative draw calls, you describe what to draw:
<Canvas>
<Rect x={0} y={0} width={256} height={256} color="blue" />
</Canvas>
The library handles:
- Memory management (automatic deletion)
- Render loops
- Platform differences (native Skia vs CanvasKit)
This is not a CanvasKit fork. It's a high-level API that uses CanvasKit for web and native Skia for mobile.
Worth considering if you're building in React.
Who Actually Uses CanvasKit Directly
Flutter: No choice, it's the renderer.
Custom renderers: Building design tools, canvas apps, visualization libraries.
Performance-critical apps: Need GPU acceleration HTML Canvas can't provide.
Cross-platform tools: Want identical rendering on web and desktop (via Skia).
Most developers: Use a framework on top (Flutter, react-native-skia, AntV).
The Advantages
1. Real GPU Acceleration
CanvasKit uses WebGL/WebGPU. Complex paths render fast. Effects are hardware-accelerated. HTML Canvas can't compete.
2. Feature Parity
Need custom blend modes? Advanced text? Path boolean operations? CanvasKit has it. Canvas API doesn't.
3. Identical Rendering
Skia on desktop + CanvasKit on web = pixel-perfect match. Critical for design tools.
4. Control
Low-level API means no magic. You control clipping, layers, GPU resources. When you need performance, this matters.
The Disadvantages
1. Size
2.9MB gzipped is significant. Your bundle is instantly larger. First load is slower.
2. Learning Curve
Not HTML Canvas. Not SVG. It's Skia's API, translated to JavaScript. Expect weeks of learning.
3. Memory Management
Manual deletion or memory leaks. No escaping it.
4. Debugging
Errors in WASM are cryptic. Stack traces are useless. console.log() is your friend.
5. Bundler Hell
Every build tool needs custom configuration. Expect half a day fighting Webpack/Vite.
Interactive Examples
Let's start by writing simple examples that are still valuable due to tiny amount of real examples on other resources:
We're building interactive examples you can run in your browser:
Example 1: Hello, Rectangle!
Basic CanvasKit setup, creating a surface, drawing a rectangle
const surface = CK.MakeCanvasSurface('canvas-id');
const canvas = surface.getCanvas();
const paint = new CK.Paint();
const color = CK.Color(255, 0, 0, 1.0);
const rect = CK.LTRBRect(50, 50, 200, 170);
// important to notice that rect and color are Float32Arrays
paint.setColor(color);
canvas.drawRect(rect, paint);
surface.flush();
// but paint is a skia wasm object wrapper, so we are deleting it
paint.delete();
Example 2: Moving circle
Simplest render loop with requestAnimationFrame integration
const w = 420, h=256;
const surface = CK.MakeCanvasSurface('canvas-circle-id');
const canvas = surface.getCanvas();
const paint = new CK.Paint();
const color = CK.Color(128, 0, 0, 1.0);
const oval = CK.LTRBRect(50, 50, 200, 170);
paint.setColor(color);
let x = 50, y = 50, r = 30, sx=3, sy = 3;
function update(){
x+=sx;
y+=sy;
if(x+r>w || x-r<0) sx*=-1;
if(y+r>h || y-r<0) sy*=-1;
oval[0] = x-r;
oval[1] = y-r;
oval[2] = x+r;
oval[3] = y+r;
// it draws an oval in the same way as rect
canvas.drawOval(oval, paint);
surface.flush();
requestAnimationFrame(update);
}
update();
// we are not deleting the paint, because we are reusing it each frame
//paint.delete();
When to Use CanvasKit
Use CanvasKit when:
— You need features HTML Canvas lacks
— GPU performance is critical
— Cross-platform rendering consistency matters
— You're building a design/graphics tool
— 2.9MB download is acceptable
Don't use CanvasKit when:
— HTML Canvas is sufficient
— Bundle size is critical
— Simple 2D drawing is all you need
— You can't invest time learning it
The Reality Check
CanvasKit brings Skia's power to the web. But it's not polished. Documentation is sparse. Bundler integration is messy. Memory management is manual.
I spent months learning this, hit every edge case and wrote workarounds for everything.
This series documents what actually works. Not what the docs say. What survives production.
Resources
- CanvasKit docs: https://skia.org/docs/user/modules/canvaskit/
- npm package: https://npmjs.com/package/canvaskit-wasm
- TypeScript definitions: Included in package at
types/ - Shopify react-native-skia: https://shopify.github.io/react-native-skia/
- Flutter Web renderer docs: Flutter's CanvasKit usage