Skip to content

Commit

Permalink
Transform prop and flip demo (#150)
Browse files Browse the repository at this point in the history
Co-authored-by: Valentin Hervieu <ValentinH@users.noreply.github.com>
  • Loading branch information
ajlende and ValentinH authored Jun 24, 2020
1 parent 16bcba0 commit 0c36b83
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ This component requires some styles to be available in the document. By default,
| `onZoomChange` | zoom => void | | Called everytime the zoom is changed. Use it to update your `zoom` state. |
| `onRotationChange` | rotation => void | | Called everytime the rotation is changed (with mobile gestures). Use it to update your `rotation` state. |
| [`onCropComplete`](#onCropCompleteProp) | Function | | Called when the user stops moving the media or stops zooming. It will be passed the corresponding cropped area on the media in percentages and pixels |
| `transform` | string | | CSS transform to apply to the image in the editor. Defaults to `translate(${crop.x}px, ${crop.y}px) rotate(${rotation}deg) scale(${zoom})` with variables being pulled from props. |
| `style` | `{ containerStyle: object, mediaStyle: object, cropAreaStyle: object }` | | Custom styles to be used with the Cropper. Styles passed via the style prop are merged with the defaults. |
| `classes` | `{ containerClassName: string, mediaClassName: string, cropAreaClassName: string }` | | Custom class names to be used with the Cropper. Classes passed via the classes prop are merged with the defaults. If you have CSS specificity issues, you should probably use the `disableAutomaticStylesInjection` prop. |
| `mediaProps` | object | | The properties you want to apply to the media tag (<img /> or <video /> depending on your media) |
Expand Down
6 changes: 4 additions & 2 deletions docs/src/components/Demo/cropImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export const createImage = (url: string): Promise<HTMLImageElement> =>
export default async function getCroppedImg(
imageSrc: string,
pixelCrop: Area,
rotation = 0
rotation = 0,
flip = { horizontal: false, vertical: false }
): Promise<string | null> {
const image = await createImage(imageSrc)
const canvas = document.createElement('canvas')
Expand All @@ -37,9 +38,10 @@ export default async function getCroppedImg(
canvas.width = safeArea
canvas.height = safeArea

// translate canvas context to a central location to allow rotating around the center.
// translate canvas context to a central location to allow rotating and flipping around the center.
ctx.translate(safeArea / 2, safeArea / 2)
ctx.rotate(getRadianAngle(rotation))
ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1)
ctx.translate(-safeArea / 2, -safeArea / 2)

// draw rotated image and store data.
Expand Down
34 changes: 32 additions & 2 deletions docs/src/components/Demo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Button from '@material-ui/core/Button'
import IconButton from '@material-ui/core/IconButton'
import NoSsr from '@material-ui/core/NoSsr'
import Slider from '@material-ui/core/Slider'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import Typography from '@material-ui/core/Typography'
import FlipIcon from '@material-ui/icons/Flip'
import React, { useCallback, useState } from 'react'
import Cropper from 'react-easy-crop'
import { Area } from 'react-easy-crop/types'
Expand Down Expand Up @@ -61,6 +63,7 @@ const Demo: React.FC = props => {
const classes = useStyles(props)
const [crop, setCrop] = useState({ x: 0, y: 0 })
const [rotation, setRotation] = useState(0)
const [flip, setFlip] = useState({ horizontal: false, vertical: false })
const [zoom, setZoom] = useState(1)
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null)
const [croppedImage, setCroppedImage] = useState<string | null>(null)
Expand All @@ -71,12 +74,12 @@ const Demo: React.FC = props => {

const showCroppedImage = useCallback(async () => {
try {
const croppedImage = await getCroppedImg(dogImg, croppedAreaPixels, rotation)
const croppedImage = await getCroppedImg(dogImg, croppedAreaPixels, rotation, flip)
setCroppedImage(croppedImage)
} catch (e) {
console.error(e)
}
}, [croppedAreaPixels, rotation])
}, [croppedAreaPixels, rotation, flip])

const onClose = useCallback(() => {
setCroppedImage(null)
Expand All @@ -88,6 +91,13 @@ const Demo: React.FC = props => {
<NoSsr>
<Cropper
image={dogImg}
transform={[
`translate(${crop.x}px, ${crop.y}px)`,
`rotateZ(${rotation}deg)`,
`rotateY(${flip.horizontal ? 180 : 0}deg)`,
`rotateX(${flip.vertical ? 180 : 0}deg)`,
`scale(${zoom})`,
].join(' ')}
crop={crop}
rotation={rotation}
zoom={zoom}
Expand Down Expand Up @@ -128,6 +138,26 @@ const Demo: React.FC = props => {
onChange={(e, rotation) => setRotation(rotation as number)}
/>
</div>
<div className={classes.sliderContainer}>
<IconButton
aria-label="Flip Horizontal"
onClick={() => {
setFlip(prev => ({ horizontal: !prev.horizontal, vertical: prev.vertical }))
setRotation(prev => 360 - prev)
}}
>
<FlipIcon />
</IconButton>
<IconButton
aria-label="Flip Vertical"
onClick={() => {
setFlip(prev => ({ horizontal: prev.horizontal, vertical: !prev.vertical }))
setRotation(prev => 360 - prev)
}}
>
<FlipIcon style={{ transform: 'rotate(90deg)' }} />
</IconButton>
</div>
<Button
onClick={showCroppedImage}
variant="contained"
Expand Down
68 changes: 59 additions & 9 deletions examples/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type State = {
imageSrc: string
crop: Point
rotation: number
flip: { horizontal: boolean; vertical: boolean }
zoom: number
aspect: number
cropShape: 'rect' | 'round'
Expand All @@ -25,6 +26,7 @@ class App extends React.Component<{}, State> {
imageSrc,
crop: { x: 0, y: 0 },
rotation: 0,
flip: { horizontal: false, vertical: false },
zoom: 1,
aspect: 4 / 3,
cropShape: 'rect',
Expand Down Expand Up @@ -60,15 +62,56 @@ class App extends React.Component<{}, State> {
render() {
return (
<div className="App">
<input
type="range"
min={0}
max={360}
onChange={({ target: { value: rotation } }) =>
this.setState({ rotation: Number(rotation) })
}
style={{ position: 'fixed', zIndex: 9999999 }}
/>
<div className="controls">
<div>
<label>
Rotation
<input
type="range"
min={0}
max={360}
value={this.state.rotation}
onChange={({ target: { value: rotation } }) =>
this.setState({ rotation: Number(rotation) })
}
/>
</label>
</div>
<div>
<label>
<input
type="checkbox"
checked={this.state.flip.horizontal}
onChange={() =>
this.setState(prev => ({
rotation: 360 - prev.rotation,
flip: {
horizontal: !prev.flip.horizontal,
vertical: prev.flip.vertical,
},
}))
}
/>
Flip Horizontal
</label>
<label>
<input
type="checkbox"
checked={this.state.flip.vertical}
onChange={() =>
this.setState(prev => ({
rotation: 360 - prev.rotation,
flip: {
horizontal: prev.flip.horizontal,
vertical: !prev.flip.vertical,
},
}))
}
/>
Flip Vertical
</label>
</div>
</div>
<div className="crop-container">
<Cropper
image={this.state.imageSrc}
Expand All @@ -89,6 +132,13 @@ class App extends React.Component<{}, State> {
initialCroppedAreaPixels={
!!urlArgs.setInitialCrop ? { width: 699, height: 524, x: 875, y: 157 } : undefined // used to set the initial crop in e2e test
}
transform={[
`translate(${this.state.crop.x}px, ${this.state.crop.y}px)`,
`rotateZ(${this.state.rotation}deg)`,
`rotateY(${this.state.flip.horizontal ? 180 : 0}deg)`,
`rotateX(${this.state.flip.vertical ? 180 : 0}deg)`,
`scale(${this.state.zoom})`,
].join(' ')}
/>
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions examples/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ body {
bottom: 0;
}

.controls {
z-index: 1;
position: fixed;
}

.crop-container {
position: absolute;
top: 0;
Expand Down
8 changes: 6 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import cssStyles from './styles.css'
type Props = {
image?: string
video?: string
transform?: string
crop: Point
zoom: number
rotation: number
Expand Down Expand Up @@ -439,6 +440,7 @@ class Cropper extends React.Component<Props, State> {
image,
video,
mediaProps,
transform,
crop: { x, y },
rotation,
zoom,
Expand Down Expand Up @@ -466,7 +468,8 @@ class Cropper extends React.Component<Props, State> {
ref={(el: HTMLImageElement) => (this.imageRef = el)}
style={{
...mediaStyle,
transform: `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${zoom})`,
transform:
transform || `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${zoom})`,
}}
onLoad={this.onMediaLoad}
/>
Expand All @@ -483,7 +486,8 @@ class Cropper extends React.Component<Props, State> {
onLoadedMetadata={this.onMediaLoad}
style={{
...mediaStyle,
transform: `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${zoom})`,
transform:
transform || `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${zoom})`,
}}
controls={false}
/>
Expand Down

0 comments on commit 0c36b83

Please sign in to comment.