Files
skia/site/user/modules/canvaskit.md
2022-09-13 14:31:24 +08:00

13 KiB

CanvasKit - Skia + WebAssembly

Skia now offers a WebAssembly build for easy deployment of our graphics APIs on the web.

CanvasKit provides a playground for testing new Canvas and SVG platform APIs, enabling fast-paced development on the web platform. It can also be used as a deployment mechanism for custom web apps requiring cutting-edge features, like Skia's Lottie animation support.

Features

  • WebGL context encapsulated as an SkSurface, allowing for direct drawing to an HTML canvas
  • Core set of Skia canvas/paint/path/text APIs available, see bindings
  • Draws to a hardware-accelerated backend
  • Security tested with Skia's fuzzers

Samples

<style> #demo canvas { border: 1px dashed #AAA; margin: 2px; } #patheffect, #ink { width: 400px; height: 400px; } #sk_legos, #sk_drinks, #sk_party, #sk_onboarding { width: 300px; height: 300px; } figure { display: inline-block; margin: 0; } figcaption > a { margin: 2px 10px; } </style>

Go beyond the HTML Canvas2D

Star JSFiddle
Ink JSFiddle

Skottie (click for fiddles)

Text Shaping using ICU and Harfbuzz

Text Shaping JSFiddle
<script type="text/javascript" charset="utf-8"> (function() { // Tries to load the WASM version if supported, shows error otherwise let s = document.createElement('script'); var locate_file = ''; if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') { console.log('WebAssembly is supported!'); locate_file = 'https://unpkg.com/canvaskit-wasm@0.4.0/bin/'; } else { console.log('WebAssembly is not supported (yet) on this browser.'); document.getElementById('demo').innerHTML = "
WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.
"; return; } s.src = locate_file + 'canvaskit.js'; s.onload = () => { var CanvasKit = null; var legoJSON = null; var drinksJSON = null; var confettiJSON = null; var onboardingJSON = null; var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500}; CanvasKitInit({ locateFile: (file) => locate_file + file, }).ready().then((CK) => { CanvasKit = CK; DrawingExample(CanvasKit); InkExample(CanvasKit); ShapingExample(CanvasKit); // Set bounds to fix the 4:3 resolution of the legos SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); // Re-size to fit SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); }); fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => { resp.text().then((str) => { legoJSON = str; SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}); }); }); fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => { resp.text().then((str) => { drinksJSON = str; SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds); }); }); fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => { resp.text().then((str) => { confettiJSON = str; SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds); }); }); fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => { resp.text().then((str) => { onboardingJSON = str; SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds); }); }); function preventScrolling(canvas) { canvas.addEventListener('touchmove', (e) => { // Prevents touch events in the canvas from scrolling the canvas. e.preventDefault(); e.stopPropagation(); }); } function DrawingExample(CanvasKit) { const surface = CanvasKit.MakeCanvasSurface('patheffect'); if (!surface) { console.log('Could not make surface'); } const context = CanvasKit.currentContext(); const canvas = surface.getCanvas(); const paint = new CanvasKit.SkPaint(); const textPaint = new CanvasKit.SkPaint(); textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); textPaint.setAntiAlias(true); const textFont = new CanvasKit.SkFont(null, 30); let i = 0; let X = 200; let Y = 200; function drawFrame() { const path = starPath(CanvasKit, X, Y); CanvasKit.setCurrentContext(context); const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5); i++; paint.setPathEffect(dpe); paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); canvas.drawPath(path, paint); canvas.drawText('Try Clicking!', 10, 380, textPaint, textFont); canvas.flush(); dpe.delete(); path.delete(); window.requestAnimationFrame(drawFrame); } window.requestAnimationFrame(drawFrame); // Make animation interactive let interact = (e) => { if (!e.buttons) { return; } X = e.offsetX; Y = e.offsetY; }; document.getElementById('patheffect').addEventListener('pointermove', interact); document.getElementById('patheffect').addEventListener('pointerdown', interact); preventScrolling(document.getElementById('patheffect')); // A client would need to delete this if it didn't go on forever. // font.delete(); // paint.delete(); } function InkExample(CanvasKit) { const surface = CanvasKit.MakeCanvasSurface('ink'); if (!surface) { console.log('Could not make surface'); } const context = CanvasKit.currentContext(); const canvas = surface.getCanvas(); let paint = new CanvasKit.SkPaint(); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setStrokeWidth(4.0); // This effect smooths out the drawn lines a bit. paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50)); // Draw I N K let path = new CanvasKit.SkPath(); path.moveTo(80, 30); path.lineTo(80, 80); path.moveTo(100, 80); path.lineTo(100, 15); path.lineTo(130, 95); path.lineTo(130, 30); path.moveTo(150, 30); path.lineTo(150, 80); path.moveTo(170, 30); path.lineTo(150, 55); path.lineTo(170, 80); let paths = [path]; let paints = [paint]; function drawFrame() { CanvasKit.setCurrentContext(context); for (let i = 0; i < paints.length && i < paths.length; i++) { canvas.drawPath(paths[i], paints[i]); } canvas.flush(); window.requestAnimationFrame(drawFrame); } let hold = false; let interact = (e) => { let type = e.type; if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) { hold = false; return; } if (hold) { path.lineTo(e.offsetX, e.offsetY); } else { paint = paint.copy(); paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2)); paints.push(paint); path = new CanvasKit.SkPath(); paths.push(path); path.moveTo(e.offsetX, e.offsetY); } hold = true; }; document.getElementById('ink').addEventListener('pointermove', interact); document.getElementById('ink').addEventListener('pointerdown', interact); document.getElementById('ink').addEventListener('lostpointercapture', interact); document.getElementById('ink').addEventListener('pointerup', interact); preventScrolling(document.getElementById('ink')); window.requestAnimationFrame(drawFrame); } function ShapingExample(CanvasKit) { const surface = CanvasKit.MakeCanvasSurface('shaping'); if (!surface) { console.log('Could not make surface'); return; } const context = CanvasKit.currentContext(); const skcanvas = surface.getCanvas(); const paint = new CanvasKit.SkPaint(); paint.setColor(CanvasKit.BLUE); paint.setStyle(CanvasKit.PaintStyle.Stroke); const textPaint = new CanvasKit.SkPaint(); const bigFont = new CanvasKit.SkFont(null, 30); const smallFont = new CanvasKit.SkFont(null, 14); 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. '; let X = 280; let Y = 190; function drawFrame() { CanvasKit.setCurrentContext(context); skcanvas.clear(CanvasKit.TRANSPARENT); const shapedText = new CanvasKit.ShapedText({ font: smallFont, leftToRight: true, text: TEXT, width: X - 10, }); skcanvas.drawRect(CanvasKit.LTRBRect(10, 10, X, Y), paint); skcanvas.drawText(shapedText, 10, 10, textPaint, smallFont); skcanvas.drawText('Try dragging the box!', 10, 380, textPaint, bigFont); surface.flush(); shapedText.delete(); window.requestAnimationFrame(drawFrame); } window.requestAnimationFrame(drawFrame); // Make animation interactive let interact = (e) => { if (!e.pressure) { return; } X = e.offsetX; Y = e.offsetY; }; document.getElementById('shaping').addEventListener('pointermove', interact); document.getElementById('shaping').addEventListener('pointerdown', interact); document.getElementById('shaping').addEventListener('lostpointercapture', interact); document.getElementById('shaping').addEventListener('pointerup', interact); preventScrolling(document.getElementById('shaping')); window.requestAnimationFrame(drawFrame); } function starPath(CanvasKit, X=128, Y=128, R=116) { let p = new CanvasKit.SkPath(); p.moveTo(X + R, Y); for (let i = 1; i < 8; i++) { let a = 2.6927937 * i; p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); } return p; } function SkottieExample(CanvasKit, id, jsonStr, bounds) { if (!CanvasKit || !jsonStr) { return; } const animation = CanvasKit.MakeAnimation(jsonStr); const duration = animation.duration() * 1000; const size = animation.size(); let c = document.getElementById(id); bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h}; const surface = CanvasKit.MakeCanvasSurface(id); if (!surface) { console.log('Could not make surface'); } const context = CanvasKit.currentContext(); const canvas = surface.getCanvas(); let firstFrame = new Date().getTime(); function drawFrame() { let now = new Date().getTime(); let seek = ((now - firstFrame) / duration) % 1.0; CanvasKit.setCurrentContext(context); animation.seek(seek); animation.render(canvas, bounds); canvas.flush(); window.requestAnimationFrame(drawFrame); } window.requestAnimationFrame(drawFrame); //animation.delete(); } } document.head.appendChild(s); })(); </script>

Lottie files courtesy of the lottiefiles.com community: Lego Loader, I'm thirsty, Confetti, Onboarding

Test server

Test your code on our CanvasKit Fiddle

Download

Get CanvasKit on NPM