Skia on React Native: First look
When Google released Flutter couple of years ago, their motto was simple. They said;
You have the control of every pixel on screen
which basically translates into “From now on, you can do anything anywhere in your app including those adorable liquid-like menus or path animation that you could only do in After Effects”. But how they do that?
This was actually possible thanks to Skia Library and Rendering Engine that we will cover in this article. For those who don’t know, Skia is a GPU accelerated rendering system that includes many graphical APIs, including path drawing, image filters, custom shaders and a whole set of SVG tools. So guys on Flutter came up with an idea that making a Skia canvas as a whole viewport area of the app and any GUI element inside would be redraw and look like native. Since they are all in Skia canvas, if you would do something fancy, say like “melted” button, you could do it too. This obviously created a wow effect quickly inside the community and since then people created interesting and vibrant designs for their app.
So we know what Skia is, let’s get back to our topic;
If you’re a React Native programmer that love to watch tutorials, reading all articles, do all the stuff to improve yourself; chances are you ran into William Candillon, a brilliant programmer dedicated himself to React Native animations and interactive UIs. In his YouTube channel he has tons of challenging tutorials that make you master on React Native animations or animations in general. When I was watching his videos, I also learnt too much about math concepts and techniques — say trigs for example and how to apply them on common UI interactions. If you’re interested React Native animations in particular, you definitely want to visit his channel at: https://www.youtube.com/c/wcandillon
Well, around last year William and his friend Christian Falch decided to “port” the Skia Library to React Native and they built a wonderful package called react-native-skia
This library brings almost all APIs of Skia Library along with animation techniques that you may be familiar from react-native-reanimated. If you have ever used react-three-fiber for Three JS animation, same concept applies here, every Skia API is performed in a separate context which uses own hooks and values. Let’s start off with an example and later I’ll explain what I mean;
This code draws 3 circles with multiply blend mode applied on overlapping parts. So we first define a Canvas for our paint, which basically create a context for our objects. Then we define a Group which allows us to apply transformations, clipping and other operations to its children. Group is essential tag in RN Skia, since the shapes and other objects have no individual transformation properties. So if you’d like to move a Rectangle for example, you have to first wrap it up with Group.
A quick interruption:
React Native Skia has also Imperative API which means instead of declaring tags you simply utilize the library with a set of commands. I don’t cover imperative way in this article as it’s a bit against declarative nature of React. If you find yourself struggling to apply your logic in tags you can switch to Imperative API per component basis. You can find an example here: https://shopify.github.io/react-native-skia/docs/getting-started/hello-world
Shapes and Paths
Moving on with shapes, React Native Skia has primitive shapes like rectangles, circles, polygons and lines as well as paths defined with SVG notation you’re familiar with. The shapes attributes are almost identical to SVG equivalents like Circle has cx, cy or Rect has x, y and so on. The only thing is, there are some elements for specific cases, like <Box> element for optimized inner and outer shadows or <Patch> element to draw a Coons Patch (I don’t know what’s the use-case of this one).
The shapes accept a <Paint> as their child to modify painting properties such as stroke or fill. Take a look at the example below:
Here we have a Paint tag inside Circle to fill or stroke the circle we’ve drawn. At first this notation may seem you weird, but actually it’s much more convenient to determine relationships with different APIs. The same structure applies to Group as well. In this example Group has an opacity prop which affecting everything inside of it. This nested structure with non-visual elements reminded me Flutter widgets like Spacing, Center etc. If you’re coming from Flutter this will seem very familiar to you.
Effects and Filters
Now the fun part begins: The cool filters! One of the biggest reason you would want to use Skia is absolutely the vast image effects and filters that you can’t normally find in bare React Native framework. Being a rendering engine as well, Skia canvas has a shader model which allows you modify the pixels on the screen whatever the way you want. Remember the beginning of the article?
So you want a real shadow with cool softness in Android instead of dumb elevation prop? No problem! A great menu with background image blurred underneath? Can do that. Pretty much every filter is included as well as the complex ones such as Color Filter or Displacement Map. Additionally there are some generators like Fractal Noise or Turbulent Noise that you can combine with Displacement Map and make your filter much cooler. If none of these are enough you can always write your own shader and make something unique for your taste. The shader language is very similar to GLSL which is used in WebGL and can be playgrounded here: https://shaders.skia.org/
OK so let’s make a quick demo:
Here we picked up our image and defined a Displacement Map using a Turbulence to generate some sort of Warp Effect. The result looks like this:
In this example, first thing you notice is the useImage hook. React Native Skia has own elements even they are completely similar to base RN Components such as Image. The <Image> tag of Skia takes an Image in form of SkImage type which can be achieved by providing image path to useImage hook. That’s because Skia is performing in UI thread separating from main thread of React Native itself. You’ll see this difference much more when you start messing with animations. After loading our image we can set any filter as a child component of Image tag. You can here combine any filter, play with different values and achieve different results. Of course not to mention, those values are also animatable which we will cover in another article :)
Masks
For this article, I lastly want to mention about masking features. The masking elements are not some sort of uniform shapes, it’s rather a container that accept any shape or drawing. If you used react-native-mask before, it’s just the same style that goes as follows:
The <Mask> element takes a mask node that could be anything as long as it produces a luminance or alpha value which the element operates on. Here we provide a circle that masking an image which would look like an avatar below:
You can of course draw shapes, polygons or even use turbulence filters and make the image looks like cloudy.
Conclusion
Well, those were the main features of React Native Skia that you can enjoy. Obviously there is a huge animation topic I haven’t mentioned here because I’m saving it for another article. There are also couple of trade-offs I’d like point out;
- The library is relatively new and it’s in between alpha and beta stage. That been said, expect some bugs and performance issues.
- In terms of performance, avoid to use it as a FlatList item or something as repetitively generated.
- Custom shaders sometimes doesn’t behave correctly
- When Group element has many children, performance issues rise again. Try to keep it low.
Despite those and other problems, the library is marvelous and does a wonderful job. The community is also very active and responsive, I’m sure they will fix the bugs quickly on the upcoming versions. I highly suggest to check it out the library now and feel free to express your creativity. Until then;
Happy coding!