跳到主要内容

Web 支持

React Native Skia 通过 CanvasKit 在浏览器中运行,CanvasKit 是 Skia 的 WebAssembly (WASM) 构建版本。 CanvasKit WASM 文件在 gzip 压缩后为 2.9MB,它是异步加载的。 尽管它体积较大,但它在决定何时以及如何加载 Skia 方面提供了灵活性,让您可以完全控制用户体验。

我们支持与 ExpoRemotion 的直接集成。此外,您还可以找到适用于任何 webpack 项目的手动安装步骤。

还应该提到的是,React Native Skia 可以用于不需要安装 React Native Web 的项目。

Expo

信息

从 v0.1.240 及更高版本开始,提供 Metro 和 expo-router 支持。如果您使用的是 v0.1.221(推荐用于 Expo SDK 50 的版本),则可以使用此补丁(使用 patch-package)。

使用 setup-skia-web 脚本以确保 canvaskit.wasm 文件在您的 Expo 项目的 public 文件夹中可访问。 如果您正在从 CDN 加载 CanvasKit,则无需运行 setup-skia-web 脚本。

bash
$ npx expo install @shopify/react-native-skia
$ yarn setup-skia-web
bash
$ npx expo install @shopify/react-native-skia
$ yarn setup-skia-web
信息

每次升级 @shopify/react-native-skia 包时,请运行 yarn setup-skia-web。 考虑将其纳入您的 postinstall 脚本以方便使用。

设置完成后,选择您的 加载 Skia 的方法。

对于使用 Expo Router 的项目,您可以使用代码拆分延迟组件注册。 如果您希望将延迟组件注册与 Expo Router 结合使用,您需要在 package.json 中创建您自己的 main 属性。 例如,如果您在根目录中创建了 index.tsxindex.web.tsx,请相应地更新您的 package.json

- "main": "expo-router/entry", + "main": "index",
- "main": "expo-router/entry", + "main": "index",

以下是 index.web.tsx 的示例

tsx
import '@expo/metro-runtime';
import { App } from 'expo-router/build/qualified-entry';
import { renderRootComponent } from 'expo-router/build/renderRootComponent';
import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';
LoadSkiaWeb().then(async () => {
renderRootComponent(App);
});
tsx
import '@expo/metro-runtime';
import { App } from 'expo-router/build/qualified-entry';
import { renderRootComponent } from 'expo-router/build/renderRootComponent';
import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';
LoadSkiaWeb().then(async () => {
renderRootComponent(App);
});

对于 index.tsx 文件,直接调用 renderRootComponent(App)

Snack

使用代码拆分方法将 React Native Skia 合并到 snack 中。

Remotion

按照这些安装步骤将 React Native Skia 与 Remotion 结合使用。

加载 Skia

在导入 Skia 模块之前,请确保 Skia 已完全加载和初始化。 有两种方法可以促进 Skia 的加载

  • <WithSkiaWeb /> 用于代码拆分,延迟加载导入 Skia 的组件。
  • LoadSkiaWeb() 用于延迟根组件的注册,直到 Skia 加载。

使用代码拆分

<WithSkiaWeb> 组件利用代码拆分来预加载 Skia。 以下示例演示了在渲染 MySkiaComponent 之前预加载 Skia

tsx
import React from 'react';
import { Text } from "react-native";
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
export default function App() {
return (
<WithSkiaWeb
// import() uses the default export of MySkiaComponent.tsx
getComponent={() => import("@/components/MySkiaComponent")}
fallback={<Text>Loading Skia...</Text>}
/>
);
}
tsx
import React from 'react';
import { Text } from "react-native";
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
export default function App() {
return (
<WithSkiaWeb
// import() uses the default export of MySkiaComponent.tsx
getComponent={() => import("@/components/MySkiaComponent")}
fallback={<Text>Loading Skia...</Text>}
/>
);
}
信息

在开发模式下使用 expo router 时,您无法加载位于 app 目录中的组件,因为它们会在 CanvasKit 加载之前被路由器评估。 请确保要加载的组件位于“app”目录之外。

使用延迟组件注册

LoadSkiaWeb() 函数有助于在启动 React 应用程序之前加载 Skia。 以下是 index.web.js 示例

tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
LoadSkiaWeb().then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});
tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
LoadSkiaWeb().then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});

使用 CDN

