Skip to content

Commit 6bc0508

Browse files
authored
feat(🎨): Enable new reconciler by default on iOS and Android (#2865)
The old reconciler is still available via `CanvasOld` if needed
1 parent ec9c8c9 commit 6bc0508

File tree

13 files changed

+579
-228
lines changed

13 files changed

+579
-228
lines changed

Diff for: ‎apps/paper/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"react-native-reanimated": "^3.16.5",
3232
"react-native-safe-area-context": "^4.10.9",
3333
"react-native-screens": "^4.3.0",
34-
"react-native-svg": "^15.9.0",
34+
"react-native-svg": "^15.10.1",
3535
"react-native-wgpu": "0.1.19",
3636
"typescript": "^5.2.2"
3737
},
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React, { useCallback, useMemo, useState } from "react";
2+
import { Button, StyleSheet, useWindowDimensions, View } from "react-native";
3+
import {
4+
BlurMask,
5+
vec,
6+
Canvas,
7+
Circle,
8+
Fill,
9+
Group,
10+
polar2Canvas,
11+
mix,
12+
} from "@shopify/react-native-skia";
13+
import type { SharedValue } from "react-native-reanimated";
14+
import { useDerivedValue } from "react-native-reanimated";
15+
16+
import { useLoop } from "../../components/Animations";
17+
18+
const c1 = "#61bea2";
19+
const c2 = "#529ca0";
20+
21+
interface RingProps {
22+
index: number;
23+
progress: SharedValue<number>;
24+
total: number;
25+
}
26+
27+
const Ring = ({ index, progress, total }: RingProps) => {
28+
const { width, height } = useWindowDimensions();
29+
const R = width / 4;
30+
const center = useMemo(
31+
() => vec(width / 2, height / 2 - 64),
32+
[height, width]
33+
);
34+
35+
const theta = (index * (2 * Math.PI)) / total;
36+
const transform = useDerivedValue(() => {
37+
const { x, y } = polar2Canvas(
38+
{ theta, radius: progress.value * R },
39+
{ x: 0, y: 0 }
40+
);
41+
const scale = mix(progress.value, 0.3, 1);
42+
return [{ translateX: x }, { translateY: y }, { scale }];
43+
});
44+
45+
return (
46+
<Circle
47+
c={center}
48+
r={R}
49+
color={index % 2 ? c1 : c2}
50+
origin={center}
51+
transform={transform}
52+
/>
53+
);
54+
};
55+
56+
export const Breathe = () => {
57+
const [rings, setRings] = useState(12);
58+
const { width, height } = useWindowDimensions();
59+
const center = useMemo(
60+
() => vec(width / 2, height / 2 - 64),
61+
[height, width]
62+
);
63+
64+
const progress = useLoop({ duration: 3000 });
65+
66+
const transform = useDerivedValue(() => [
67+
{ rotate: mix(progress.value, -Math.PI, 0) },
68+
]);
69+
70+
const add = useCallback(() => {
71+
setRings((r) => r + 1);
72+
}, []);
73+
const remove = useCallback(() => {
74+
setRings((r) => Math.max(1, r - 1));
75+
}, []);
76+
return (
77+
<View style={{ flex: 1 }}>
78+
<View>
79+
<Button onPress={add} title="add" />
80+
<Button onPress={remove} title="remove" />
81+
</View>
82+
<Canvas style={styles.container} opaque>
83+
<Fill color="rgb(36,43,56)" />
84+
<Group origin={center} transform={transform} blendMode="screen">
85+
<BlurMask style="solid" blur={40} />
86+
{new Array(rings).fill(0).map((_, index) => {
87+
return (
88+
<Ring
89+
key={index}
90+
index={index}
91+
progress={progress}
92+
total={rings}
93+
/>
94+
);
95+
})}
96+
</Group>
97+
</Canvas>
98+
</View>
99+
);
100+
};
101+
102+
const styles = StyleSheet.create({
103+
container: {
104+
flex: 1,
105+
},
106+
});

Diff for: ‎packages/skia/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import "./skia/NativeSetup";
22
export { JsiSkImage } from "./skia/web/JsiSkImage";
33
export * from "./renderer";
4+
export * from "./renderer/CanvasOld";
45
export * from "./renderer/Canvas";
5-
export * from "./renderer/Canvas2";
66
export * from "./renderer/Offscreen";
77
export * from "./views";
88
export * from "./skia";

Diff for: ‎packages/skia/src/renderer/Canvas.tsx

