• 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 {
33    width: 400px;
34    height: 400px;
35  }
36
37  #sk_legos, #sk_drinks, #sk_party, #sk_onboarding {
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>Text Shaping using ICU and Harfbuzz</h3>
91  <figure>
92    <canvas id=shaping width=400 height=400></canvas>
93    <figcaption>
94      <a href="https://jsfiddle.skia.org/canvaskit/d5c61a106d57ff4ada119a46ddc3bfa479d343330d0c9e50da21f4ef113d0dee"
95          target=_blank rel=noopener>
96        Text Shaping JSFiddle</a>
97    </figcaption>
98  </figure>
99
100</div>
101
102<script type="text/javascript" charset="utf-8">
103(function() {
104  // Tries to load the WASM version if supported, shows error otherwise
105  let s = document.createElement('script');
106  var locate_file = '';
107  if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
108    console.log('WebAssembly is supported!');
109    locate_file = 'https://unpkg.com/canvaskit-wasm@0.4.0/bin/';
110  } else {
111    console.log('WebAssembly is not supported (yet) on this browser.');
112    document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
113    return;
114  }
115  s.src = locate_file + 'canvaskit.js';
116  s.onload = () => {
117  var CanvasKit = null;
118  var legoJSON = null;
119  var drinksJSON = null;
120  var confettiJSON = null;
121  var onboardingJSON = null;
122  var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
123  CanvasKitInit({
124    locateFile: (file) => locate_file + file,
125  }).ready().then((CK) => {
126    CanvasKit = CK;
127    DrawingExample(CanvasKit);
128    InkExample(CanvasKit);
129    ShapingExample(CanvasKit);
130     // Set bounds to fix the 4:3 resolution of the legos
131    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
132    // Re-size to fit
133    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
134    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
135    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
136  });
137
138  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
139    resp.text().then((str) => {
140      legoJSON = str;
141      SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
142    });
143  });
144
145  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
146    resp.text().then((str) => {
147      drinksJSON = str;
148      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
149    });
150  });
151
152  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
153    resp.text().then((str) => {
154      confettiJSON = str;
155      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
156    });
157  });
158
159  fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => {
160    resp.text().then((str) => {
161      onboardingJSON = str;
162      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
163    });
164  });
165
166  function preventScrolling(canvas) {
167    canvas.addEventListener('touchmove', (e) => {
168      // Prevents touch events in the canvas from scrolling the canvas.
169      e.preventDefault();
170      e.stopPropagation();
171    });
172  }
173
174  function DrawingExample(CanvasKit) {
175    const surface = CanvasKit.MakeCanvasSurface('patheffect');
176    if (!surface) {
177      console.log('Could not make surface');
178    }
179    const context = CanvasKit.currentContext();
180
181    const canvas = surface.getCanvas();
182
183    const paint = new CanvasKit.SkPaint();
184
185    const textPaint = new CanvasKit.SkPaint();
186    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
187    textPaint.setAntiAlias(true);
188
189    const textFont = new CanvasKit.SkFont(null, 30);
190
191    let i = 0;
192
193    let X = 200;
194    let Y = 200;
195
196    function drawFrame() {
197      const path = starPath(CanvasKit, X, Y);
198      CanvasKit.setCurrentContext(context);
199      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
200      i++;
201
202      paint.setPathEffect(dpe);
203      paint.setStyle(CanvasKit.PaintStyle.Stroke);
204      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
205      paint.setAntiAlias(true);
206      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
207
208      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
209
210      canvas.drawPath(path, paint);
211      canvas.drawText('Try Clicking!', 10, 380, textPaint, textFont);
212      canvas.flush();
213      dpe.delete();
214      path.delete();
215      window.requestAnimationFrame(drawFrame);
216    }
217    window.requestAnimationFrame(drawFrame);
218
219    // Make animation interactive
220    let interact = (e) => {
221      if (!e.buttons) {
222        return;
223      }
224      X = e.offsetX;
225      Y = e.offsetY;
226    };
227    document.getElementById('patheffect').addEventListener('pointermove', interact);
228    document.getElementById('patheffect').addEventListener('pointerdown', interact);
229    preventScrolling(document.getElementById('patheffect'));
230
231    // A client would need to delete this if it didn't go on forever.
232    // font.delete();
233    // paint.delete();
234  }
235
236  function InkExample(CanvasKit) {
237    const surface = CanvasKit.MakeCanvasSurface('ink');
238    if (!surface) {
239      console.log('Could not make surface');
240    }
241    const context = CanvasKit.currentContext();
242
243    const canvas = surface.getCanvas();
244
245    let paint = new CanvasKit.SkPaint();
246    paint.setAntiAlias(true);
247    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
248    paint.setStyle(CanvasKit.PaintStyle.Stroke);
249    paint.setStrokeWidth(4.0);
250    // This effect smooths out the drawn lines a bit.
251    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
252
253    // Draw I N K
254    let path = new CanvasKit.SkPath();
255    path.moveTo(80, 30);
256    path.lineTo(80, 80);
257
258    path.moveTo(100, 80);
259    path.lineTo(100, 15);
260    path.lineTo(130, 95);
261    path.lineTo(130, 30);
262
263    path.moveTo(150, 30);
264    path.lineTo(150, 80);
265    path.moveTo(170, 30);
266    path.lineTo(150, 55);
267    path.lineTo(170, 80);
268
269    let paths = [path];
270    let paints = [paint];
271
272    function drawFrame() {
273      CanvasKit.setCurrentContext(context);
274
275      for (let i = 0; i < paints.length && i < paths.length; i++) {
276        canvas.drawPath(paths[i], paints[i]);
277      }
278      canvas.flush();
279
280      window.requestAnimationFrame(drawFrame);
281    }
282
283    let hold = false;
284    let interact = (e) => {
285      let type = e.type;
286      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
287        hold = false;
288        return;
289      }
290      if (hold) {
291        path.lineTo(e.offsetX, e.offsetY);
292      } else {
293        paint = paint.copy();
294        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
295        paints.push(paint);
296        path = new CanvasKit.SkPath();
297        paths.push(path);
298        path.moveTo(e.offsetX, e.offsetY);
299      }
300      hold = true;
301    };
302    document.getElementById('ink').addEventListener('pointermove', interact);
303    document.getElementById('ink').addEventListener('pointerdown', interact);
304    document.getElementById('ink').addEventListener('lostpointercapture', interact);
305    document.getElementById('ink').addEventListener('pointerup', interact);
306    preventScrolling(document.getElementById('ink'));
307    window.requestAnimationFrame(drawFrame);
308  }
309
310  function ShapingExample(CanvasKit) {
311    const surface = CanvasKit.MakeCanvasSurface('shaping');
312    if (!surface) {
313      console.log('Could not make surface');
314      return;
315    }
316    const context = CanvasKit.currentContext();
317    const skcanvas = surface.getCanvas();
318    const paint = new CanvasKit.SkPaint();
319    paint.setColor(CanvasKit.BLUE);
320    paint.setStyle(CanvasKit.PaintStyle.Stroke);
321
322    const textPaint = new CanvasKit.SkPaint();
323    const bigFont = new CanvasKit.SkFont(null, 30);
324    const smallFont = new CanvasKit.SkFont(null, 14);
325
326    const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac leo vitae ipsum hendrerit euismod quis rutrum nibh. Quisque non suscipit urna. Donec enim urna, facilisis vitae volutpat in, mattis at elit. Sed quis augue et dolor dignissim fringilla. Sed non massa eu neque tristique malesuada. ';
327
328    let X = 280;
329    let Y = 190;
330
331    function drawFrame() {
332      CanvasKit.setCurrentContext(context);
333      skcanvas.clear(CanvasKit.TRANSPARENT);
334
335      const shapedText = new CanvasKit.ShapedText({
336        font: smallFont,
337        leftToRight: true,
338        text: TEXT,
339        width: X - 10,
340      });
341
342      skcanvas.drawRect(CanvasKit.LTRBRect(10, 10, X, Y), paint);
343      skcanvas.drawText(shapedText, 10, 10, textPaint, smallFont);
344      skcanvas.drawText('Try dragging the box!', 10, 380, textPaint, bigFont);
345
346      surface.flush();
347
348      shapedText.delete();
349
350      window.requestAnimationFrame(drawFrame);
351    }
352    window.requestAnimationFrame(drawFrame);
353
354    // Make animation interactive
355    let interact = (e) => {
356      if (!e.pressure) {
357        return;
358      }
359      X = e.offsetX;
360      Y = e.offsetY;
361    };
362    document.getElementById('shaping').addEventListener('pointermove', interact);
363    document.getElementById('shaping').addEventListener('pointerdown', interact);
364    document.getElementById('shaping').addEventListener('lostpointercapture', interact);
365    document.getElementById('shaping').addEventListener('pointerup', interact);
366    preventScrolling(document.getElementById('shaping'));
367    window.requestAnimationFrame(drawFrame);
368  }
369
370  function starPath(CanvasKit, X=128, Y=128, R=116) {
371    let p = new CanvasKit.SkPath();
372    p.moveTo(X + R, Y);
373    for (let i = 1; i < 8; i++) {
374      let a = 2.6927937 * i;
375      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
376    }
377    return p;
378  }
379
380  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
381    if (!CanvasKit || !jsonStr) {
382      return;
383    }
384    const animation = CanvasKit.MakeAnimation(jsonStr);
385    const duration = animation.duration() * 1000;
386    const size = animation.size();
387    let c = document.getElementById(id);
388    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
389
390    const surface = CanvasKit.MakeCanvasSurface(id);
391    if (!surface) {
392      console.log('Could not make surface');
393    }
394    const context = CanvasKit.currentContext();
395    const canvas = surface.getCanvas();
396
397    let firstFrame = new Date().getTime();
398
399    function drawFrame() {
400      let now = new Date().getTime();
401      let seek = ((now - firstFrame) / duration) % 1.0;
402      CanvasKit.setCurrentContext(context);
403      animation.seek(seek);
404
405      animation.render(canvas, bounds);
406      canvas.flush();
407      window.requestAnimationFrame(drawFrame);
408    }
409    window.requestAnimationFrame(drawFrame);
410    //animation.delete();
411  }
412  }
413  document.head.appendChild(s);
414})();
415</script>
416
417Lottie files courtesy of the lottiefiles.com community:
418[Lego Loader](https://www.lottiefiles.com/410-lego-loader),
419[I'm thirsty](https://www.lottiefiles.com/77-im-thirsty),
420[Confetti](https://www.lottiefiles.com/1370-confetti),
421[Onboarding](https://www.lottiefiles.com/1134-onboarding-1)
422
423Test server
424-----------
425Test your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit)
426
427Download
428--------
429Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm)
430