今天尝试使用 canvaskit 第一次使用就翻了车。因为 umi 不支持 wasm 格式的解析,因此安装好 canvaskit-wasm 后无法直接使用,报了错。

正确安装方法

官方的指南是在 config 里添加配置:

  1. config.plugins.push(
  2. new CopyWebpackPlugin([
  3. { from: 'node_modules/canvaskit-wasm/bin/canvaskit.wasm' }
  4. ])
  5. );

但是在 umi 中不能这么用。因为 webpack 的配置项没有被暴露出来,所以使用 chainwebpack 进行修改。使用 CopyWebpackPlugin 插件加载 wasm 文件。

  1. // webpackChain.ts
  2. import IWebpackChainConfig from 'webpack-chain';
  3. import CopyWebpackPlugin from 'copy-webpack-plugin';
  4. export default (config: IWebpackChainConfig) => {
  5. config
  6. .plugin('wasm')
  7. .use(CopyWebpackPlugin, [
  8. { patterns: [{ from: 'node_modules/canvaskit-wasm/bin/canvaskit.wasm' }] },
  9. ]);
  10. };
  11. // config.ts
  12. import { defineConfig } from 'umi';
  13. import webpackPlugin from './plugin.config';
  14. export default defineConfig({
  15. // ...
  16. chainWebpack: webpackPlugin,
  17. });

虽然项目使用了TS,且 canvaskit-wasm 也有 ts 定义文件(由 Alexander Shilov 老哥编写),但是他写的应该是有点问题的,我自己做了点调整。

网上已有的案例

  1. Alexander Shilov 做的 https://github.com/ashlanderr/canvaskit-examples

    测试案例

测试代码使用了官网的的涂写案例

import React, { useEffect } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { CanvasKit as CanvasKitType } from 'canvaskit-wasm';
const CanvasKitInit = require('canvaskit-wasm');

function preventScrolling(canvas) {
  canvas.addEventListener('touchmove', e => {
    // Prevents touch events in the canvas from scrolling the canvas.
    e.preventDefault();
    e.stopPropagation();
  });
}

function InkExample(CanvasKit) {
  const surface = CanvasKit.MakeCanvasSurface('ink');
  if (!surface) {
    console.error('Could not make surface');
    return;
  }

  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);
  paint.setPathEffect(CanvasKit.SkPathEffect.MakeCorner(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(canvas) {
    canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));

    for (let i = 0; i < paints.length && i < paths.length; i++) {
      canvas.drawPath(paths[i], paints[i]);
    }

    surface.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() + 0.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'));
  surface.requestAnimationFrame(drawFrame);
}

export default (): React.ReactNode => {
  useEffect(() => {
    CanvasKitInit().then((CanvasKit: CanvasKitType) => {
      InkExample(CanvasKit);
    });
  }, []);
  return (
    <PageHeaderWrapper>
      <canvas id="ink" width="1024" height="1024" />
    </PageHeaderWrapper>
  );
};

渲染效果如下:
1.gif
同样的画面在手机端的表现如下: 文彬 2020-07-07 01.13.59.mp4 (2.9MB) 整体表现感觉还不错,下一步应该就是找地方了解 CanvasKit 的 api 和基本使用方法了。