Absolute Zoom
#
IdeaLet's look at the cropper below. It's pretty simple, but it has distinguish feature: zoom slider. The problem here, that the cropper state doesn't have the concept of absolute zoom. The image zoom is defined by the visible area size completely.
In this tutorial we will discuss about two different aspects:
- First, we need to extend the cropper, add the slider into one.
- Secondly, we need to implement the absolute zoom. We should be able to define the current absolute zoom value and to zoom an image to specific one.
#
Preparation#
CropperWe will create the simplest custom cropper based on FixedCropper
.
- CustomCropper.tsx
import React, { forwardRef } from 'react';import { ImageRestriction, FixedCropper, FixedCropperRef, FixedCropperProps } from 'react-advanced-cropper';
export type CustomCropperProps = FixedCropperProps;
export type CustomCropperRef = FixedCropperRef;
export const CustomCropper = forwardRef<CustomCropperRef, CustomCropperProps>( ({ stencilProps, ...props }: CustomCropperProps, ref) => { return ( <FixedCropper ref={ref} stencilProps={{ handlers: false, lines: false, movable: false, resizable: false, ...stencilProps, }} imageRestriction={ImageRestriction.stencil} {...props} /> ); },);
CustomCropper.displayName = 'CustomCropper';
#
NavigationIn this tutorial the prepared component will be used: <Navigation/>
. It can be arbitrary component, but
for sake of brevity the ready one used.
#
Extending the cropperTo add the navigation into the cropper we should create the custom cropper wrapper. The cropper wrapper is the special component that wraps all the cropper's internals (include image, stencil, etc.) and has the direct access to the cropper state.component
The default wrapper is <CropperWrapper/>
.
It can be used as the foundation for the custom cropper wrapper with navigation and perhaps some other
advanced features.
- CustomWrapper.tsx
- CustomWrapper.scss
import React, { CSSProperties, FC } from 'react';import cn from 'classnames';import { CropperRef, CropperFade } from 'react-advanced-cropper';import { Navigation } from './Navigation/Navigation';import './CustomWrapper.scss';
interface Props { cropper: CropperRef; loading: boolean; loaded: boolean; className?: string; style?: CSSProperties;}
export const CustomWrapper: FC<Props> = ({ cropper, children, loaded, className }) => { return ( <CropperFade className={cn('custom-wrapper', className)} visible={state && loaded}> {children} <Navigation className="custom-wrapper__navigation" /> </CropperFade> );};
.custom-wrapper { flex-grow: 1; min-height: 0; &__navigation { width: 462px; position: absolute; bottom: 0; left: 0; right: 0; margin: 0 auto; }}
#
Absolute ZoomThe cropper state CropperState
doesn't have the concept of absolute zoom.
It can be only derived from it. It's the not trivial process, but, fortunately, there
is the advanced-cropper/extensions/absolute-zoom
extension that can be used to accomplish it.
#
Extension FunctionsThe two functions is needed to implement the absolute zoom.
getAbsoluteZoom
#
Definition:
getAbsoluteZoom(state: CropperState, settings: CropperSettings, normalize = true): number
Details:
This function returns the current absolute visible area size (with respect to different restrictions):
0
correspond to the minimum size and 1
correspond to the maximum size.
If normalize
is true
the result will always belong to [0, 1]
.
getZoomFactor
#
Definition:
getZoomFactor(state: CropperState, settings: CropperSettings, absoluteZoom: number): number
Details:
This function returns the ratio of the visible area size that corresponds to desired absolute zoom and the visible area size that corresponds to the current absolute zoom.
In other words, it's the relative visible area scale that is needed to get the desired absolute zoom.
#
How to useThe using of these functions is pretty straightforward.
#
Getting the current absolute zoomconst absoluteZoom = getAbsoluteZoom(cropper.getState(), cropper.getSettings());
#
Zooming the cropper to specific absolute zoom:const onZoom = (value: number, transitions?: boolean) => { if (cropper) { cropper.zoomImage( getZoomFactor(cropper.getState(), cropper.getSettings(), value), { transitions, }, ); }};
#
Integration#
Cropper WrapperFirst of all, let's use the principles described above in the custom wrapper.
- CustomWrapper.tsx
- CustomWrapper.scss
import React, { CSSProperties, FC } from 'react';import cn from 'classnames';import { CropperRef, CropperFade } from 'react-advanced-cropper';import { getAbsoluteZoom, getZoomFactor } from 'advanced-cropper/extensions/absolute-zoom';import { Navigation } from './Navigation/Navigation';import './CustomWrapper.scss';
interface Props { cropper: CropperRef; loading: boolean; loaded: boolean; className?: string; style?: CSSProperties;}
export const CustomWrapper: FC<Props> = ({ cropper, children, loaded, className }) => { const state = cropper.getState();
const settings = cropper.getSettings();
const absoluteZoom = getAbsoluteZoom(state, settings);
const onZoom = (value: number, transitions?: boolean) => { cropper.zoomImage(getZoomFactor(state, settings, value), { transitions: !!transitions, }); };
return ( <CropperFade className={cn('custom-wrapper', className)} visible={state && loaded}> {children} <Navigation className="custom-wrapper__navigation" zoom={absoluteZoom} onZoom={onZoom} /> </CropperFade> );};
.custom-wrapper { flex-grow: 1; min-height: 0; &__navigation { width: 462px; position: absolute; bottom: 0; left: 0; right: 0; margin: 0 auto; }}
#
Custom CropperThe created wrapper should be passed into the custom cropper. And that is the last step to create the cropper that you can see in the start of this tutorial.
- CustomCropper.tsx
import React, { forwardRef } from 'react';import { ImageRestriction, FixedCropper, FixedCropperRef, FixedCropperProps } from 'react-advanced-cropper';import { CustomWrapper } from './CustomWrapper';
export type CustomCropperProps = FixedCropperProps;
export type CustomCropperRef = FixedCropperRef;
export const CustomCropper = forwardRef<CustomCropperRef, CustomCropperProps>( ({ stencilProps, ...props }: CustomCropperProps, ref) => { return ( <FixedCropper ref={ref} stencilProps={{ handlers: false, lines: false, movable: false, resizable: false, ...stencilProps, }} imageRestriction={ImageRestriction.stencil} wrapperComponent={CustomWrapper} {...props} /> ); },);
CustomCropper.displayName = 'CustomCropper';