Web 支持
React Native Skia 通过 CanvasKit 在浏览器中运行,CanvasKit 是 Skia 的 WebAssembly (WASM) 构建版本。 CanvasKit WASM 文件在 gzip 压缩后为 2.9MB,它是异步加载的。 尽管它体积较大,但它在决定何时以及如何加载 Skia 方面提供了灵活性,让您可以完全控制用户体验。
我们支持与 Expo 和 Remotion 的直接集成。此外,您还可以找到适用于任何 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.tsx
和 index.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.tsxgetComponent={() => 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.tsxgetComponent={() => 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 (<WithSkiaWebopts={{ 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 (<WithSkiaWebopts={{ 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
文件。 - 配置构建系统以解析
fs
和path
节点模块,这可以通过node polyfill 插件来实现。 - 更新
react-native-reanimated
和react-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 availabilitynew (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 modulesnew 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 availabilitynew (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 modulesnew 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。