- 图形学
- 移动开发
- 跨平台
- UI组件
【免费下载链接】react-native-skia
High-performance React Native Graphics using Skia
本文围绕 react-native-skia 提供的Offset图像过滤器展开,讲解如何沿 X/Y 轴平移被过滤的图像,涵盖组件属性、声明式用法、与Skia.ImageFilter.MakeOffset底层 API 的对应关系,以及如何将其与Blur等过滤器组合使用。读完本文,你将掌握在 apps/docs/docs/image-filters/offset.md 场景下实现像素级平移、组合滤镜与 Web 端兼容性处理的具体方案。
Offset 是什么
Offset是 react-native-skia 提供的图像过滤器(Image Filter)组件。图像过滤器是一类作用于图像所有像素颜色位上的效果(见 图像过滤器总览),而Offset的作用非常直观:将输入图像沿 X 轴与 Y 轴平移指定的像素距离,其余像素保持原样。
该过滤器与 SVG 的feOffset滤镜(即 Web 标准中的<feOffset>元素)行为一致,因此熟悉 SVG 滤镜的开发者可以无缝迁移到 react-native-skia。
组件属性
Offset组件接受以下属性:
| 名称 | 类型 | 说明 |
|---|---|---|
| x | number | 沿 X 轴的偏移量(单位:像素)。 |
| y | number | 沿 Y 轴的偏移量(单位:像素)。 |
| children? | ImageFilter | 可选的图像过滤器,会先被应用,再执行本偏移。 |
从源码看,Offset的组件实现位于 packages/skia/src/renderer/components/imageFilters/Offset.tsx,其类型定义OffsetImageFilterProps位于 packages/skia/src/dom/types/ImageFilters.ts:
export interface OffsetImageFilterProps extends ChildrenProps { x: number; y: number; }组件默认值为x = 0、y = 0,即不传属性时过滤器不产生任何平移效果。x、y均可为负值(向相反方向平移)。
基础示例:平移一张图片
下面是最简单的用法——在Canvas中绘制一张图片,并应用Offset将其向右下角各平移 64 像素:
import { Canvas, Image, Offset, useImage, Fill } from "@shopify/react-native-skia"; const Filter = () => { const image = useImage(require("./assets/oslo.jpg")); if (!image) { return null; } return ( <Canvas style={{ width: 256, height: 256 }}> <Fill color="lightblue" /> <Image image={image} x={0} y={0} width={256} height={256} fit="cover" > <Offset x={64} y={64} /> </Image> </Canvas> ); };要点说明:
useImage返回的图片加载完成前为null,因此需要先做空值判断(如上例中的if (!image)提前返回);Offset作为<Image>的子组件(children)使用,形成"先渲染图片、再对结果施加偏移"的滤镜管线;- 背景先填充了
lightblue,这样偏移后露出的画布区域可见,能直观看到平移产生的"空边"效果; fit="cover"会让图片按覆盖方式填充到 256×256 的区域。
运行效果如下图所示(完整示例源自 offset.md 示例代码):
组合滤镜:Offset 作为子过滤器管道的一环
Offset的children属性允许传入另一个图像过滤器,使其先于偏移执行。这在构建复合效果时非常实用,例如"先模糊、再平移":
import { Canvas, Image, Offset, Blur, useImage } from "@shopify/react-native-skia"; const Composed = () => { const image = useImage(require("./assets/oslo.jpg")); if (!image) { return null; } return ( <Canvas style={{ width: 256, height: 256 }}> <Image image={image} x={0} y={0} width={256} height={256} fit="cover"> <Offset x={40} y={40}> <Blur blur={6} mode="clamp" /> </Offset> </Image> </Canvas> ); };这里<Blur>作为Offset的子节点先执行模糊,模糊结果再被整体平移 40 像素。这种"子过滤器先应用、父过滤器后应用"的嵌套语义,与 图像过滤器总览 中介绍的组合方式(如ColorMatrix嵌套在Blur内)一脉相承,也呼应了 SVG 滤镜 DAG(有向无环图)中的输入-输出串联模型。
底层实现:从组件到 Skia.ImageFilter.MakeOffset
声明式组件如何被翻译为底层 API
Offset组件本身是一个轻量封装(Offset.tsx),它把x、y转发给 DOM 节点skOffsetImageFilter。在渲染记录(Recorder)层,declareOffsetImageFilter负责把属性翻译成 Skia 工厂调用(packages/skia/src/sksg/Recorder/commands/ImageFilters.ts):
const declareOffsetImageFilter = ( ctx: DrawingContext, props: OffsetImageFilterProps ) => { "worklet"; const { x, y } = props; const imgf = ctx.Skia.ImageFilter.MakeOffset(x, y, null); ctx.imageFilters.push(imgf); };注意这里传给MakeOffset的第三个参数是null,表示使用"动态源图像"(即当前正在绘制的输入内容)作为过滤输入。这解释了声明式用法中为何Offset无需显式指定输入——它天然作用于其宿主元素(如<Image>)的渲染结果。
命令式 API:Skia.ImageFilter.MakeOffset
如果你更偏好命令式写法,可以直接使用Skia.ImageFilter.MakeOffset工厂方法(类型签名见 packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts):
MakeOffset( dx: number, dy: number, input?: SkImageFilter | null, // 为 null 时使用动态源图像 cropRect?: SkRect | null // 可选:裁剪输入与输出 ): SkImageFilter;参数说明:
dx/dy:沿 X / Y 轴的偏移量;input:输入过滤器;为null时使用动态源图像(例如保存的图层或当前绘制内容);cropRect:可选的裁剪矩形,同时裁剪输入与输出。
该命令式过滤器同样可以作为<ImageFilter filter={...} />的filter属性传入声明式组件。仓库的 e2e 测试(packages/skia/src/renderer/tests/e2e/ImageFilter.spec.tsx)展示了这一用法:
const offsetFilter = Skia.ImageFilter.MakeOffset(20, 20, null); const img = await surface.draw( <Group> <ImageFilter filter={offsetFilter} /> <Circle cx={50} cy={50} r={30} color="blue" /> </Group> ); checkImage(img, docPath("image-filter/offset-filter.png"));同一个测试文件还验证了"偏移 + 模糊"的复合过滤器写法(Skia.ImageFilter.MakeOffset(10, 10, blurFilter)),与声明式children嵌套在语义上完全等价。
组合与合成顺序
在渲染记录器中,多个过滤器通过Skia.ImageFilter.MakeCompose(outer, inner)依次合成(composeImageFilters):后弹出的过滤器作为外层的outer,先弹出的作为内层的inner。因此嵌套顺序决定了效果的执行顺序——越内层的子过滤器越先作用于源图像,这一点在组合Blur与Offset时需特别留意。
平台差异:React Native Web 上的注意点
react-native-skia 同时支持原生(Android/iOS)与 Web 平台。在 Web 端,MakeOffset由 CanvasKit 实现(packages/skia/src/skia/web/JsiSkImageFilterFactory.ts):
MakeOffset(dx, dy, input?, cropRect?) { const inputFilter = input === null || input === undefined ? null : JsiSkImageFilter.fromValue(input); if (cropRect) { console.warn("cropRect is not supported on React Native Web for MakeOffset"); } const filter = this.CanvasKit.ImageFilter.MakeOffset(dx, dy, inputFilter); return new JsiSkImageFilter(this.CanvasKit, filter); }两点平台差异值得记住:
cropRect在 Web 端不被支持:传入时会打印一条警告"cropRect is not supported on React Native Web for MakeOffset"并被忽略,因此在跨平台项目中应避免依赖cropRect参数;input为null或undefined时,Web 端同样会回退为使用动态源图像,行为与原生端一致。
典型应用场景
Offset虽然简单,却是很多复杂效果的基础构件,常见用途包括:
- 投影/描边效果:
DropShadow过滤器内部正是通过"偏移 + 模糊 + 混合"实现的。事实上,MakeInnerShadow的源码(ImageFilters.ts)就调用了MakeOffset(dx, dy, f1)来定位阴影:先生成轮廓,再偏移出阴影位置,最后模糊。 - 像素级平移:将贴图、文字或图形整体移动若干像素,制造"错位"或"重影"效果;
- 复合滤镜的中间步骤:作为过滤器 DAG 中的一环,例如先偏移再混合(
MakeCompose/MakeBlend),实现更精细的合成控制; - 动画偏移:由于
Offset的属性可以被 react-native-skia 的动画系统驱动(x、y支持传入动画值),可以轻松实现图片缓慢滑动、视差滚动等动效。
小结
Offset是 react-native-skia 中最基础的图像过滤器之一,等价于 SVG 的feOffset,作用是把输入图像沿 X/Y 轴平移;- 属性极简:
x、y两个必选数字,children可嵌套其他过滤器实现"先过滤、再偏移"; - 底层由
Skia.ImageFilter.MakeOffset(dx, dy, input, cropRect)实现,声明式组件与命令式 API 完全互通; - Web 端基于 CanvasKit 实现,但
cropRect不被支持,跨平台开发时需规避。
如需查看更多过滤器,可继续阅读 图像过滤器总览、模糊滤镜 Blur、位移映射 DisplacementMap 与 阴影 Shadows。
- 图形学
- 移动开发
- 跨平台
- UI组件
【免费下载链接】react-native-skia
High-performance React Native Graphics using Skia
相关推荐
React Native Skia Morphology 图像滤镜:基于 Skia 的腐蚀(erode)与膨胀(dilate)实现指南
React Native Skia Morphology 图像滤镜:基于 Skia 的腐蚀(erode)与膨胀(dilate)实现指南 Morphology(形
图形学移动开发跨平台UI组件Rust 标准库指针偏移详解:`ptr::offset` 的语义、安全契约与底层实现
Rust 标准库指针偏移详解: ptr::offset 的语义、安全契约与底层实现 指针偏移(pointer offset)是 Rust 中所有内存遍历与数据结
编程语言编译器语言运行时标准库告别"直升机"模式:华硕笔记本风扇静音改造完全指南
告别"直升机"模式:华硕笔记本风扇静音改造完全指南 你是否曾在深夜工作时,笔记本风扇突然"起飞"打断你的思绪?或者在视频会议中,风扇噪音让你不得不重复发言?如果
桌面应用系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考