diff --git a/example/src/Examples/API/List.tsx b/example/src/Examples/API/List.tsx
index 7e9f64bb86..a99ebf8b69 100644
--- a/example/src/Examples/API/List.tsx
+++ b/example/src/Examples/API/List.tsx
@@ -14,6 +14,10 @@ const examples = [
screen: "Images",
title: "🏞 Images",
},
+ {
+ screen: "Skottie",
+ title: "▶️ Skottie",
+ },
{
screen: "Clipping",
title: "✂️ & 🎭 Clipping & Masking",
diff --git a/example/src/Examples/API/Routes.ts b/example/src/Examples/API/Routes.ts
index 3cd2dc2465..8388865436 100644
--- a/example/src/Examples/API/Routes.ts
+++ b/example/src/Examples/API/Routes.ts
@@ -17,4 +17,5 @@ export type Routes = {
Freeze: undefined;
UseCanvas: undefined;
Reanimated: undefined;
+ Skottie: undefined;
};
diff --git a/example/src/Examples/API/SkottieAnimations.tsx b/example/src/Examples/API/SkottieAnimations.tsx
new file mode 100644
index 0000000000..c72a706203
--- /dev/null
+++ b/example/src/Examples/API/SkottieAnimations.tsx
@@ -0,0 +1,47 @@
+import React, { useMemo } from "react";
+import { ScrollView, useWindowDimensions } from "react-native";
+import {
+ Canvas,
+ Skia,
+ SkottieAnimation,
+ useTiming,
+ Easing,
+} from "@shopify/react-native-skia";
+
+import _LottieAnim from "../../assets/material_wave_loading.json";
+
+const LottieAnim = JSON.stringify(_LottieAnim);
+
+export const SkottieAnimations = () => {
+ const { width, height } = useWindowDimensions();
+
+ // TODO: build a hook that abstracts this logic
+ const skottieAnimation = useMemo(() => Skia.SkottieAnimation(LottieAnim), []);
+
+ const progress = useTiming(
+ {
+ from: 0,
+ to: 1,
+ loop: true,
+ },
+ {
+ duration: skottieAnimation.duration * 1000,
+ easing: Easing.linear,
+ }
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/example/src/Examples/API/index.tsx b/example/src/Examples/API/index.tsx
index 5f99400669..f9f5d8c566 100644
--- a/example/src/Examples/API/index.tsx
+++ b/example/src/Examples/API/index.tsx
@@ -20,6 +20,7 @@ import { UseCanvas } from "./UseCanvas";
import { FreezeExample } from "./Freeze";
import { Touch } from "./Touch";
import { Reanimated } from "./Reanimated";
+import { SkottieAnimations } from "./SkottieAnimations";
const Stack = createNativeStackNavigator();
export const API = () => {
@@ -40,6 +41,13 @@ export const API = () => {
title: "🔺 Shapes",
}}
/>
+
(context));
diff --git a/package/cpp/api/JsiSkCanvas.h b/package/cpp/api/JsiSkCanvas.h
index 10c203bebe..5650411e50 100644
--- a/package/cpp/api/JsiSkCanvas.h
+++ b/package/cpp/api/JsiSkCanvas.h
@@ -534,6 +534,17 @@ class JsiSkCanvas : public JsiSkHostObject {
void setCanvas(SkCanvas *canvas) { _canvas = canvas; }
SkCanvas *getCanvas() { return _canvas; }
+ /**
+ Returns the underlying object from a host object of this type
+ */
+ static SkCanvas *fromValue(jsi::Runtime &runtime,
+ const jsi::Value &obj)
+ {
+ return obj.asObject(runtime)
+ .asHostObject(runtime)
+ ->getCanvas();
+ }
+
private:
SkCanvas *_canvas;
};
diff --git a/package/cpp/api/JsiSkSkottieAnimation.h b/package/cpp/api/JsiSkSkottieAnimation.h
new file mode 100644
index 0000000000..c2e65e5811
--- /dev/null
+++ b/package/cpp/api/JsiSkSkottieAnimation.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdocumentation"
+
+#include
+
+#pragma clang diagnostic pop
+
+#include
+
+// TODO: we probably also want to turn that into a factory.
+namespace RNSkia {
+ using namespace facebook;
+
+ class JsiSkSkottieAnimation : public JsiSkWrappingSkPtrHostObject
+ {
+ public:
+ //#region Properties
+ JSI_PROPERTY_GET(duration) { return static_cast(getObject()->duration()); }
+ JSI_PROPERTY_GET(fps) { return static_cast(getObject()->fps()); }
+
+ JSI_EXPORT_PROPERTY_GETTERS(JSI_EXPORT_PROP_GET(JsiSkSkottieAnimation, duration),
+ JSI_EXPORT_PROP_GET(JsiSkSkottieAnimation, fps))
+ //#endregion
+
+
+ //#region Methods
+ JSI_HOST_FUNCTION(seek) {
+ getObject()->seek(arguments[0].asNumber());
+ return jsi::Value::undefined();
+ }
+
+ JSI_HOST_FUNCTION(render) {
+ auto canvas = JsiSkCanvas::fromValue(runtime, arguments[0]);
+ auto rect = JsiSkRect::fromValue(runtime, arguments[1]);
+ if (canvas != nullptr && rect != nullptr)
+ getObject()->render(canvas, rect.get());
+
+ return jsi::Value::undefined();
+ }
+
+ JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkSkottieAnimation, seek),
+ JSI_EXPORT_FUNC(JsiSkSkottieAnimation, render), )
+ //#endregion
+
+ /**
+ Constructor
+ */
+ JsiSkSkottieAnimation(std::shared_ptr context,
+ const sk_sp animation)
+ : JsiSkWrappingSkPtrHostObject(std::move(context), std::move(animation)){}
+
+ /**
+ Returns the jsi object from a host object of this type
+ */
+ static sk_sp fromValue(jsi::Runtime &runtime, const jsi::Value &obj) {
+ return obj.asObject(runtime)
+ .asHostObject(runtime)
+ ->getObject();
+ }
+
+ /**
+ * Creates the function for contructing a new instance of the
+ * JsiSkSkottieAnimation class.
+ *
+ * @param context platform context
+ * @return A function for creating a new host object wrapper for the JsiSkSkottieAnimation class.
+ */
+ static const jsi::HostFunctionType
+ createCtor(std::shared_ptr context) {
+ return JSI_HOST_FUNCTION_LAMBDA {
+ auto jsonStr = arguments[0].asString(runtime).utf8(runtime);
+ auto animation = skottie::Animation::Builder()
+ .make(jsonStr.c_str(), jsonStr.size());
+
+ // Return the newly constructed object
+ return jsi::Object::createFromHostObject(
+ runtime, std::make_shared(std::move(context), std::move(animation)));
+ };
+ }
+ };
+}
\ No newline at end of file
diff --git a/package/package.json b/package/package.json
index 6d0cfe829d..a7381401f6 100644
--- a/package/package.json
+++ b/package/package.json
@@ -30,6 +30,8 @@
"libs/ios/libskia.xcframework",
"libs/ios/libskshaper.xcframework",
"libs/ios/libsvg.xcframework",
+ "libs/ios/libskottie.xcframework",
+ "libs/ios/libsksg.xcframework",
"react-native-skia.podspec",
"scripts/install-npm.js",
"scripts/setup-canvaskit.js",
diff --git a/package/react-native-skia.podspec b/package/react-native-skia.podspec
index 2e4868fe79..e1ccd84a23 100644
--- a/package/react-native-skia.podspec
+++ b/package/react-native-skia.podspec
@@ -31,7 +31,9 @@ Pod::Spec.new do |s|
s.ios.vendored_frameworks = [
'libs/ios/libskia.xcframework',
'libs/ios/libsvg.xcframework',
- 'libs/ios/libskshaper.xcframework'
+ 'libs/ios/libskshaper.xcframework',
+ 'libs/ios/libskottie.xcframework',
+ 'libs/ios/libsksg.xcframework'
]
# All iOS cpp/h files
diff --git a/package/src/renderer/components/index.ts b/package/src/renderer/components/index.ts
index 857481e3a9..8a059db355 100644
--- a/package/src/renderer/components/index.ts
+++ b/package/src/renderer/components/index.ts
@@ -9,6 +9,7 @@ export * from "./imageFilters";
export * from "./pathEffects";
export * from "../processors";
export * from "./Picture";
+export * from "./skottie";
export * from "./Group";
export * from "./Mask";
diff --git a/package/src/renderer/components/skottie/SkottieAnimation.tsx b/package/src/renderer/components/skottie/SkottieAnimation.tsx
new file mode 100644
index 0000000000..49aa9e3920
--- /dev/null
+++ b/package/src/renderer/components/skottie/SkottieAnimation.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+import type { AnimatedProps } from "../../processors";
+import type { RectProps } from "../shapes";
+import { processRect } from "../../processors";
+import { createDrawing } from "../../nodes/Drawing";
+import type { SkSkottieAnimation } from "../../../skia/types/SkottieAnimation";
+
+export type SkottieProps = RectProps & {
+ anim: SkSkottieAnimation;
+ // value from 0 - 1 (or negative as well?)
+ progress: number;
+};
+
+const onDraw = createDrawing(
+ ({ canvas, Skia }, { anim, progress, ...rectProps }) => {
+ const rect = processRect(Skia, rectProps);
+ anim.seek(progress);
+ anim.render(canvas, rect);
+ }
+);
+export const SkottieAnimation = (props: AnimatedProps) => {
+ return ;
+};
diff --git a/package/src/renderer/components/skottie/index.ts b/package/src/renderer/components/skottie/index.ts
new file mode 100644
index 0000000000..8981245ea9
--- /dev/null
+++ b/package/src/renderer/components/skottie/index.ts
@@ -0,0 +1 @@
+export * from "./SkottieAnimation";
diff --git a/package/src/skia/types/Skia.ts b/package/src/skia/types/Skia.ts
index 8d13c03146..9c5c1b4fb1 100644
--- a/package/src/skia/types/Skia.ts
+++ b/package/src/skia/types/Skia.ts
@@ -27,6 +27,7 @@ import type { SkPath } from "./Path/Path";
import type { SkContourMeasureIter } from "./ContourMeasure";
import type { PictureFactory, SkPictureRecorder } from "./Picture";
import type { Color, SkColor } from "./Color";
+import type { SkSkottieAnimation } from "./SkottieAnimation";
/**
* Declares the interface for the native Skia API
@@ -45,6 +46,7 @@ export interface Skia {
) => SkContourMeasureIter;
Paint: () => SkPaint;
PictureRecorder: () => SkPictureRecorder;
+ SkottieAnimation: (jsonString: string) => SkSkottieAnimation;
Picture: PictureFactory;
Path: PathFactory;
Matrix: (matrix?: readonly number[]) => SkMatrix;
diff --git a/package/src/skia/types/SkottieAnimation.ts b/package/src/skia/types/SkottieAnimation.ts
new file mode 100644
index 0000000000..cca7d93284
--- /dev/null
+++ b/package/src/skia/types/SkottieAnimation.ts
@@ -0,0 +1,9 @@
+import type { SkCanvas } from "./Canvas";
+import type { SkRect } from "./Rect";
+
+export interface SkSkottieAnimation {
+ readonly duration: number;
+ readonly fps: number;
+ readonly seek: (time: number) => void;
+ readonly render: (canvas: SkCanvas, rect: SkRect) => void;
+}
diff --git a/package/src/skia/web/JsiSkia.ts b/package/src/skia/web/JsiSkia.ts
index decd319872..697555b38c 100644
--- a/package/src/skia/web/JsiSkia.ts
+++ b/package/src/skia/web/JsiSkia.ts
@@ -97,4 +97,7 @@ export const JsiSkApi = (CanvasKit: CanvasKit): Skia => ({
return new JsiSkRect(CanvasKit, CanvasKit.XYWHRect(x, y, width, height));
},
Surface: new JsiSkSurfaceFactory(CanvasKit),
+ SkottieAnimation: (_jsonString) => {
+ throw new Error("Not implemented yet on React Native Web");
+ },
});
diff --git a/scripts/build-npm-package.ts b/scripts/build-npm-package.ts
index 11dcb7b158..dc092c8c80 100644
--- a/scripts/build-npm-package.ts
+++ b/scripts/build-npm-package.ts
@@ -41,7 +41,7 @@ if (process.env.GITHUB_RUN_NUMBER === undefined) {
// Check that Android Skia libs are built
["armeabi-v7a", "arm64-v8a", "x86", "x86_64"].forEach((cpu) => {
- ["libskia.a", "libskshaper.a", "libsvg.a"].forEach((target) => {
+ ["libskia.a", "libskshaper.a", "libsvg.a", "libskottie.a", "libsksg.a"].forEach((target) => {
const path = `./package/libs/android/${cpu}/${target}`;
checkFileExists(
path,
@@ -56,6 +56,8 @@ if (process.env.GITHUB_RUN_NUMBER === undefined) {
"libskia.xcframework",
"libskshaper.xcframework",
"libsvg.xcframework",
+ "libskottie.xcframework",
+ "libsksg.xcframework"
].forEach((lib) => {
checkFileExists(
`./package/libs/ios/${lib}`,
diff --git a/scripts/skia-configuration.ts b/scripts/skia-configuration.ts
index 7e8e7c8600..edfa215faf 100644
--- a/scripts/skia-configuration.ts
+++ b/scripts/skia-configuration.ts
@@ -13,7 +13,7 @@ export const commonArgs = [
["skia_use_system_zlib", false],
["skia_enable_tools", false],
["is_official_build", true],
- ["skia_enable_skottie", false],
+ ["skia_enable_skottie", true],
["is_debug", false],
["skia_enable_pdf", false],
["skia_enable_flutter_defines", true],
@@ -84,7 +84,7 @@ export const configurations: Configuration = {
],
],
outputRoot: "package/libs/android",
- outputNames: ["libskia.a", "libskshaper.a", "libsvg.a"],
+ outputNames: ["libskia.a", "libskshaper.a", "libsvg.a", "libskottie.a", "libsksg.a"],
},
ios: {
targets: {
@@ -143,6 +143,6 @@ export const configurations: Configuration = {
["cxx", '"clang++"'],
],
outputRoot: "package/libs/ios",
- outputNames: ["libskia.a", "libskshaper.a", "libsvg.a"],
+ outputNames: ["libskia.a", "libskshaper.a", "libsvg.a", "libskottie.a", "libsksg.a"],
},
};
diff --git a/scripts/workflow-copy-libs.ts b/scripts/workflow-copy-libs.ts
index 6ee857deda..59ac289ddc 100644
--- a/scripts/workflow-copy-libs.ts
+++ b/scripts/workflow-copy-libs.ts
@@ -18,11 +18,13 @@ const sources = [
const destinations = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"];
-const androidFiles = ["libskia.a", "libskshaper.a", "libsvg.a"];
+const androidFiles = ["libskia.a", "libskshaper.a", "libsvg.a", "libskottie.a", "libsksg.a"];
const iosFiles = [
"libskia.xcframework",
"libskshaper.xcframework",
"libsvg.xcframework",
+ "libskottie.xcframework",
+ "libsksg.xcframework"
];
const copyFiles = (from: string, to: string, files: string[]) => {