下面,CanvasKit 通过 CDN 的代码拆分加载。 CDN 托管的 CanvasKit 版本必须与 React Native Skia 的要求保持一致,这一点至关重要。

tsx
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { version } from 'canvaskit-wasm/package.json';
export default function App() {
return (
<WithSkiaWeb
opts={{ locateFile: (file) => `https://cdn.jsdelivr.net.cn/npm/canvaskit-wasm@${version}/bin/full/${file}` }}
getComponent={() => import("./MySkiaComponent")}
);
}
tsx
import { WithSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { version } from 'canvaskit-wasm/package.json';
export default function App() {
return (
<WithSkiaWeb
opts={{ locateFile: (file) => `https://cdn.jsdelivr.net.cn/npm/canvaskit-wasm@${version}/bin/full/${file}` }}
getComponent={() => import("./MySkiaComponent")}
);
}

或者,使用延迟组件注册

tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { version } from 'canvaskit-wasm/package.json';
LoadSkiaWeb({
locateFile: (file) => `https://cdn.jsdelivr.net.cn/npm/canvaskit-wasm@${version}/bin/full/${file}`
}).then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});
tsx
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web";
import { version } from 'canvaskit-wasm/package.json';
LoadSkiaWeb({
locateFile: (file) => `https://cdn.jsdelivr.net.cn/npm/canvaskit-wasm@${version}/bin/full/${file}`
}).then(async () => {
const App = (await import("./src/App")).default;
AppRegistry.registerComponent("Example", () => App);
});

不支持的功能

以下 React Native Skia API 目前在 React Native Web 上不受支持。 要请求这些功能,请在 GitHub 上提交功能请求

不支持

  • PathEffectFactory.MakeSum()
  • PathEffectFactory.MakeCompose()
  • PathFactory.MakeFromText()
  • ShaderFilter

手动 webpack 安装

要在 Web 上使用 webpack 启用 React Native Skia,需要三个关键操作

  • 确保构建系统可以访问 canvaskit.wasm 文件。
  • 配置构建系统以解析 fspath 节点模块,这可以通过node polyfill 插件来实现。
  • 更新 react-native-reanimatedreact-native/Libraries/Image/AssetRegistry 的别名,以便 webpack 可以进行捆绑。

这是一个支持 React Native Skia 的 webpack v5 配置示例

tsx
import fs from "fs";
import { sources } from "webpack";
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
const newConfiguration = {
...currentConfiguration,
plugins: [
...currentConfiguration.plugins,
// 1. Ensure wasm file availability
new (class CopySkiaPlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: "copy-skia",
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
async () => {
const src = require.resolve("canvaskit-wasm/bin/full/canvaskit.wasm");
if (!compilation.getAsset(src)) {
compilation.emitAsset("/canvaskit.wasm", new sources.RawSource(await fs.promises.readFile(src)));
}
}
);
});
}
})(),
// 2. Polyfill fs and path modules
new NodePolyfillPlugin()
],
alias: {
...currentConfiguration.alias,
// 3. Suppress reanimated module warning
// This assumes Reanimated is installed, if not you can use false.
"react-native-reanimated/package.json": require.resolve(
"react-native-reanimated/package.json"
),
"react-native-reanimated": require.resolve("react-native-reanimated"),
"react-native/Libraries/Image/AssetRegistry": false,
},
}
tsx
import fs from "fs";
import { sources } from "webpack";
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
const newConfiguration = {
...currentConfiguration,
plugins: [
...currentConfiguration.plugins,
// 1. Ensure wasm file availability
new (class CopySkiaPlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: "copy-skia",
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
async () => {
const src = require.resolve("canvaskit-wasm/bin/full/canvaskit.wasm");
if (!compilation.getAsset(src)) {
compilation.emitAsset("/canvaskit.wasm", new sources.RawSource(await fs.promises.readFile(src)));
}
}
);
});
}
})(),
// 2. Polyfill fs and path modules
new NodePolyfillPlugin()
],
alias: {
...currentConfiguration.alias,
// 3. Suppress reanimated module warning
// This assumes Reanimated is installed, if not you can use false.
"react-native-reanimated/package.json": require.resolve(
"react-native-reanimated/package.json"
),
"react-native-reanimated": require.resolve("react-native-reanimated"),
"react-native/Libraries/Image/AssetRegistry": false,
},
}

最后,继续加载 Skia