- 图形学
- 移动开发
- 跨平台
- UI组件
【免费下载链接】react-native-skia
High-performance React Native Graphics using Skia
<输出文章>
React Native Skia Path Effects 实战指南:六种路径效果的用法、参数与源码原理
导读
Path Effects(路径效果)是 @shopify/react-native-skia 中用来在绘制路径(Path)或形状(Rect等)时即时改变其外观形态的声明式组件,本质上是 Skia 原生SkPathEffect的 React 封装。本文以 path-effects.md 为骨架,完整讲解 Discrete、Dash、Corner、Path1D、Path2D、Line2D 六种路径效果的定义、参数、完整可运行示例与效果图,并结合 PathEffect.ts、PathEffects.ts 与 PathEffects.ts(Recorder 命令) 等源码,揭示每个组件背后的原生调用链与参数约束。读完本文,你将能够在自己的 Canvas 场景中熟练运用路径效果实现虚线、粗糙手绘风、圆角拐角、自定义笔刷与网点填充等绘制效果。
适用前提:以下所有组件均需在
<Canvas>内部、作为Path/Rect等绘制节点的子节点使用;示例基于当前仓库 example 中的用法,版本以仓库package.json实际发布内容为准。
背景:什么是 PathEffect,React Native Skia 如何封装它
在 Skia 中,SkPathEffect是一类作用于路径几何数据的处理器:它在绘制发生之前修改路径的轮廓,例如把路径打碎、加虚线、圆角化或用另一个路径去重复"盖章"。与滤镜(ImageFilter)不同,PathEffect 改变的是路径本身的形态而非像素颜色。
React Native Skia 的封装分为两层:
- 声明式组件层(渲染器):
packages/skia/src/renderer/components/pathEffects/下的Discrete.tsx、Dash.tsx、Corner.tsx、Path1D.tsx、Path2D.tsx、Line2D.tsx、Sum.tsx分别渲染skDiscretePathEffect等底层 DOM 节点,例如 Discrete.tsx 只是简单地把 props 透传给<skDiscretePathEffect seed={seed} {...props} />。 - 属性类型层:
packages/skia/src/dom/types/PathEffects.ts定义了每个组件的 Props 接口(如DiscretePathEffectProps、DashPathEffectProps),这些类型同时约束了声明式 API 的参数。
在 Skia Worklet 记录阶段,Recorder/commands/PathEffects.ts 中的pushPathEffect会把每种节点分发到对应的declareXxxPathEffect函数,最终调用ctx.Skia.PathEffect.MakeXxx(...)原生工厂方法并把结果压入ctx.pathEffects栈;当路径上有多个效果时,composePathEffects会用MakeCompose逐对合并。理解这条链路,有助于理解后面每个组件的参数语义与顺序敏感性。
DiscretePathEffect:把路径打碎成粗糙的"手绘"线段
组件作用与参数
DiscretePathEffect创建一个效果:把路径按指定长度切成若干小段,并将每段的端点以最大偏差随机移离原路径,形成类似手绘/锯齿的离散化外观。
| Name | Type | 描述 |
|---|---|---|
| length | number | 子段的长度。值越小分段越密,锯齿感越强。 |
| deviation | number | 端点随机移动的最大限制(偏差量)。值越大偏离越剧烈。 |
| seed | number | 修改随机性(随机种子),详见 SkDiscretePathEffect.h。默认值 0(由组件实现提供)。 |
| children? | PathEffect | 可选的、要叠加应用的路径效果。 |
注意:seed在 PathEffects.ts 类型定义 中是必填的,但声明式组件 Discrete.tsx 用SkiaDefaultProps<DiscretePathEffectProps, "seed">为其提供了默认值 0,因此 JSX 中可以不传。
完整示例
import {Canvas, DiscretePathEffect, Path} from "@shopify/react-native-skia"; const logo = "M256 128.015C256 111.057 234.762..."; const Discrete = () => { return ( <Canvas style={{ flex: 1 }}> <Path path={logo} color="#61DAFB"> <DiscretePathEffect length={10} deviation={2} /> </Path> </Canvas> ); };效果图
源码级补充:参数如何流入原生层
在 PathEffect.ts 中,对应的原生工厂方法签名与注释为:
MakeDiscrete(segLength: number, dev: number, seedAssist: number): SkPathEffect; // "breaks path into segments of segLength length, and randomly move // the endpoints away from the original path by a maximum of deviation"而 Recorder 层 declareDiscretePathEffect 会解构{ length, deviation, seed }后直接调用PathEffect.MakeDiscrete(length, deviation, seed),三者的对应关系一目了然。端到端测试 PathEffects.spec.tsx 中用length={10} deviation={4} seed={0}绘制星形路径并与snapshots/path-effects/discrete.png做快照比对,同时特意注明"所有与噪声相关的测试只在 E2E 后端运行"——因为随机噪声结果依赖具体渲染后端,无法在 CPU 后端稳定复现。
参数取值建议:
length越小、deviation越大,效果越"粗糙"。seed固定后,同一路径同一参数会得到确定性的随机结果,适合做动画中间帧的稳定插值。
DashPathEffect:虚线/点线效果
组件作用与参数
DashPathEffect为路径添加**虚线(dash)**效果。它根据intervals数组定义的"开-关"序列沿路径绘制线段。
| Name | Type | 描述 |
|---|---|---|
| intervals | number[] | 数组必须包含偶数个条目:偶数索引指定"实线(on)"段的长度,奇数索引指定"空白(off)"段的长度。 |
| phase | number | 相对于 intervals 数组起点的偏移量,用于错开虚线相位。默认值 0。 |
| children? | PathEffect | 可选的要叠加应用的路径效果。 |
完整示例
import {Canvas, DashPathEffect, Path} from "@shopify/react-native-skia"; const logo = "M256 128.015C256 111.057 234.762..."; const Discrete = () => { return ( <Canvas style={{ flex: 1 }}> <Path path={logo} color="#61DAFB" style="stroke" strokeWidth={4}> <DashPathEffect intervals={[4, 4]} /> </Path> </Canvas> ); };示例中intervals={[4, 4]}表示"画 4 个单位的实线、空 4 个单位的空白",形成均匀的虚线。重要提示:虚线对描边路径才有直观意义,因此示例中Path使用了style="stroke"与strokeWidth={4};若用于填充路径,效果视觉上不明显。
效果图
源码级补充:intervals 的偶数约束与 phase
原生定义在 PathEffect.ts 中:
MakeDash(intervals: number[], phase?: number): SkPathEffect; // "even number of entries with even indicies specifying the length of // the 'on' intervals, and the odd indices specifying the length of 'off'"声明式组件 Dash.tsx 直接透传phase(可省略),Recorder 的 declareDashPathEffect 调用MakeDash(intervals, phase)。phase用于产生"跑马灯"式的移动虚线:通过让phase随时间变化(配合 动画钩子),可以做出剪裁动画中常见的流动虚线进度效果。
CornerPathEffect:把锐角变圆角
组件作用与参数
CornerPathEffect把路径中的尖角替换为指定半径的圆角。
| Name | Type | 描述 |
|---|---|---|
| r | number | 圆角半径。 |
| children? | PathEffect | 可选的要叠加应用的路径效果。 |
完整示例
import {Canvas, CornerPathEffect, Rect} from "@shopify/react-native-skia"; const Discrete = () => { return ( <Canvas style={{ flex: 1 }}> <Rect x={64} y={16} width={128} height={256 - 16} color="#61DAFB" > <CornerPathEffect r={64} /> </Rect> </Canvas> ); };注意本示例的作用对象不是Path而是Rect——路径效果可以作用在任意生成路径的绘制节点上,这里是让矩形四角变成半径 64 的圆角。r={64}已接近矩形短边一半,因此得到的几乎是一个胶囊形状。
效果图
源码级补充:r <= 0 时的行为
原生注释明确指出边界条件:
MakeCorner(radius: number): SkPathEffect | null; // "if <=0, returns null"在 declareCornerPathEffect 中,当MakeCorner(r)返回null时会抛出"CornerPathEffect: couldn't create path effect"。因此不要把r设为 0 或负数。作为圆角替代方案,Rect自身也支持直接传r属性实现圆角,但当你想在任意自定义路径上做圆角化时,CornerPathEffect是通用解法。
Path1DPathEffect:用指定路径"盖章"出虚线
组件作用与参数
Path1DPathEffect通过沿路径重复复制(replicate)指定的子路径来实现虚线:每一处"盖章"都用子路径替换一段实线,子路径的朝向可随当前位置的切线与法线旋转。
| Name | Type | 描述 |
|---|---|---|
| path | PathDef | 要重复复制(作为 dash 单元)的路径,可以是字符串 SVG 路径或SkPath。 |
| advance | number | 子路径实例之间的间距。 |
| phase | number | 沿路径的初始位置偏移(对 advance 取模),用于调整首枚"章"的位置。 |
| style | Path1DEffectStyle | 在每个点如何变换子路径(基于当前位置与切线):Translate/Rotate/Morph。 |
| children? | PathEffect | 可选的要叠加应用的路径效果。 |
完整示例
import {Canvas, Path1DPathEffect, Path} from "@shopify/react-native-skia"; const logo = "M256 128.015C256 111.057 234.762..."; const Path1D = () => { return ( <Canvas style={{ flex: 1 }}> <Path path={logo} color="#61DAFB" style="stroke" strokeWidth={15}> <Path1DPathEffect path="M -10 0 L 0 -10, 10 0, 0 10 Z" advance={20} phase={0} style="rotate" /> </Path> </Canvas> ); };示例中用一个小菱形"M -10 0 L 0 -10, 10 0, 0 10 Z"作为"笔刷",advance={20}控制间距,style="rotate"让每个菱形随路径切线方向旋转,形成沿 Logo 轮廓排布的菱形链。
效果图
源码级补充:Path1DEffectStyle 枚举与 worklet 转换
原生枚举定义在 PathEffect.ts:
export enum Path1DEffectStyle { Translate, // 0:仅平移,不旋转 Rotate, // 1:随切线旋转 Morph, // 2:随切线/法线变形 }JSX 中 style 传入的是字符串,Recorder 的 declarePath1DPathEffect 使用enumKey(style)把字符串映射回枚举数字后再调用MakePath1D(path, advance, phase, Path1DEffectStyle[enumKey(style)])。三种风格的区别:
Translate:子路径始终保持自身朝向,仅做平移排布;Rotate:子路径随路径切点方向旋转(示例所用);Morph:子路径同时依据切线做更复杂的形态变化。
若
MakePath1D返回null(例如 path 无效),Recorder 会抛出"Path1DPathEffect: could not create path effect"。
Path2DPathEffect:按矩阵格点"盖章"填满形状
组件作用与参数
Path2DPathEffect把指定的子路径按照矩阵定义的网格重复盖章(stamp),直到填满目标形状。
| Name | Type | 描述 |
|---|---|---|
| path | PathDef | 要盖章使用的子路径。 |
| matrix | SkMatrix | 用于定义网格(格点间距/旋转)的矩阵。 |
| children? | PathEffect | 可选的要叠加应用的路径效果。 |
完整示例
import {Canvas, Path2DPathEffect, Path, processTransform2d} from "@shopify/react-native-skia"; const logo = "M256 128.015C256 111.057 234.762..."; const Path2D = () => { return ( <Canvas style={{ flex: 1 }}> <Path path={logo} color="#61DAFB" style="stroke" strokeWidth={15}> <Path2DPathEffect path="M -10 0 L 0 -10, 10 0, 0 10 Z" matrix={processTransform2d([{ scale: 40 }])} /> </Path> </Canvas> ); };processTransform2d([{ scale: 40 }])生成一个缩放 40 倍的矩阵——矩阵的缩放决定了网格间距:子路径"M -10 0 L 0 -10, 10 0, 0 10 Z"的尺寸约为 20×20,缩放 40 后即按 40 单位间距排布。
效果图
源码级补充:processTransform2d 与矩阵几何
processTransform2d定义在 core/Matrix.ts:
export const processTransform2d = (transforms: Transforms3d) => { "worklet"; return processTransform(Skia.Matrix(), transforms); };它把变换描述数组(如[{ scale: 40 }]、[{ translateX: 20, translateY: 20 }, { rotate: Math.PI / 4 }])投影到一个新矩阵上。原生工厂方法 MakePath2D 的注释说明:"the scale of the matrix determines how far apart the path instances should be drawn",即矩阵缩放决定盖章间距、旋转决定盖章朝向。Recorder 的 declarePath2DPathEffect 会先processPath把PathDef(字符串/对象)转为SkPath,再调用MakePath2D(matrix, path);当返回null时抛出"Path2DPathEffect: invalid path"。
Line2DPathEffect:用平行线纹理填满形状
组件作用与参数
Line2DPathEffect用一组无限长的平行直线(由矩阵决定间距与角度)以填充图案的方式盖满目标形状。
| Name | Type | 描述 |
|---|---|---|
| width | number | 线条的宽度(必须 >= 0)。 |
| matrix | SkMatrix | 用于定义网格(线间距/朝向)的矩阵。 |
| children? | PathEffect | 可选的要叠加应用的路径效果。 |
说明:原文档参数表中
width一行标注的类型有笔误(写成了PathDef),实际类型为number,以 dom/types/PathEffects.ts 中的Line2DPathEffectProps { width: number; matrix: SkMatrix }为准。
完整示例
import {Canvas, Line2DPathEffect, Path, processTransform2d} from "@shopify/react-native-skia"; const logo = "M256 128.015C256 111.057 234.762..."; const Line2D = () => { return ( <Canvas style={{ flex: 1 }}> <Path path={logo} color="#61DAFB" style="stroke" strokeWidth={15}> <Line2DPathEffect width={0} matrix={processTransform2d([{ scale: 8 }])} /> </Path> </Canvas> ); };width={0}生成最细的线,scale: 8让平行线以 8 单位间距排布,形成细密的剖面线纹理。
效果图
源码级补充:width 的边界条件
原生注释对width有硬性约束:
MakeLine2D(width: number, matrix: SkMatrix): SkPathEffect | null; // "width must be >= 0"Recorder 的 declareLine2DPathEffect 在返回null时抛出"Line2DPathEffect: could not create path effect"。与 Path2D 同理,矩阵的缩放决定线距、旋转决定线向;矩阵对 Line2D 的效果描述为:"the scale of the provided matrix will determine how far apart the lines should be drawn, its rotation affects the lines' orientation"。
组合多个路径效果:SumPathEffect 与嵌套 children
文档中每个组件都声明了可选的children?: PathEffect。当你在一个效果内嵌套另一个效果时,React Native Skia 会按声明顺序把它们组合。仓库提供了显式的组合组件SumPathEffect(见 Sum.tsx),其 Recorder 实现 declareSumPathEffect 会弹出栈中所有已声明效果并用PathEffect.MakeSum依次合并;而栈中仅有一个效果时,composePathEffects使用MakeCompose(outer, inner)把两个效果按"先内后外"叠加(outer(inner(path)))。
端到端测试 PathEffects.spec.tsx 演示了求和组合:
<Path path={path} style="stroke" strokeWidth={2} color="lightblue"> <SumPathEffect> <DiscretePathEffect length={10} deviation={4} seed={0} /> <DiscretePathEffect length={10} deviation={4} seed={12345} /> </SumPathEffect> </Path>测试用两个不同 seed 的离散效果求和,生成snapshots/path-effects/sum.png快照——两种随机噪声叠加后形成更复杂的锯齿纹理。原生 MakeSum / MakeCompose 的注释也明确了区别:MakeSum是"两个效果按顺序先后作用于路径"(first(path) + second(path)),MakeCompose是"先应用 inner 再应用 outer"(outer(inner(path)))。实际使用时,通过直接嵌套 children或显式<SumPathEffect>都能达到组合目的,但组合顺序会影响最终效果,需要按预期视觉效果调整声明次序。
参数速查与实战建议
| 组件 | 必填参数 | 可选参数 | 典型用途 |
|---|---|---|---|
| DiscretePathEffect | length,deviation | seed(默认 0) | 手绘风、粗糙边缘、噪点化描边 |
| DashPathEffect | intervals(偶数长度数组) | phase(默认 0) | 虚线、流动虚线进度动画 |
| CornerPathEffect | r(> 0) | — | 圆角化任意路径与形状 |
| Path1DPathEffect | path,advance,phase,style | — | 自定义笔刷虚线(箭头/菱形链) |
| Path2DPathEffect | path,matrix | — | 网格盖章填充(图案纹理) |
| Line2DPathEffect | width(>= 0),matrix | — | 剖面线、斜线纹理填充 |
实战要点:
- 虚线类效果请配合
style="stroke"与strokeWidth使用(Dash 最典型),否则效果不明显; - 矩阵类效果(Path2D/Line2D):矩阵缩放控制单元间距,旋转控制图案朝向,用
processTransform2d([{ scale: N }])即可快速生成; - 组合多个效果时注意顺序——
MakeCompose先内后外,MakeSum顺序叠加,结果截然不同; - 边界条件:
CornerPathEffect.r <= 0、Line2DPathEffect.width < 0、无效的PathDef都会让原生工厂返回null,并在 Recorder 层抛出异常,编码时需避开; - 随机类效果(Discrete):固定
seed可获得确定性输出;涉及噪声的渲染在 CPU 后端可能不稳定,仓库测试将其限定在 E2E 后端运行(见 PathEffects.spec.tsx)。
更完整的 API 类型与原生工厂签名可查阅 dom/types/PathEffects.ts 与 skia/types/PathEffect.ts;如果你需要把路径效果与动画结合,可进一步参考 动画相关文档。
- 图形学
- 移动开发
- 跨平台
- UI组件
【免费下载链接】react-native-skia
High-performance React Native Graphics using Skia
相关推荐
Cytoscape.js 最短路径实战:eles.dijkstra() 算法用法、参数与源码原理详解
Cytoscape.js 最短路径实战:eles.dijkstra 算法用法、参数与源码原理详解 导读 Dijkstra(迪杰斯特拉)算法是图论中最经典的"单源
数据可视化MoviePy 淡出效果 FadeOut 深度指南:参数、源码原理与实战用法
MoviePy 淡出效果 FadeOut 深度指南:参数、源码原理与实战用法 本篇指南以 MoviePy 官方 API 参考文档 moviepy.video.f
音视频视频处理音频处理在电脑上重温经典:Citra 3DS模拟器终极使用指南
在电脑上重温经典:Citra 3DS模拟器终极使用指南 想在电脑大屏幕上畅玩《精灵宝可梦》、《塞尔达传说》等Nintendo 3DS经典游戏吗?Citra模拟器
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考