Skip to content

CropZoom

An ideal, practical and unopinionated component for image and video cropping needs, among its features we can find the following:

  • Custom UI: Build your own custom UI on top of it.
  • Resumable: Resumable and accurate pinch to zoom features; pan, pinch and even pan with the pinch gesture! It will resume where you left.
  • Barebones: For complex use cases, use it as an overlay view and mirror its transformation values to some other components, for instance React Native Skia's components.
  • Fixed size: Enforce all resulting crops to be of a fixed size, ideal for profile pictures.

This component comes with a handy algorithm to perform cropping operations for you, however you will need the help a of deidicated library for this task.

The next video footage is taken from the Example app.

How to use

It's usage is quite similar to the one of ResumableZoom component, reading CropZoom and Expo Image Manipulator guide is highly advised for a better understanding of this component's usage, however here is a pseudo-example of its usage:

Remember

  • This component uses flex: 1 style property therefore it will attempt to take all available space, its minimum dimensions are the values provided to cropSize property.
  • Child component must use the following styles {width: 100%, height: '100%'}.
tsx
import { Image, View, StyleSheet } from 'react-native';
import { CropZoom, useImageResolution, type CropZoomType } from 'react-native-zoom-toolkit`;

const imageUrl = 'url to some image';
const cropSize = { width: 200, height: 200 };

const App = () => {
  // A reference so you can access all methods
  const ref = useRef<CropZoomType>(null);

  // Utility hook to get the resolution of a network image
  const { resolution } = useImageResolution({uri: imageUrl });

  // A function that renders an svg with a hole in it.
  const renderOverlay = () => <SomeComponent />

  if(resolution === undefined) {
    return null;
  }

  return (
    <CropZoom
      ref={ref}
      cropSize={cropSize}
      resolution={resolution}
      OverlayComponent={renderOverlay}
    >
      <Image
        source={{uri: iamgeUrl }}
        style={{ width: '100%', height: '100%' }}
      />
    </CropZoom>
  );
}

export default App;

Properties

cropSize

TypeRequired
{ width: number; height: number; }Yes

Size of the cropping area.

resolution

TypeRequired
{ width: number; height: number; }Yes

Resolution of your image, video or how big whatever you are trying to crop really is.

OverlayComponent

TypeDefaultRequired
() => React.ReactElementundefinedNo

A function that returns a component, such component will be located on top of the component to crop and below the gesture detection area, for instance you can pass an svg component with a hole in it.

minScale

TypeDefaultRequired
number1No

Minimum scale value allowed by the pinch gesture, expects values greater than or equals one.

maxScale

TypeDefaultRequired
numberundefinedNo

Maximum scale value allowed by the pinch gesture, if the value is undefined maxScale value will be infered based on cropSize and resolution properties in a such way maxScale is a value just before your content starts to pixelate.

panMode

TypeDefaultRequired
'clamp' | 'free' | 'friction''clamp'No

Determine how your component must behave when it's panned beyond the specified boundaries by the container enclosing it, possible values are:

  • clamp prevents the user from dragging the component out of its enclosing container boundaries.
  • free lets the user drag the component around freely, if the pan gesture ends in an out of bounds position it will animate back to a position with the boundaries of its enclosing container.
  • friction is the same as freemode, however it just adds some amount of friction as you pan.

scaleMode

TypeDefaultRequired
'clamp' | 'bounce''bounce'No

Determine how your component must behave when the pinch gesture's scale value exceeds boundaries defined by minScale and maxScale properties, possible values are:

  • clamp keeps the scale whithin the already mentioned scale boundaries.
  • bounce lets the user scale above and below the scale boundary values, at the end of the pinch gesture the scale value animates back to minScale or maxScale respectively.

allowPinchPanning

TypeDefaultRequired
booleantrueNo

Lets the user pan the component around as they pinch as well as providing a more accurate pinch gesture calculation to user interaction. Panning as you pinch will not trigger any pan gesture related callbacks.

onTap

TypeDefaultAdditional Info
(e: TapGestureEvent) => voidundefinedsee tap gesture event data

Callback triggered when the user taps the wrapped component once.

onPanStart

TypeDefaultAdditional Info
(e: PanGestureEvent) => voidundefinedsee pan gesture event data

Callback triggered when the pan gesture starts.

onPanEnd

TypeDefaultAdditional Info
(e: PanGestureEvent) => voidundefinedsee pan gesture event data

Callback triggered as soon as the user lifts their finger off the screen.

onPinchStart

TypeDefaultAdditional Info
(e: PinchGestureEvent) => voidundefinedsee pinch gesture event data

Callback triggered when the pinch gesture starts.

onPinchEnd

TypeDefaultAdditional Info
(e: PinchGestureEvent) => voidundefinedsee pinch gesture event data

Callback triggered as soon as the user lifts their fingers off the screen after pinching.

onUpdate

TypeDefaultRequiredAdditional Info
(state: CropZoomState) => voidundefinedNosee worklets

