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>An Interactive Path</h3>
55  <figure>
56    <canvas id=patheffect width=400 height=400></canvas>
57    <figcaption>
58      <a href="https://jsfiddle.skia.org/canvaskit/28004d8841e7e497013263598241a3c1edc21dc1cf87a679abba307f39fa5fe6"
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</div>
91
92<script type="text/javascript" charset="utf-8">
93(function() {
94  // Tries to load the WASM version if supported, shows error otherwise
95  let s = document.createElement('script');
96  var locate_file = '';
97  if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
98    console.log('WebAssembly is supported!');
99    locate_file = 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/';
100  } else {
101    console.log('WebAssembly is not supported (yet) on this browser.');
102    document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
103    return;
104  }
105  s.src = locate_file + 'canvaskit.js';
106  s.onload = () => {
107  var CanvasKit = null;
108  var legoJSON = null;
109  var drinksJSON = null;
110  var confettiJSON = null;
111  var onboardingJSON = null;
112  var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
113  CanvasKitInit({
114    locateFile: (file) => locate_file + file,
115  }).then((CK) => {
116    CanvasKit = CK;
117    DrawingExample(CanvasKit);
118    InkExample(CanvasKit);
119     // Set bounds to fix the 4:3 resolution of the legos
120    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
121    // Re-size to fit
122    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
123    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
124    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
125  });
126
127  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
128    resp.text().then((str) => {
129      legoJSON = str;
130      SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
131    });
132  });
133
134  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
135    resp.text().then((str) => {
136      drinksJSON = str;
137      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
138    });
139  });
140
141  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
142    resp.text().then((str) => {
143      confettiJSON = str;
144      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
145    });
146  });
147
148  fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => {
149    resp.text().then((str) => {
150      onboardingJSON = str;
151      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
152    });
153  });
154
155  function preventScrolling(canvas) {
156    canvas.addEventListener('touchmove', (e) => {
157      // Prevents touch events in the canvas from scrolling the canvas.
158      e.preventDefault();
159      e.stopPropagation();
160    });
161  }
162
163  function DrawingExample(CanvasKit) {
164    const surface = CanvasKit.MakeCanvasSurface('patheffect');
165    if (!surface) {
166      console.log('Could not make surface');
167    }
168    const context = CanvasKit.currentContext();
169
170    const canvas = surface.getCanvas();
171
172    const paint = new CanvasKit.SkPaint();
173
174    const textPaint = new CanvasKit.SkPaint();
175    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
176    textPaint.setTextSize(30);
177    textPaint.setAntiAlias(true);
178
179    let i = 0;
180
181    let X = 200;
182    let Y = 200;
183
184    function drawFrame() {
185      const path = starPath(CanvasKit, X, Y);
186      CanvasKit.setCurrentContext(context);
187      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
188      i++;
189
190      paint.setPathEffect(dpe);
191      paint.setStyle(CanvasKit.PaintStyle.Stroke);
192      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
193      paint.setAntiAlias(true);
194      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
195
196      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
197
198      canvas.drawPath(path, paint);
199      canvas.drawText('Try Clicking!', 10, 380, textPaint);
200      canvas.flush();
201      dpe.delete();
202      path.delete();
203      window.requestAnimationFrame(drawFrame);
204    }
205    window.requestAnimationFrame(drawFrame);
206
207    // Make animation interactive
208    let interact = (e) => {
209      if (!e.buttons) {
210        return;
211      }
212      X = e.offsetX;
213      Y = e.offsetY;
214    };
215    document.getElementById('patheffect').addEventListener('pointermove', interact);
216    document.getElementById('patheffect').addEventListener('pointerdown', interact);
217    preventScrolling(document.getElementById('patheffect'));
218
219    // A client would need to delete this if it didn't go on for ever.
220    //paint.delete();
221  }
222
223  function InkExample(CanvasKit) {
224    const surface = CanvasKit.MakeCanvasSurface('ink');
225    if (!surface) {
226      console.log('Could not make surface');
227    }
228    const context = CanvasKit.currentContext();
229
230    const canvas = surface.getCanvas();
231
232    let paint = new CanvasKit.SkPaint();
233    paint.setAntiAlias(true);
234    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
235    paint.setStyle(CanvasKit.PaintStyle.Stroke);
236    paint.setStrokeWidth(4.0);
237    // This effect smooths out the drawn lines a bit.
238    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
239
240    // Draw I N K
241    let path = new CanvasKit.SkPath();
242    path.moveTo(80, 30);
243    path.lineTo(80, 80);
244
245    path.moveTo(100, 80);
246    path.lineTo(100, 15);
247    path.lineTo(130, 95);
248    path.lineTo(130, 30);
249
250    path.moveTo(150, 30);
251    path.lineTo(150, 80);
252    path.moveTo(170, 30);
253    path.lineTo(150, 55);
254    path.lineTo(170, 80);
255
256    let paths = [path];
257    let paints = [paint];
258
259    function drawFrame() {
260      CanvasKit.setCurrentContext(context);
261
262      for (let i = 0; i < paints.length && i < paths.length; i++) {
263        canvas.drawPath(paths[i], paints[i]);
264      }
265      canvas.flush();
266
267      window.requestAnimationFrame(drawFrame);
268    }
269
270    let hold = false;
271    let interact = (e) => {
272      let type = e.type;
273      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
274        hold = false;
275        return;
276      }
277      if (hold) {
278        path.lineTo(e.offsetX, e.offsetY);
279      } else {
280        paint = paint.copy();
281        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
282        paints.push(paint);
283        path = new CanvasKit.SkPath();
284        paths.push(path);
285        path.moveTo(e.offsetX, e.offsetY);
286      }
287      hold = true;
288    };
289    document.getElementById('ink').addEventListener('pointermove', interact);
290    document.getElementById('ink').addEventListener('pointerdown', interact);
291    document.getElementById('ink').addEventListener('lostpointercapture', interact);
292    document.getElementById('ink').addEventListener('pointerup', interact);
293    preventScrolling(document.getElementById('ink'));
294    window.requestAnimationFrame(drawFrame);
295  }
296
297  function starPath(CanvasKit, X=128, Y=128, R=116) {
298    let p = new CanvasKit.SkPath();
299    p.moveTo(X + R, Y);
300    for (let i = 1; i < 8; i++) {
301      let a = 2.6927937 * i;
302      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
303    }
304    return p;
305  }
306
307  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
308    if (!CanvasKit || !jsonStr) {
309      return;
310    }
311    const animation = CanvasKit.MakeAnimation(jsonStr);
312    const duration = animation.duration() * 1000;
313    const size = animation.size();
314    let c = document.getElementById(id);
315    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
316
317    const surface = CanvasKit.MakeCanvasSurface(id);
318    if (!surface) {
319      console.log('Could not make surface');
320    }
321    const context = CanvasKit.currentContext();
322    const canvas = surface.getCanvas();
323
324    let firstFrame = new Date().getTime();
325
326    function drawFrame() {
327      let now = new Date().getTime();
328      let seek = ((now - firstFrame) / duration) % 1.0;
329      CanvasKit.setCurrentContext(context);
330      animation.seek(seek);
331
332      animation.render(canvas, bounds);
333      canvas.flush();
334      window.requestAnimationFrame(drawFrame);
335    }
336    window.requestAnimationFrame(drawFrame);
337    //animation.delete();
338  }
339  }
340  document.head.appendChild(s);
341})();
342</script>
343
344Lottie files courtesy of the lottiefiles.com community:
345[Lego Loader](https://www.lottiefiles.com/410-lego-loader),
346[I'm thirsty](https://www.lottiefiles.com/77-im-thirsty),
347[Confetti](https://www.lottiefiles.com/1370-confetti),
348[Onboarding](https://www.lottiefiles.com/1134-onboarding-1)
349
350Test server
351-----------
352Test your code on our [CanvasKit Fiddle](https://jsfiddle.skia.org/canvaskit)
353
354Download
355--------
356Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm)
357