mirror of
https://github.com/thead-yocto-mirror/skia
synced 2026-07-20 14:57:56 +02:00
13 KiB
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
Skottie (click for fiddles)
Text Shaping using ICU and Harfbuzz
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