Worklet callback triggered when the transformation state of the component changes, the internal state is updated as the user makes use of the gestures or execute its methods, ideal if you need to mirror its current transformation values to some other component as it updates, see CropZoomState.

onGestureEnd

TypeDefaultRequired
() => voidundefinedNo

Callback triggered when a pan, pinch or double tap gesture ends, if an animation started at the end of one of the gestures the execution of this callback will be delayed until such animation finishes.

Methods

All methods are accessible through a ref object.

tsx
import { useRef } from 'react';
import { CropZoom, type CropZoomType } from 'react-native-zoom-toolkit';

const ref = useRef<CropZoomType>(null);
ref.current?.crop(200);

<CropZoom ref={ref} />;

crop

Map all the transformations applied to your component into an object describing the context necessary to perform a crop operation, such object must be used along with a library capable of cropping images and/or videos, for instance Expo Image Manipulator, see CropZoom and Expo Image Manipulator.

  • type definition: (fixedWidth?: number) => CropContextResult
  • parameter information
NameTypeDefaultDescription
fixedWidthnumber | undefinedundefinedEnforce all resulting crops to be of a fixed width, height is inferred by the computed aspect ratio of CropZoom's cropSize property.

Beware

Calling crop method with the fixedWidth argument will subject your crops to one pixel margin of error, this behavior is intentional in order to prevent image cropping libraries from crashing your app.

rotate

Rotate the component 90 degrees clockwise or counterclockwise in a range from 0 to 360 degrees (or 0 to -360 degrees counterclockwise).

  • type definition: (animate?: boolean, clockwise?: boolean, cb?: (angle: number) => void) => void
  • paramter information
NameTypeDefaultDescription
animateboolean | undefinedtrueWhether to animate the transition or not.
clockwiseboolean | undefinedtrueWhether to rotate clockwise (90 degrees) or counterclockwise (-90 degrees)
cbfunction | undefinedundefinedCallback triggered at the beginning of the transition, as its only argument receives the angle your component will transition to, this angle ranges from 0 to 360 degrees (or -360 degrees if counterclockwise) (at 360 or -360 degrees it's clamped to 0).

flipHorizontal

Flip the component horizontally.

  • type definition: (animate?: boolean, cb?: (angle: number) => void) => void
  • parameter information
NameTypeDefaultDescription
animateboolean | undefinedtrueWhether to animate the transition or not.
cbfunction | undefinedundefinedCallback to trigger at the beginning of the transition, as its only argument receives the angle your component will transition to, values are 0 or 180.

flipVertical

Flip the component vertically.

  • type definition: (animate?: boolean, cb?: (angle: number) => void) => void
  • parameter information
NameTypeDefaultDescription
animateboolean | undefinedtrueWhether to animate the transition or not.
cbfunction | undefinedundefinedCallback to trigger at the beginning of the transition, as its only argument receives the angle your component will transition to, values are 0 or 180.

reset

Reset all transformations to their initial state.

  • type definition: (animate:? boolean) => void
  • parameter information:
NameTypeDefaultDescription
animateboolean | undefinedtrueWhether to animate the transition or not.

requestState

Request internal transformation values of this component at the moment of the call.

assignState

Assign the internal transformation values for this component, if the state provided is considered to be not achievable by the component's boundaries, it'll be approximated to the closest valid state.

  • type definition: (state: CropAssignableState, animate?: boolean) => void
  • parameter information
NameTypeDescription
stateCropAssignableStateObject describing the transformation values to assign to CropZoom component.
animateboolean | undefinedWhether to animate the transition or not, defaults to true.

Type Definitions

CropZoomState

NameTypeDescription
widthnumberCurrent width of the wrapped component.
heightnumberCurrent height of the wrapped component.
translateXnumberCurrent translateX transformation value.
translateYnumberCurrent translateY transformation value.
rotatenumberCurrent rotate transformation value, angle is measured in radians.
rotateXnumberCurrent rotateX transformation value, angle is measured in radians.
rotateYnumberCurrent rotateY transformation value, angle is measured in radians.

CropAssignableState

NameTypeDescription
translateXnumberTranslateX transformation value.
translateYnumberTranslateY transformation value.
rotatenumberRotate transformation value, angle measured in radians.
rotateXnumberRotateX transformation value, angle measured in radians.
rotateYnumberRotateY transformation value, angle measured in radians.
PropertyTypeDescription
crop
typescript
{
  originX: number;
  originY: number;
  width: number;
  height: number;
}
Fields specify the top left corner and size of the crop.
context
typescript
{
  rotationAngle: number;
  flipHorizontal: boolean;
  flipVertical: boolean;
}
Fields specify if the image/video needs to flipped and to what angle must be rotated, the angle is measured in degrees.
resize
typescript
{
  width: number;
  height: number;
} | undefined
Fields specify the size your image/video must be resized to before cropping, if this property is undefined it means no resizing is necessary.

This property is always undefined if you call crop method without fixedWidth argument.

Released under the MIT License.