Skip to content

Commit 41f3970

Browse files
authored
feat(🎨): zIndex (#3557)
1 parent fe07cb2 commit 41f3970

File tree

26 files changed

+1027
-490
lines changed

26 files changed

+1027
-490
lines changed

‎apps/docs/docs/group.md‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ It can apply the following operations to its children:
2121
| clip? | `RectOrRRectOrPath` | Rectangle, rounded rectangle, or Path to use to clip the children. |
2222
| invertClip? | `boolean` | Invert the clipping region: parts outside the clipping region will be shown and, inside will be hidden. |
2323
| layer? | `RefObject<Paint>` | Draws the children as a bitmap and applies the effects provided by the paint. |
24+
| zIndex? | `number` | Overrides the drawing order of the children. A child with a higher zIndex will be drawn on top of a child with a lower zIndex. The zIndex is local to the group. The default zIndex is 0. Negative values are supported. |
2425

2526
The following three components are not being affected by the group properties. To apply paint effects on these component, you need to use [layer effects](#layer-effects).
2627
In each component reference, we also document how to apply paint effects on them.
@@ -289,6 +290,43 @@ const Clip = () => {
289290

290291
<img alt="Rasterize" src={require("/static/img/group/rasterize.png").default} width="256" height="256" />
291292

293+
## Drawing Order (zIndex)
294+
295+
The `zIndex` property allows you to control the drawing order of elements. It can be applied to a group or a drawing command. An element with a higher `zIndex` will be drawn on top of a sibling element with a lower `zIndex`. The default `zIndex` is 0, and negative values are supported.
296+
297+
The `zIndex` is scoped to the parent [`<Group />`](/docs/group). This means that the `zIndex` of an element only affects its drawing order relative to its siblings within the same group. A group's `zIndex` will determine its order among its sibling groups.
298+
299+
### Example
300+
301+
In the example below, the cyan circle (`zIndex={2}`) is drawn on top, followed by the magenta circle (`zIndex={1}`), and finally the yellow circle (`zIndex={0}`).
302+
303+
```tsx twoslash
304+
import { Canvas, Circle, Group, BlurMask } from "@shopify/react-native-skia";
305+
306+
export const ZIndexDemo = () => {
307+
const r = 80;
308+
const width = 256;
309+
const height = 256;
310+
return (
311+
<Canvas style={{ width, height }}>
312+
<Group>
313+
<BlurMask style="solid" blur={10} />
314+
<Circle cx={r} cy={r} r={r} color="cyan" zIndex={2} />
315+
<Circle cx={width - r} cy={r} r={r} color="magenta" zIndex={1} />
316+
<Circle
317+
cx={width / 2}
318+
cy={height - r}
319+
r={r}
320+
color="yellow"
321+
zIndex={0}
322+
/>
323+
</Group>
324+
</Canvas>
325+
);
326+
};
327+
```
328+
329+
292330
## Fitbox
293331

294332
The `FitBox` component is based on the `Group` component and allows you to scale drawings to fit into a destination rectangle automatically.
@@ -332,4 +370,4 @@ const Hello = () => {
332370
};
333371
```
334372

335-
<img src={require("/static/img/group/scale-path.png").default} width="256" height="256" />
373+
<img src={require("/static/img/group/scale-path.png").default} width="256" height="256" />

‎apps/example/ios/Podfile.lock‎

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2116,76 +2116,76 @@ SPEC CHECKSUMS:
21162116
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
21172117
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
21182118
hermes-engine: b417d2b2aee3b89b58e63e23a51e02be91dc876d
2119-
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
2119+
RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809
21202120
RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35
21212121
RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea
21222122
RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd
21232123
React: b229c49ed5898dab46d60f61ed5a0bfa2ee2fadb
21242124
React-callinvoker: 2ac508e92c8bd9cf834cc7d7787d94352e4af58f
2125-
React-Core: 325b4f6d9162ae8b9a6ff42fe78e260eb124180d
2126-
React-CoreModules: 558041e5258f70cd1092f82778d07b8b2ff01897
2127-
React-cxxreact: 8fff17cbe76e6a8f9991b59552e1235429f9c74b
2125+
React-Core: 13cdd1558d0b3f6d9d5a22e14d89150280e79f02
2126+
React-CoreModules: b07a6744f48305405e67c845ebf481b6551b712a
2127+
React-cxxreact: 1055a86c66ac35b4e80bd5fb766aed5f494dfff4
21282128
React-debug: 0a5fcdbacc6becba0521e910c1bcfdb20f32a3f6
2129-
React-defaultsnativemodule: 618dc50a0fad41b489997c3eb7aba3a74479fd14
2130-
React-domnativemodule: 7ba599afb6c2a7ec3eb6450153e2efe0b8747e9a
2131-
React-Fabric: 252112089d2c63308f4cbfade4010b6606db67d1
2132-
React-FabricComponents: 3c0f75321680d14d124438ab279c64ec2a3d13c4
2133-
React-FabricImage: 728b8061cdec2857ca885fd605ee03ad43ffca98
2129+
React-defaultsnativemodule: 4bb28fc97fee5be63a9ebf8f7a435cfe8ba69459
2130+
React-domnativemodule: b36a11c2597243d7563985028c51ece988d8ae33
2131+
React-Fabric: afc561718f25b2cd800b709d934101afe376a12c
2132+
React-FabricComponents: f4e0a4e18a27bf6d39cbf2a0b42f37a92fa4e37f
2133+
React-FabricImage: 37d8e8b672eda68a19d71143eb65148084efb325
21342134
React-featureflags: 19682e02ef5861d96b992af16a19109c3dfc1200
2135-
React-featureflagsnativemodule: 23528c7e7d50782b7ef0804168ba40bbaf1e86ab
2136-
React-graphics: fefe48f71bfe6f48fd037f59e8277b12e91b6be1
2137-
React-hermes: a9a0c8377627b5506ef9a7b6f60a805c306e3f51
2138-
React-idlecallbacksnativemodule: 7e2b6a3b70e042f89cd91dbd73c479bb39a72a7e
2139-
React-ImageManager: e3300996ac2e2914bf821f71e2f2c92ae6e62ae2
2140-
React-jserrorhandler: fa75876c662e5d7e79d6efc763fc9f4c88e26986
2141-
React-jsi: f3f51595cc4c089037b536368f016d4742bf9cf7
2142-
React-jsiexecutor: cca6c232db461e2fd213a11e9364cfa6fdaa20eb
2143-
React-jsinspector: 2bd4c9fddf189d6ec2abf4948461060502582bef
2144-
React-jsinspectortracing: a417d8a0ad481edaa415734b4dac81e3e5ee7dc6
2145-
React-jsitracing: 1ff7172c5b0522cbf6c98d82bdbb160e49b5804e
2146-
React-logger: 018826bfd51b9f18e87f67db1590bc510ad20664
2147-
React-Mapbuffer: 3c11cee7737609275c7b66bd0b1de475f094cedf
2148-
React-microtasksnativemodule: 843f352b32aacbe13a9c750190d34df44c3e6c2c
2149-
react-native-safe-area-context: 0f14bce545abcdfbff79ce2e3c78c109f0be283e
2150-
react-native-skia: 83ea407f6494e4a24a9c971cbfccedca4fe00e65
2151-
React-NativeModulesApple: 88433b6946778bea9c153e27b671de15411bf225
2152-
React-perflogger: 9e8d3c0dc0194eb932162812a168aa5dc662f418
2153-
React-performancetimeline: 5a2d6efef52bdcefac079c7baa30934978acd023
2135+
React-featureflagsnativemodule: d7cddf6d907b4e5ab84f9e744b7e88461656e48c
2136+
React-graphics: b0f78580cdaf5800d25437e3d41cc6c3d83b7aea
2137+
React-hermes: 71186f872c932e4574d5feb3ed754dda63a0b3bd
2138+
React-idlecallbacksnativemodule: dd2af19cdd3bc55149d17a2409ed72b694dfbe9c
2139+
React-ImageManager: a77dde8d5aa6a2b6962c702bf3a47695ef0aa32b
2140+
React-jserrorhandler: 9c14e89f12d5904257a79aaf84a70cd2e5ac07ba
2141+
React-jsi: 0775a66820496769ad83e629f0f5cce621a57fc7
2142+
React-jsiexecutor: 2cf5ba481386803f3c88b85c63fa102cba5d769e
2143+
React-jsinspector: 8052d532bb7a98b6e021755674659802fb140cc5
2144+
React-jsinspectortracing: bdd8fd0adcb4813663562e7874c5842449df6d8a
2145+
React-jsitracing: 2bab3bf55de3d04baf205def375fa6643c47c794
2146+
React-logger: 795cd5055782db394f187f9db0477d4b25b44291
2147+
React-Mapbuffer: 0502faf46cab8fb89cfc7bf3e6c6109b6ef9b5de
2148+
React-microtasksnativemodule: 663bc64e3a96c5fc91081923ae7481adc1359a78
2149+
react-native-safe-area-context: 286b3e7b5589795bb85ffc38faf4c0706c48a092
2150+
react-native-skia: 0457c9311947ef4642e3f55d5647220c5e529eb2
2151+
React-NativeModulesApple: 16fbd5b040ff6c492dacc361d49e63cba7a6a7a1
2152+
React-perflogger: ab51b7592532a0ea45bf6eed7e6cae14a368b678
2153+
React-performancetimeline: bc2e48198ec814d578ac8401f65d78a574358203
21542154
React-RCTActionSheet: 592674cf61142497e0e820688f5a696e41bf16dd
2155-
React-RCTAnimation: e6d669872f9b3b4ab9527aab283b7c49283236b7
2156-
React-RCTAppDelegate: de2343fe08be4c945d57e0ecce44afcc7dd8fc03
2157-
React-RCTBlob: 3e2dce94c56218becc4b32b627fc2293149f798d
2158-
React-RCTFabric: cac2c033381d79a5956e08550b0220cb2d78ea93
2159-
React-RCTFBReactNativeSpec: d10ca5e0ccbfeac8c047361fedf8e4ac653887b6
2160-
React-RCTImage: dc04b176c022d12a8f55ae7a7279b1e091066ae0
2161-
React-RCTLinking: 88f5e37fe4f26fbc80791aa2a5f01baf9b9a3fd5
2162-
React-RCTNetwork: f213693565efbd698b8e9c18d700a514b49c0c8e
2163-
React-RCTSettings: a2d32a90c45a3575568cad850abc45924999b8a5
2164-
React-RCTText: 54cdcd1cbf6f6a91dc6317f5d2c2b7fc3f6bf7a0
2165-
React-RCTVibration: 11dae0e7f577b5807bb7d31e2e881eb46f854fd4
2155+
React-RCTAnimation: 8fbb8dba757b49c78f4db403133ab6399a4ce952
2156+
React-RCTAppDelegate: 7f88baa8cb4e5d6c38bb4d84339925c70c9ac864
2157+
React-RCTBlob: f89b162d0fe6b570a18e755eb16cbe356d3c6d17
2158+
React-RCTFabric: 8ad6d875abe6e87312cef90e4b15ef7f6bed72e6
2159+
React-RCTFBReactNativeSpec: 8c29630c2f379c729300e4c1e540f3d1b78d1936
2160+
React-RCTImage: ccac9969940f170503857733f9a5f63578e106e1
2161+
React-RCTLinking: d82427bbf18415a3732105383dff119131cadd90
2162+
React-RCTNetwork: 12ad4d0fbde939e00251ca5ca890da2e6825cc3c
2163+
React-RCTSettings: e7865bf9f455abf427da349c855f8644b5c39afa
2164+
React-RCTText: 2cdfd88745059ec3202a0842ea75a956c7d6f27d
2165+
React-RCTVibration: a3a1458e6230dfd64b3768ebc0a4aac430d9d508
21662166
React-rendererconsistency: 64e897e00d2568fd8dfe31e2496f80e85c0aaad1
2167-
React-rendererdebug: 41ce452460c44bba715d9e41d5493a96de277764
2167+
React-rendererdebug: a3f6d3ae7d2fa0035885026756281c07ee32479e
21682168
React-rncore: 58748c2aa445f56b99e5118dad0aedb51c40ce9f
2169-
React-RuntimeApple: 7785ed0d8ae54da65a88736bb63ca97608a6d933
2170-
React-RuntimeCore: 6029ea70bc77f98cfd43ebe69217f14e93ba1f12
2169+
React-RuntimeApple: f0fda7bacabd32daa099cfda8f07466c30acd149
2170+
React-RuntimeCore: 683ee0b6a76d4b4bf6fbf83a541895b4887cc636
21712171
React-runtimeexecutor: a188df372373baf5066e6e229177836488799f80
2172-
React-RuntimeHermes: a264609c28b796edfffc8ae4cb8fad1773ab948b
2173-
React-runtimescheduler: 23ec3a1e0fb1ec752d1a9c1fb15258c30bfc7222
2172+
React-RuntimeHermes: 907c8e9bec13ea6466b94828c088c24590d4d0b6
2173+
React-runtimescheduler: a2e2a39125dd6426b5d8b773f689d660cd7c5f60
21742174
React-timing: bb220a53a795ed57976a4855c521f3de2f298fe5
2175-
React-utils: 3b054aaebe658fc710a8d239d0e4b9fd3e0b78f9
2176-
ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8
2177-
ReactCodegen: e232f8db3a40721044ec81b9388f95a7afaad36a
2178-
ReactCommon: 0c097b53f03d6bf166edbcd0915da32f3015dd90
2179-
ReactNativeHost: 1b6ccdcfc87bb31e772a5b3c3ca3b09c90954328
2180-
ReactTestApp-DevSupport: ea18f446cff64b6c9a3e28788600c82ecf51bde6
2175+
React-utils: 300d8bbb6555dcffaca71e7a0663201b5c7edbbc
2176+
ReactAppDependencyProvider: f2e81d80afd71a8058589e19d8a134243fa53f17
2177+
ReactCodegen: 50b6e45bbbef9b39d9798820cdbe87bfc7922e22
2178+
ReactCommon: 3d39389f8e2a2157d5c999f8fba57bd1c8f226f0
2179+
ReactNativeHost: e96154926221741f253f2c1ded13c6959c5c3d43
2180+
ReactTestApp-DevSupport: 6994b53b5b81139a8ce63e0776c726c95de079a1
21812181
ReactTestApp-Resources: 1bd9ff10e4c24f2ad87101a32023721ae923bccf
2182-
RNGestureHandler: dcb1b1db024f3744b03af56d132f4f72c4c27195
2183-
RNReanimated: d5f33d14a4d1da33a02d89124de233a64b3aaeaa
2184-
RNScreens: 790123c4a28783d80a342ce42e8c7381bed62db1
2185-
RNSVG: 05a9310dfd69f18a69f6aa7ba5ddaa44bf24fd1c
2182+
RNGestureHandler: 66e593addd8952725107cfaa4f5e3378e946b541
2183+
RNReanimated: 858fe25904af44131b8b608a5005b64778609a6a
2184+
RNScreens: 0f01bbed9bd8045a8d58e4b46993c28c7f498f3c
2185+
RNSVG: 57b9a2e7d149f115d70848c3247f31c7010c6941
21862186
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
2187-
Yoga: afd04ff05ebe0121a00c468a8a3c8080221cb14c
2187+
Yoga: 9b7fb56e7b08cde60e2153344fa6afbd88e5d99f
21882188

21892189
PODFILE CHECKSUM: 87506345285a0371afb28b9c3e6daaa999c214f3
21902190

2191-
COCOAPODS: 1.16.2
2191+
COCOAPODS: 1.15.2

‎apps/example/src/Examples/API/List.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export const examples = [
5858
screen: "Transform",
5959
title: "🔄 Transformations",
6060
},
61+
{
62+
screen: "ZIndex",
63+
title: "🧱 zIndex",
64+
},
6165
{
6266
screen: "ColorFilter",
6367
title: "🌃 Color Filters",

‎apps/example/src/Examples/API/Routes.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type Routes = {
77
Path: undefined;
88
Clipping: undefined;
99
Transform: undefined;
10+
ZIndex: undefined;
1011
ColorFilter: undefined;
1112
ImageFilters: undefined;
1213
Gradients: undefined;

‎apps/example/src/Examples/API/index.tsx‎

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import React from "react";
1+
import React, { useEffect } from "react";
2+
import { BlurMask, Canvas, Circle, Group } from "@shopify/react-native-skia";
23
import { createNativeStackNavigator } from "@react-navigation/native-stack";
4+
import {
5+
Easing,
6+
useDerivedValue,
7+
useSharedValue,
8+
withRepeat,
9+
withTiming,
10+
} from "react-native-reanimated";
311

412
import { ImageLoading } from "../ImageLoading/ImageLoading";
513

@@ -38,6 +46,64 @@ import { StressTest4 } from "./StressTest4";
3846
import { FirstFrame } from "./FirstFrame";
3947

4048
const Stack = createNativeStackNavigator<Routes>();
49+
50+
const cycleDuration = 3600;
51+
52+
const computeLayerOrder = (value: number, id: number) => {
53+
"worklet";
54+
const stage = Math.floor(value) % 3;
55+
const relative = (id - stage + 3) % 3;
56+
return 2 - relative;
57+
};
58+
59+
export const ZIndexDemo = () => {
60+
const r = 80;
61+
const width = 256;
62+
const height = 256;
63+
const phase = useSharedValue(0);
64+
65+
useEffect(() => {
66+
phase.value = withRepeat(
67+
withTiming(3, {
68+
duration: cycleDuration,
69+
easing: Easing.linear,
70+
}),
71+
-1,
72+
false
73+
);
74+
}, [phase]);
75+
76+
const cyanZ = useDerivedValue(
77+
() => computeLayerOrder(phase.value, 0),
78+
[phase]
79+
);
80+
const magentaZ = useDerivedValue(
81+
() => computeLayerOrder(phase.value, 1),
82+
[phase]
83+
);
84+
const yellowZ = useDerivedValue(
85+
() => computeLayerOrder(phase.value, 2),
86+
[phase]
87+
);
88+
89+
return (
90+
<Canvas style={{ width, height }}>
91+
<Group>
92+
<BlurMask style="solid" blur={10} />
93+
<Circle cx={r} cy={r} r={r} color="cyan" zIndex={cyanZ} />
94+
<Circle cx={width - r} cy={r} r={r} color="magenta" zIndex={magentaZ} />
95+
<Circle
96+
cx={width / 2}
97+
cy={height - r}
98+
r={r}
99+
color="yellow"
100+
zIndex={yellowZ}
101+
/>
102+
</Group>
103+
</Canvas>
104+
);
105+
};
106+
41107
export const API = () => {
42108
return (
43109
<Stack.Navigator>
@@ -161,6 +227,13 @@ export const API = () => {
161227
title: "🔄 Transformations",
162228
}}
163229
/>
230+
<Stack.Screen
231+
name="ZIndex"
232+
component={ZIndexDemo}
233+
options={{
234+
title: "🧱 zIndex",
235+
}}
236+
/>
164237
<Stack.Screen
165238
name="SVG"
166239
component={SVG}

‎packages/skia/cpp/api/recorder/Command.h‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#pragma once
22

3+
#include <memory>
4+
#include <optional>
5+
#include <vector>
6+
37
namespace RNSkia {
48

59
enum CommandType {
@@ -56,4 +60,12 @@ class Command {
5660
virtual ~Command() = default;
5761
};
5862

63+
class GroupCommand : public Command {
64+
public:
65+
std::optional<float> zIndex;
66+
std::vector<std::unique_ptr<Command>> children;
67+
68+
GroupCommand() : Command(CommandType::Group) {}
69+
};
70+
5971
} // namespace RNSkia

‎packages/skia/cpp/api/recorder/JsiRecorder.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ class JsiRecorder : public JsiSkWrappingSharedPtrHostObject<Recorder> {
110110
}
111111

112112
JSI_HOST_FUNCTION(saveGroup) {
113-
getObject()->saveGroup();
113+
const jsi::Value *value = count > 0 ? &arguments[0] : nullptr;
114+
getObject()->saveGroup(runtime, value);
114115
return jsi::Value::undefined();
115116
}
116117

0 commit comments

Comments
 (0)