react-image-crop
Version:
A responsive image cropping tool for React
348 lines (307 loc) • 10.2 kB
text/typescript
import { PixelCrop, PercentCrop, Crop, Ords } from './types'
export const defaultCrop: PixelCrop = {
x: 0,
y: 0,
width: 0,
height: 0,
unit: 'px',
}
export const clamp = (num: number, min: number, max: number) => Math.min(Math.max(num, min), max)
export const cls = (...args: unknown[]) => args.filter(v => v && typeof v === 'string').join(' ')
export const areCropsEqual = (cropA: Partial<Crop>, cropB: Partial<Crop>) =>
cropA === cropB ||
(cropA.width === cropB.width &&
cropA.height === cropB.height &&
cropA.x === cropB.x &&
cropA.y === cropB.y &&
cropA.unit === cropB.unit)
export function makeAspectCrop(
crop: Pick<PercentCrop, 'unit'> & Partial<Omit<PercentCrop, 'unit'>>,
aspect: number,
containerWidth: number,
containerHeight: number
): PercentCrop
export function makeAspectCrop(
crop: Pick<PixelCrop, 'unit'> & Partial<Omit<PixelCrop, 'unit'>>,
aspect: number,
containerWidth: number,
containerHeight: number
): PixelCrop
export function makeAspectCrop(crop: Partial<Crop>, aspect: number, containerWidth: number, containerHeight: number) {
const pixelCrop = convertToPixelCrop(crop, containerWidth, containerHeight)
if (crop.width) {
pixelCrop.height = pixelCrop.width / aspect
}
if (crop.height) {
pixelCrop.width = pixelCrop.height * aspect
}
if (pixelCrop.y + pixelCrop.height > containerHeight) {
pixelCrop.height = containerHeight - pixelCrop.y
pixelCrop.width = pixelCrop.height * aspect
}
if (pixelCrop.x + pixelCrop.width > containerWidth) {
pixelCrop.width = containerWidth - pixelCrop.x
pixelCrop.height = pixelCrop.width / aspect
}
if (crop.unit === '%') {
return convertToPercentCrop(pixelCrop, containerWidth, containerHeight)
}
return pixelCrop
}
export function centerCrop(
crop: Pick<PercentCrop, 'unit'> & Partial<Omit<PercentCrop, 'unit'>>,
containerWidth: number,
containerHeight: number
): PercentCrop
export function centerCrop(
crop: Pick<PixelCrop, 'unit'> & Partial<Omit<PixelCrop, 'unit'>>,
containerWidth: number,
containerHeight: number
): PixelCrop
export function centerCrop(crop: Partial<Crop>, containerWidth: number, containerHeight: number) {
const pixelCrop = convertToPixelCrop(crop, containerWidth, containerHeight)
pixelCrop.x = (containerWidth - pixelCrop.width) / 2
pixelCrop.y = (containerHeight - pixelCrop.height) / 2
if (crop.unit === '%') {
return convertToPercentCrop(pixelCrop, containerWidth, containerHeight)
}
return pixelCrop
}
export function convertToPercentCrop(
crop: Partial<Crop>,
containerWidth: number,
containerHeight: number
): PercentCrop {
if (crop.unit === '%') {
return { ...defaultCrop, ...crop, unit: '%' }
}
return {
unit: '%',
x: crop.x ? (crop.x / containerWidth) * 100 : 0,
y: crop.y ? (crop.y / containerHeight) * 100 : 0,
width: crop.width ? (crop.width / containerWidth) * 100 : 0,
height: crop.height ? (crop.height / containerHeight) * 100 : 0,
}
}
export function convertToPixelCrop(crop: Partial<Crop>, containerWidth: number, containerHeight: number): PixelCrop {
if (!crop.unit) {
return { ...defaultCrop, ...crop, unit: 'px' }
}
if (crop.unit === 'px') {
return { ...defaultCrop, ...crop, unit: 'px' }
}
return {
unit: 'px',
x: crop.x ? (crop.x * containerWidth) / 100 : 0,
y: crop.y ? (crop.y * containerHeight) / 100 : 0,
width: crop.width ? (crop.width * containerWidth) / 100 : 0,
height: crop.height ? (crop.height * containerHeight) / 100 : 0,
}
}
// Sorry.
export function containCrop(
pixelCrop: PixelCrop,
aspect: number,
ord: Ords,
containerWidth: number,
containerHeight: number,
minWidth = 0,
minHeight = 0,
maxWidth = containerWidth,
maxHeight = containerHeight
) {
const containedCrop = { ...pixelCrop }
let _minWidth = Math.min(minWidth, containerWidth)
let _minHeight = Math.min(minHeight, containerHeight)
let _maxWidth = Math.min(maxWidth, containerWidth)
let _maxHeight = Math.min(maxHeight, containerHeight)
if (aspect) {
if (aspect > 1) {
// Landscape - increase width min + max.
_minWidth = minHeight ? minHeight * aspect : _minWidth
_minHeight = _minWidth / aspect
_maxWidth = maxWidth * aspect
} else {
// Portrait - increase height min + max.
_minHeight = minWidth ? minWidth / aspect : _minHeight
_minWidth = _minHeight * aspect
_maxHeight = maxHeight / aspect
}
}
// Stop underflow on top.
if (containedCrop.y < 0) {
containedCrop.height = Math.max(containedCrop.height + containedCrop.y, _minHeight)
containedCrop.y = 0
}
// Stop underflow on left.
if (containedCrop.x < 0) {
containedCrop.width = Math.max(containedCrop.width + containedCrop.x, _minWidth)
containedCrop.x = 0
}
// Stop overflow on right.
const xOverflow = containerWidth - (containedCrop.x + containedCrop.width)
if (xOverflow < 0) {
containedCrop.x = Math.min(containedCrop.x, containerWidth - _minWidth)
containedCrop.width += xOverflow
}
// Stop overflow on bottom.
const yOverflow = containerHeight - (containedCrop.y + containedCrop.height)
if (yOverflow < 0) {
containedCrop.y = Math.min(containedCrop.y, containerHeight - _minHeight)
containedCrop.height += yOverflow
}
// Make crop respect min width generally.
if (containedCrop.width < _minWidth) {
if (ord === 'sw' || ord == 'nw') {
// Stops box moving when min is hit.
containedCrop.x -= _minWidth - containedCrop.width
}
containedCrop.width = _minWidth
}
// Make crop respect min height generally.
if (containedCrop.height < _minHeight) {
if (ord === 'nw' || ord == 'ne') {
// Stops box moving when min is hit.
containedCrop.y -= _minHeight - containedCrop.height
}
containedCrop.height = _minHeight
}
// Make crop respect max width generally.
if (containedCrop.width > _maxWidth) {
if (ord === 'sw' || ord == 'nw') {
// Stops box moving when max is hit.
containedCrop.x -= _maxWidth - containedCrop.width
}
containedCrop.width = _maxWidth
}
// Make crop respect max height generally.
if (containedCrop.height > _maxHeight) {
if (ord === 'nw' || ord == 'ne') {
// Stops box moving when min is hit.
containedCrop.y -= _maxHeight - containedCrop.height
}
containedCrop.height = _maxHeight
}
// Maintain aspect after size fixing.
if (aspect) {
const currAspect = containedCrop.width / containedCrop.height
if (currAspect < aspect) {
// Crop is shrunk on the width so adjust the height.
const newHeight = Math.max(containedCrop.width / aspect, _minHeight)
if (ord === 'nw' || ord == 'ne') {
// Stops box moving when min is hit.
containedCrop.y -= newHeight - containedCrop.height
}
containedCrop.height = newHeight
} else if (currAspect > aspect) {
// Crop is shrunk on the height so adjust the width.
const newWidth = Math.max(containedCrop.height * aspect, _minWidth)
if (ord === 'sw' || ord == 'nw') {
// Stops box moving when max is hit.
containedCrop.x -= newWidth - containedCrop.width
}
containedCrop.width = newWidth
}
}
return containedCrop
}
export function nudgeCrop(pixelCrop: PixelCrop, key: string, offset: number, ord: Ords) {
const nextCrop = { ...pixelCrop }
if (key === 'ArrowLeft') {
if (ord === 'nw') {
nextCrop.x -= offset
nextCrop.y -= offset
nextCrop.width += offset
nextCrop.height += offset
} else if (ord === 'w') {
nextCrop.x -= offset
nextCrop.width += offset
} else if (ord === 'sw') {
nextCrop.x -= offset
nextCrop.width += offset
nextCrop.height += offset
} else if (ord === 'ne') {
nextCrop.y += offset
nextCrop.width -= offset
nextCrop.height -= offset
} else if (ord === 'e') {
nextCrop.width -= offset
} else if (ord === 'se') {
nextCrop.width -= offset
nextCrop.height -= offset
}
} else if (key === 'ArrowRight') {
if (ord === 'nw') {
nextCrop.x += offset
nextCrop.y += offset
nextCrop.width -= offset
nextCrop.height -= offset
} else if (ord === 'w') {
// Niche: Will move right if minWidth hit.
nextCrop.x += offset
nextCrop.width -= offset
} else if (ord === 'sw') {
nextCrop.x += offset
nextCrop.width -= offset
nextCrop.height -= offset
} else if (ord === 'ne') {
nextCrop.y -= offset
nextCrop.width += offset
nextCrop.height += offset
} else if (ord === 'e') {
nextCrop.width += offset
} else if (ord === 'se') {
nextCrop.width += offset
nextCrop.height += offset
}
}
if (key === 'ArrowUp') {
if (ord === 'nw') {
nextCrop.x -= offset
nextCrop.y -= offset
nextCrop.width += offset
nextCrop.height += offset
} else if (ord === 'n') {
nextCrop.y -= offset
nextCrop.height += offset
} else if (ord === 'ne') {
nextCrop.y -= offset
nextCrop.width += offset
nextCrop.height += offset
} else if (ord === 'sw') {
nextCrop.x += offset
nextCrop.width -= offset
nextCrop.height -= offset
} else if (ord === 's') {
nextCrop.height -= offset
} else if (ord === 'se') {
nextCrop.width -= offset
nextCrop.height -= offset
}
} else if (key === 'ArrowDown') {
if (ord === 'nw') {
nextCrop.x += offset
nextCrop.y += offset
nextCrop.width -= offset
nextCrop.height -= offset
} else if (ord === 'n') {
// Niche: Will move down if minHeight hit.
nextCrop.y += offset
nextCrop.height -= offset
} else if (ord === 'ne') {
nextCrop.y += offset
nextCrop.width -= offset
nextCrop.height -= offset
} else if (ord === 'sw') {
nextCrop.x -= offset
nextCrop.width += offset
nextCrop.height += offset
} else if (ord === 's') {
nextCrop.height += offset
} else if (ord === 'se') {
nextCrop.width += offset
nextCrop.height += offset
}
}
return nextCrop
}