+80-78
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
1-
import React, {
2-
useEffect,
1+
import {
2+
forwardRef,
33
useCallback,
4+
useEffect,
5+
useImperativeHandle,
46
useMemo,
5-
forwardRef,
67
useRef,
78
} from "react";
8-
import type {
9-
RefObject,
10-
ReactNode,
11-
MutableRefObject,
12-
ForwardedRef,
13-
FunctionComponent,
14-
} from "react";
15-
import type { LayoutChangeEvent } from "react-native";
9+
import type { LayoutChangeEvent, ViewProps } from "react-native";
10+
import type { SharedValue } from "react-native-reanimated";
1611

17-
import { SkiaDomView } from "../views";
12+
import { SkiaViewNativeId } from "../views/SkiaViewNativeId";
13+
import SkiaPictureViewNativeComponent from "../specs/SkiaPictureViewNativeComponent";
14+
import type { SkRect, SkSize } from "../skia/types";
15+
import { SkiaSGRoot } from "../sksg/Reconciler";
16+
import { Skia } from "../skia";
1817
import type { SkiaBaseViewProps } from "../views";
1918

20-
import { SkiaRoot } from "./Reconciler";
21-
22-
export const useCanvasRef = () => useRef<SkiaDomView>(null);
23-
24-
export interface CanvasProps extends SkiaBaseViewProps {
25-
ref?: RefObject<SkiaDomView>;
26-
children: ReactNode;
27-
mode?: "default" | "continuous";
28-
}
19+
const NativeSkiaPictureView = SkiaPictureViewNativeComponent;
2920

21+
// TODO: no need to go through the JS thread for this
3022
const useOnSizeEvent = (
3123
resultValue: SkiaBaseViewProps["onSize"],
3224
onLayout?: (event: LayoutChangeEvent) => void
@@ -46,81 +38,91 @@ const useOnSizeEvent = (
4638
);
4739
};
4840

49-
export const Canvas = forwardRef<SkiaDomView, CanvasProps>(
41+
export interface CanvasProps extends ViewProps {
42+
debug?: boolean;
43+
opaque?: boolean;
44+
onSize?: SharedValue<SkSize>;
45+
mode?: "continuous" | "default";
46+
}
47+
48+
export const Canvas = forwardRef(
5049
(
5150
{
52-
children,
53-
style,
51+
mode,
5452
debug,
55-
mode = "default",
56-
onSize: _onSize,
53+
opaque,
54+
children,
55+
onSize,
5756
onLayout: _onLayout,
58-
...props
59-
},
60-
forwardedRef
57+
...viewProps
58+
}: CanvasProps,
59+
ref
6160
) => {
62-
const onLayout = useOnSizeEvent(_onSize, _onLayout);
63-
const innerRef = useCanvasRef();
64-
const ref = useCombinedRefs(forwardedRef, innerRef);
65-
const redraw = useCallback(() => {
66-
innerRef.current?.redraw();
67-
}, [innerRef]);
68-
const getNativeId = useCallback(() => {
69-
const id = innerRef.current?.nativeId ?? -1;
70-
return id;
71-
}, [innerRef]);
61+
const rafId = useRef<number | null>(null);
62+
const onLayout = useOnSizeEvent(onSize, _onLayout);
63+
// Native ID
64+
const nativeId = useMemo(() => {
65+
return SkiaViewNativeId.current++;
66+
}, []);
7267

73-
const root = useMemo(
74-
() => new SkiaRoot(redraw, getNativeId),
75-
[redraw, getNativeId]
76-
);
68+
// Root
69+
const root = useMemo(() => new SkiaSGRoot(Skia, nativeId), [nativeId]);
7770

78-
// Render effect
71+
// Render effects
7972
useEffect(() => {
8073
root.render(children);
81-
}, [children, root, redraw]);
74+
}, [children, root]);
8275

8376
useEffect(() => {
8477
return () => {
8578
root.unmount();
8679
};
8780
}, [root]);
8881

82+
const requestRedraw = useCallback(() => {
83+
rafId.current = requestAnimationFrame(() => {
84+
root.render(children);
85+
if (mode === "continuous") {
86+
requestRedraw();
87+
}
88+
});
89+
}, [children, mode, root]);
90+
91+
useEffect(() => {
92+
if (mode === "continuous") {
93+
console.warn("The `mode` property in `Canvas` is deprecated.");
94+
requestRedraw();
95+
}
96+
return () => {
97+
if (rafId.current !== null) {
98+
cancelAnimationFrame(rafId.current);
99+
}
100+
};
101+
}, [mode, requestRedraw]);
102+
// Component methods
103+
useImperativeHandle(ref, () => ({
104+
makeImageSnapshot: (rect?: SkRect) => {
105+
return SkiaViewApi.makeImageSnapshot(nativeId, rect);
106+
},
107+
makeImageSnapshotAsync: (rect?: SkRect) => {
108+
return SkiaViewApi.makeImageSnapshotAsync(nativeId, rect);
109+
},
110+
redraw: () => {
111+
SkiaViewApi.requestRedraw(nativeId);
112+
},
113+
getNativeId: () => {
114+
return nativeId;
115+
},
116+
}));
89117
return (
90-
<SkiaDomView
91-
ref={ref}
92-
style={style}
93-
root={root.dom}
94-
onLayout={onLayout}
118+
<NativeSkiaPictureView
119+
collapsable={false}
120+
nativeID={`${nativeId}`}
95121
debug={debug}
96-
mode={mode}
97-
{...props}
122+
opaque={opaque}
123+
onLayout={onLayout}
124+
{...viewProps}
98125
/>
99126
);
100127
}
101-
) as FunctionComponent<CanvasProps & React.RefAttributes<SkiaDomView>>;
102-
103-
/**
104-
* Combines a list of refs into a single ref. This can be used to provide
105-
* both a forwarded ref and an internal ref keeping the same functionality
106-
* on both of the refs.
107-
* @param refs Array of refs to combine
108-
* @returns A single ref that can be used in a ref prop.
109-
*/
110-
const useCombinedRefs = <T,>(
111-
...refs: Array<MutableRefObject<T> | ForwardedRef<T>>
112-
) => {
113-
const targetRef = React.useRef<T>(null);
114-
React.useEffect(() => {
115-
refs.forEach((ref) => {
116-
if (ref) {
117-
if (typeof ref === "function") {
118-
ref(targetRef.current);
119-
} else {
120-
ref.current = targetRef.current;
121-
}
122-
}
123-
});
124-
}, [refs]);
125-
return targetRef;
126-
};
128+
);

0 commit comments

Comments
 (0)