• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1CanvasKit - Skia + WebAssembly
2==============================
3
4Skia now offers a WebAssembly build for easy deployment of our graphics APIs on
5the web.
6
7CanvasKit provides a playground for testing new Canvas and SVG platform APIs,
8enabling fast-paced development on the web platform.
9It can also be used as a deployment mechanism for custom web apps requiring
10cutting-edge features, like Skia's [Lottie
11animation](https://skia.org/user/modules/skottie) support.
12
13
14Features
15--------
16
17  - WebGL context encapsulated as an SkSurface, allowing for direct drawing to
18    an HTML canvas
19  - Core set of Skia canvas/paint/path/text APIs available, see bindings
20  - Draws to a hardware-accelerated backend
21  - Security tested with Skia's fuzzers
22
23Samples
24-------
25
26<style>
27  #demo canvas {
28    border: 1px dashed #AAA;
29    margin: 2px;
30  }
31
32  #patheffect, #ink, #shaping {
33    width: 400px;
34    height: 400px;
35  }
36
37  #sk_legos, #sk_drinks, #sk_party, #sk_onboarding, #shader1 {
38    width: 300px;
39    height: 300px;
40  }
41
42  figure {
43    display: inline-block;
44    margin: 0;
45  }
46
47  figcaption > a {
48    margin: 2px 10px;
49  }
50
51</style>
52
53<div id=demo>
54  <h3>Go beyond the HTML Canvas2D</h3>
55  <figure>
56    <canvas id=patheffect width=400 height=400></canvas>
57    <figcaption>
58      <a href="https://jsfiddle.skia.org/canvaskit/ea89749ae8c90bce807ea2e7e34fb7b09b950cee70d9db0a9cdfd2d67bd48ef0"
59          target=_blank rel=noopener>
60        Star JSFiddle</a>
61    </figcaption>
62  </figure>
63  <figure>
64    <canvas id=ink width=400 height=400></canvas>
65    <figcaption>
66      <a href="https://jsfiddle.skia.org/canvaskit/43475699d6d7d3d7dad1004c29f84015752a6a6dee2bb90f2e891b53e31d45cc"
67          target=_blank rel=noopener>
68        Ink JSFiddle</a>
69    </figcaption>
70  </figure>
71
72  <h3>Skottie (click for fiddles)</h3>
73  <a href="https://jsfiddle.skia.org/canvaskit/092690b273b41076d2f00f0d43d004893d6bb9992c387c0385efa8e6f6bc83d7"
74     target=_blank rel=noopener>
75    <canvas id=sk_legos width=300 height=300></canvas>
76  </a>
77  <a href="https://jsfiddle.skia.org/canvaskit/e7ac983d9859f89aff1b6d385190919202c2eb53d028a79992892cacceffd209"
78     target=_blank rel=noopener>
79    <canvas id=sk_drinks width=500 height=500></canvas>
80  </a>
81  <a href="https://jsfiddle.skia.org/canvaskit/0e06547181759731e7369d3e3613222a0826692f48c41b16504ed68d671583e1"
82     target=_blank rel=noopener>
83    <canvas id=sk_party width=500 height=500></canvas>
84  </a>
85  <a href="https://jsfiddle.skia.org/canvaskit/be3fc1c5c351e7f43cc2840033f80b44feb3475925264808f321bb9e2a21174a"
86     target=_blank rel=noopener>
87    <canvas id=sk_onboarding width=500 height=500></canvas>
88  </a>
89
90  <h3>SkParagraph (using ICU and Harfbuzz)</h3>
91  <figure>
92    <canvas id=shaping width=500 height=500></canvas>
93    <figcaption>
94      <a href="https://jsfiddle.skia.org/canvaskit/56cb197c724dfdfad0c3d8133d4fcab587e4c4e7f31576e62c17251637d3745c"
95          target=_blank rel=noopener>
96        SkParagraph JSFiddle</a>
97    </figcaption>
98  </figure>
99
100  <h3>SKSL for writing custom shaders</h3>
101  <a href="https://jsfiddle.skia.org/canvaskit/33ff9bed883cd5742b4770169da0b36fb0cbc18fd395ddd9563213e178362d30"
102    target=_blank rel=noopener>
103    <canvas id=shader1 width=512 height=512></canvas>
104  </a>
105
106</div>
107
108<script type="text/javascript" charset="utf-8">
109(function() {
110  // Tries to load the WASM version if supported, shows error otherwise
111  let s = document.createElement('script');
112  let locate_file = '';
113  // Hey, if you are looking at this code for an example of how to do it yourself, please use
114  // an actual CDN, such as https://unpkg.com/canvaskit-wasm - it will have better reliability
115  // and niceties like brotli compression.
116  if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
117    console.log('WebAssembly is supported!');
118    locate_file = 'https://particles.skia.org/static/';
119  } else {
120    console.log('WebAssembly is not supported (yet) on this browser.');
121    document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
122    return;
123  }
124  s.src = locate_file + 'canvaskit.js';
125  s.onload = () => {
126  let CanvasKit = null;
127  let legoJSON = null;
128  let drinksJSON = null;
129  let confettiJSON = null;
130  let onboardingJSON = null;
131  let fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
132  CanvasKitInit({
133    locateFile: (file) => locate_file + file,
134  }).ready().then((CK) => {
135    CanvasKit = CK;
136    DrawingExample(CanvasKit);
137    InkExample(CanvasKit);
138    ShapingExample(CanvasKit);
139     // Set bounds to fix the 4:3 resolution of the legos
140    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
141    // Re-size to fit
142    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
143    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
144    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
145    ShaderExample1(CanvasKit);
146  });
147
148  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
149    resp.text().then((str) => {
150      legoJSON = str;
151      SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
152    });
153  });
154
155  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
156    resp.text().then((str) => {
157      drinksJSON = str;
158      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
159    });
160  });
161
162  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
163    resp.text().then((str) => {
164      confettiJSON = str;
165      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
166    });
167  });
168
169  fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => {
170    resp.text().then((str) => {
171      onboardingJSON = str;
172      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
173    });
174  });
175
176  function preventScrolling(canvas) {
177    canvas.addEventListener('touchmove', (e) => {
178      // Prevents touch events in the canvas from scrolling the canvas.
179      e.preventDefault();
180      e.stopPropagation();
181    });
182  }
183
184  function DrawingExample(CanvasKit) {
185    const surface = CanvasKit.MakeCanvasSurface('patheffect');
186    if (!surface) {
187      console.log('Could not make surface');
188    }
189    const context = CanvasKit.currentContext();
190
191    const canvas = surface.getCanvas();
192
193    const paint = new CanvasKit.SkPaint();
194
195    const textPaint = new CanvasKit.SkPaint();
196    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
197    textPaint.setAntiAlias(true);
198
199    const textFont = new CanvasKit.SkFont(null, 30);
200
201    let i = 0;
202
203    let X = 200;
204    let Y = 200;
205
206    function drawFrame() {
207      const path = starPath(CanvasKit, X, Y);
208      CanvasKit.setCurrentContext(context);
209      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
210      i++;
211
212      paint.setPathEffect(dpe);
213      paint.setStyle(CanvasKit.PaintStyle.Stroke);
214      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
215      paint.setAntiAlias(true);
216      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
217
218      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
219
220      canvas.drawPath(path, paint);
221      canvas.drawText('Try Clicking!', 10, 380, textPaint, textFont);
222      canvas.flush();
223      dpe.delete();
224      path.delete();
225      window.requestAnimationFrame(drawFrame);
226    }
227    window.requestAnimationFrame(drawFrame);
228
229    // Make animation interactive
230    let interact = (e) => {
231      if (!e.buttons) {
232        return;
233      }
234      X = e.offsetX;
235      Y = e.offsetY;
236    };
237    document.getElementById('patheffect').addEventListener('pointermove', interact);
238    document.getElementById('patheffect').addEventListener('pointerdown', interact);
239    preventScrolling(document.getElementById('patheffect'));
240
241    // A client would need to delete this if it didn't go on forever.
242    // font.delete();
243    // paint.delete();
244  }
245
246  function InkExample(CanvasKit) {
247    const surface = CanvasKit.MakeCanvasSurface('ink');
248    if (!surface) {
249      console.log('Could not make surface');
250    }
251    const context = CanvasKit.currentContext();
252
253    const canvas = surface.getCanvas();
254
255    let paint = new CanvasKit.SkPaint();
256    paint.setAntiAlias(true);
257    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
258    paint.setStyle(CanvasKit.PaintStyle.Stroke);
259    paint.setStrokeWidth(4.0);
260    // This effect smooths out the drawn lines a bit.
261    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
262
263    // Draw I N K
264    let path = new CanvasKit.SkPath();
265    path.moveTo(80, 30);
266    path.lineTo(80, 80);
267
268    path.moveTo(100, 80);
269    path.lineTo(100, 15);
270    path.lineTo(130, 95);
271    path.lineTo(130, 30);
272
273    path.moveTo(150, 30);
274    path.lineTo(150, 80);
275    path.moveTo(170, 30);
276    path.lineTo(150, 55);
277    path.lineTo(170, 80);
278
279    let paths = [path];
280    let paints = [paint];
281
282    function drawFrame() {
283      CanvasKit.setCurrentContext(context);
284
285      for (let i = 0; i < paints.length && i < paths.length; i++) {
286        canvas.drawPath(paths[i], paints[i]);
287      }
288      canvas.flush();
289
290      window.requestAnimationFrame(drawFrame);
291    }
292
293    let hold = false;
294    let interact = (e) => {
295      let type = e.type;
296      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
297        hold = false;
298        return;
299      }
300      if (hold) {
301        path.lineTo(e.offsetX, e.offsetY);
302      } else {
303        paint = paint.copy();
304        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
305        paints.push(paint);
306        path = new CanvasKit.SkPath();
307        paths.push(path);
308        path.moveTo(e.offsetX, e.offsetY);
309      }
310      hold = true;
311    };
312    document.getElementById('ink').addEventListener('pointermove', interact);
313    document.getElementById('ink').addEventListener('pointerdown', interact);
314    document.getElementById('ink').addEventListener('lostpointercapture', interact);
315    document.getElementById('ink').addEventListener('pointerup', interact);
316    preventScrolling(document.getElementById('ink'));
317    window.requestAnimationFrame(drawFrame);
318  }
319
320  function ShapingExample(CanvasKit) {
321    const surface = CanvasKit.MakeCanvasSurface('shaping');
322    if (!surface) {
323      console.log('Could not make surface');
324      return;
325    }
326    let robotoData = null;
327    fetch('https://storage.googleapis.com/skia-cdn/google-web-fonts/Roboto-Regular.ttf').then((resp) => {
328      resp.arrayBuffer().then((buffer) => {
329        robotoData = buffer;
330        requestAnimationFrame(drawFrame);
331      });
332    });
333
334    let emojiData = null;
335    fetch('https://storage.googleapis.com/skia-cdn/misc/NotoColorEmoji.ttf').then((resp) => {
336      resp.arrayBuffer().then((buffer) => {
337        emojiData = buffer;
338        requestAnimationFrame(drawFrame);
339      });
340    });
341
342    const skcanvas = surface.getCanvas();
343
344    const font = new CanvasKit.SkFont(null, 18);
345    const fontPaint = new CanvasKit.SkPaint();
346    fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
347    fontPaint.setAntiAlias(true);
348
349    skcanvas.drawText(`Fetching Font data...`, 5, 450, fontPaint, font);
350    surface.flush();
351
352    const context = CanvasKit.currentContext();
353
354    let paragraph = null;
355    let X = 10;
356    let Y = 10;
357    const str = 'The quick brown fox �� ate a zesty hamburgerfons ��.\nThe ��‍��‍��‍�� laughed.';
358
359    function drawFrame() {
360      if (robotoData && emojiData && !paragraph) {
361        const fontMgr = CanvasKit.SkFontMgr.FromData([robotoData, emojiData]);
362
363        const paraStyle = new CanvasKit.ParagraphStyle({
364          textStyle: {
365            color: CanvasKit.BLACK,
366            fontFamilies: ['Roboto', 'Noto Color Emoji'],
367            fontSize: 50,
368          },
369          textAlign: CanvasKit.TextAlign.Left,
370          maxLines: 7,
371          ellipsis: '...',
372        });
373
374        const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
375        builder.addText(str);
376        paragraph = builder.build();
377      }
378      if (!paragraph) {
379        requestAnimationFrame(drawFrame);
380        return;
381      }
382      CanvasKit.setCurrentContext(context);
383      skcanvas.clear(CanvasKit.WHITE);
384
385      const wrapTo = 350 + 150 * Math.sin(Date.now() / 2000);
386      paragraph.layout(wrapTo);
387      skcanvas.drawParagraph(paragraph, 0, 0);
388      skcanvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint);
389
390      let posA = paragraph.getGlyphPositionAtCoordinate(X, Y);
391      const cp = str.codePointAt(posA.pos);
392      if (cp) {
393        const glyph = String.fromCodePoint(cp);
394        skcanvas.drawText(`At (${X.toFixed(2)}, ${Y.toFixed(2)}) glyph is '${glyph}'`, 5, 450, fontPaint, font);
395      }
396
397      surface.flush();
398      requestAnimationFrame(drawFrame);
399    }
400
401    // Make animation interactive
402    let interact = (e) => {
403      // multiply by 4/5 to account for the difference in the canvas width and the CSS width.
404      // The 10 accounts for where the mouse actually is compared to where it is drawn.
405      X = (e.offsetX * 4/5) - 10;
406      Y = e.offsetY * 4/5;
407    };
408    document.getElementById('shaping').addEventListener('pointermove', interact);
409    document.getElementById('shaping').addEventListener('pointerdown', interact);
410    document.getElementById('shaping').addEventListener('lostpointercapture', interact);
411    document.getElementById('shaping').addEventListener('pointerup', interact);
412    preventScrolling(document.getElementById('shaping'));
413    window.requestAnimationFrame(drawFrame);
414  }
415
416  function starPath(CanvasKit, X=128, Y=128, R=116) {
417    let p = new CanvasKit.SkPath();
418    p.moveTo(X + R, Y);
419    for (let i = 1; i < 8; i++) {
420      let a = 2.6927937 * i;
421      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
422    }
423    return p;
424  }
425
426  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
427    if (!CanvasKit || !jsonStr) {
428      return;
429    }
430    const animation = CanvasKit.MakeAnimation(jsonStr);
431    const duration = animation.duration() * 1000;
432    const size = animation.size();
433    let c = document.getElementById(id);
434    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
435
436    const surface = CanvasKit.MakeCanvasSurface(id);
437    if (!surface) {
438      console.log('Could not make surface');
439    }
440    const context = CanvasKit.currentContext();
441    const canvas = surface.getCanvas();
442
443    let firstFrame = new Date().getTime();
444
445    function drawFrame() {
446      let now = new Date().getTime();
447      let seek = ((now - firstFrame) / duration) % 1.0;
448      CanvasKit.setCurrentContext(context);
449      animation.seek(seek);
450
451      animation.render(canvas, bounds);
452      canvas.flush();
453      window.requestAnimationFrame(drawFrame);
454    }
455    window.requestAnimationFrame(drawFrame);
456    //animation.delete();
457  }
458
459  function ShaderExample1(CanvasKit) {
460    if (!CanvasKit) {
461      return;
462    }
463    const surface = CanvasKit.MakeCanvasSurface('shader1');
464    if (!surface) {
465      throw 'Could not make surface';
466    }
467    const skcanvas = surface.getCanvas();
468    const paint = new CanvasKit.SkPaint();
469
470    const prog = `
471uniform float rad_scale;
472uniform float2 in_center;
473uniform float4 in_colors0;
474uniform float4 in_colors1;
475
476void main(float2 p, inout half4 color) {
477    float2 pp = p - in_center;
478    float radius = sqrt(dot(pp, pp));
479    radius = sqrt(radius);
480    float angle = atan(pp.y / pp.x);
481    float t = (angle + 3.1415926/2) / (3.1415926);
482    t += radius * rad_scale;
483    t = fract(t);
484    color = half4(mix(in_colors0, in_colors1, t));
485}
486`;
487
488    // If there are multiple contexts on the screen, we need to make sure
489    // we switch to this one before we draw.
490    const context = CanvasKit.currentContext();
491    const fact = CanvasKit.SkRuntimeEffect.Make(prog);
492    function drawFrame() {
493      CanvasKit.setCurrentContext(context);
494      skcanvas.clear(CanvasKit.WHITE);
495      const shader = fact.makeShader([
496        Math.sin(Date.now() / 2000) / 5,
497        256, 256,
498        1, 0, 0, 1,
499        0, 1, 0, 1],
500        true/*=opaque*/);
501
502      paint.setShader(shader);
503      skcanvas.drawRect(CanvasKit.LTRBRect(0, 0, 512, 512), paint);
504      surface.flush();
505      requestAnimationFrame(drawFrame);
506      shader.delete();
507    }
508    requestAnimationFrame(drawFrame);
509  }
510
511  }
512  document.head.appendChild(s);
513})();
514</script>
515
516Lottie files courtesy of the lottiefiles.com community:
517[Lego Loader](https://www.lottiefiles.com/410-lego-loader),
518[I'm thirsty](https://www.lottiefiles.com/77-im-thirsty),
519[Confetti](https://www.lottiefiles.com/1370-confetti),
520[Onboarding](https://www.lottiefiles.com/1134-onboarding-1)
521
522Test server
523-----------
524Test your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit)
525
526Download
527--------
528Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm)
529