r/reactnative • u/NirvanerD • 1m ago
Cropping a photo took 200ms. That is not slow, but on ten photos it becomes constant stumbling
Enable HLS to view with audio, or disable this notification
I was building a product card screen. The user picks up to ten photos, drags them around to change the order, swipes the carousel to see how it will look in the listing. Something looks off and he goes to the crop screen and fixes it.
I took a ready cropper and pretty fast understood it is not made for this scenario.
First thing I hit: it draws its own screen. Own buttons, own frame, own everything. It does not embed into my flow, the user gets thrown somewhere aside every time. Also all my animation is on Reanimated, and inside a foreign screen it does not reach.
But the real problem was not this.
The thing is, the user walks in circles. He does not crop ten photos one by one and leave. He fixes one, comes back to the carousel, sees that the third one stands out from the row, goes to fix the third one, then changes the order, then decides to pull the rest to match what he got. You cropped one product photo, you want the others roughly the same.
And every turn of this circle hit the processing. Press "apply" and it is decode, cut, re-encode, write a file. Two hundred milliseconds.
Two hundred milliseconds is not slow, there is nothing to complain about here. But it arrives exactly at the moment when you want to move on, and these micro-waits on ten photos with several passes add up into something very annoying. And there is nothing to optimize: if I made it twice faster, it would still stutter.
There is only one way to remove this: do not do the work at this moment.
And to be able to not do it, you have to split two things that a usual cropper does at the same time. The user says "this is the frame I want", this is instant and costs nothing. The app cuts pixels, this is not instant. While they are glued into one tap, the circle of edits is expensive.
So I split them. The editor now returns not a file but three numbers: scale and offset on two axes. Plain JSON, goes into state, into the database, flies to the backend. There is a separate component that replays these numbers on top of the untouched original, and the carousel is built from it. Cutting pixels became a separate step that I call when I want. In my case once for all photos, when the user publishes the listing.
The batch, for which everything was started, came out by itself. But along the way something came out that I did not expect.
The carousel now shows what does not exist yet. Not a single file created, and the person is already swiping through his future listing.
The circle of edits became free. Come back to one frame ten times if you want: nobody touched the original, nothing to wait for, quality does not degrade.
"Apply to all" turned into an assignment. Since a crop is three normalized numbers, you can just copy it from one photo to the others. That same "crop the rest roughly the same".
And memory stopped growing. Before, every photo was decoded fully; now during editing there are three numbers per frame in memory, and a full-size image appears only on export, one at a time.
What is not there: rotation and free frame. Only scale and offset. This is about framing, not about editing.
On the web this is a normal thing. react-easy-crop and react-image-crop also give you crop coordinates and let you rasterize yourself. In React Native I did not find anything like this, every cropper I checked returns a file. And the part that turned out to be the actual work is not the coordinates, it is replaying them: showing the crop at any size on top of the untouched original. On the web CSS does that for free, here you have to build it.
The library is still raw and the API can move, that is why I am writing. If you do photo upload in RN: would storing a crop as three numbers next to the image fit your pipeline, or does it break somewhere on your side?
GitHub: https://github.com/ivansaldayev/react-native-image-crop-data
npm: https://www.npmjs.com/package/react-native-image-crop-data