pull/199/merge
Youxia 7 months ago committed by GitHub
commit cd56b2d6ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -10,6 +10,7 @@ interface ImagePickerProps extends PropsWithChildren {
className?: string; className?: string;
imageUrl?: string; // 初始image url, 仅children为空时生效 imageUrl?: string; // 初始image url, 仅children为空时生效
aspect?: number; aspect?: number;
maxSize?: number;
onChange?: (blobUrl: string) => void; onChange?: (blobUrl: string) => void;
disabled?: boolean; // 禁用选择 disabled?: boolean; // 禁用选择
} }
@ -43,6 +44,7 @@ export const ImagePicker: React.FC<ImagePickerProps> = React.memo((props) => {
<ImageCropperModal <ImageCropperModal
imageUrl={reader.result.toString()} imageUrl={reader.result.toString()}
aspect={props.aspect} aspect={props.aspect}
maxSize={props.maxSize}
onConfirm={(croppedImageBlobUrl) => { onConfirm={(croppedImageBlobUrl) => {
closeModal(key); closeModal(key);
updateAvatar(croppedImageBlobUrl); updateAvatar(croppedImageBlobUrl);

@ -12,6 +12,7 @@ import { ImagePicker } from './ImagePicker';
interface ImageUploaderProps extends PropsWithChildren { interface ImageUploaderProps extends PropsWithChildren {
circle?: boolean; circle?: boolean;
aspect?: number; aspect?: number;
maxSize?: number;
className?: string; className?: string;
usage?: UploadFileUsage; usage?: UploadFileUsage;
onUploadSuccess: (fileInfo: UploadFileResult) => void; onUploadSuccess: (fileInfo: UploadFileResult) => void;
@ -46,6 +47,7 @@ export const ImageUploader: React.FC<ImageUploaderProps> = React.memo(
'rounded-full': props.circle, 'rounded-full': props.circle,
})} })}
aspect={aspect} aspect={aspect}
maxSize={props.maxSize}
disabled={loading} disabled={loading}
onChange={handlePickImage} onChange={handlePickImage}
> >
@ -65,7 +67,14 @@ ImageUploader.displayName = 'ImageUploader';
export const AvatarUploader: React.FC<ImageUploaderProps> = React.memo( export const AvatarUploader: React.FC<ImageUploaderProps> = React.memo(
(props) => { (props) => {
return <ImageUploader aspect={1} circle={true} {...props}></ImageUploader>; return (
<ImageUploader
aspect={1}
maxSize={256}
circle={true}
{...props}
></ImageUploader>
);
} }
); );
AvatarUploader.displayName = 'AvatarUploader'; AvatarUploader.displayName = 'AvatarUploader';

@ -11,9 +11,11 @@ import { ModalWrapper } from '../Modal';
export const ImageCropperModal: React.FC<{ export const ImageCropperModal: React.FC<{
imageUrl: string; imageUrl: string;
aspect?: number; aspect?: number;
maxSize?: number;
onConfirm: (croppedImageBlobUrl: string) => void; onConfirm: (croppedImageBlobUrl: string) => void;
}> = React.memo((props) => { }> = React.memo((props) => {
const aspect = props.aspect ?? 1; const aspect = props.aspect ?? 1;
const maxSize = props.maxSize ?? Infinity;
const [crop, setCrop] = useState({ x: 0, y: 0 }); const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1); const [zoom, setZoom] = useState(1);
const [area, setArea] = useState<Area>({ width: 0, height: 0, x: 0, y: 0 }); const [area, setArea] = useState<Area>({ width: 0, height: 0, x: 0, y: 0 });
@ -21,7 +23,10 @@ export const ImageCropperModal: React.FC<{
const handleConfirm = async () => { const handleConfirm = async () => {
const blobUrl = await getCroppedImg( const blobUrl = await getCroppedImg(
await createImage(props.imageUrl), await createImage(props.imageUrl),
area area,
0,
'newFile.jpeg',
maxSize
); );
props.onConfirm(blobUrl); props.onConfirm(blobUrl);
}; };
@ -73,48 +78,45 @@ let fileUrlTemp: string | null = null; // 缓存裁剪后的图片url
* @param crop * @param crop
* @param rotation * @param rotation
* @param fileName * @param fileName
* @param maxSize
* @returns blob url * @returns blob url
*/ */
function getCroppedImg( function getCroppedImg(
image: HTMLImageElement, image: HTMLImageElement,
crop: Area, crop: Area,
rotation = 0, rotation = 0,
fileName = 'newFile.jpeg' fileName = 'newFile.jpeg',
maxSize = Infinity
): Promise<string> { ): Promise<string> {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
if (!_isNil(ctx)) { if (!_isNil(ctx)) {
const maxSize = Math.max(image.width, image.height); // 计算最大尺寸
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2)); const size = Math.min(Math.max(crop.width, crop.height), maxSize);
// set each dimensions to double largest dimension to allow for a safe area for the // 计算缩放比例
// image to rotate in without being clipped by canvas context const scale = size / Math.max(crop.width, crop.height);
canvas.width = safeArea;
canvas.height = safeArea; canvas.width = scale * crop.width;
canvas.height = scale * crop.height;
// translate canvas context to a central location on image to allow rotating around the center. // translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2); ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(getRadianAngle(rotation)); ctx.rotate(getRadianAngle(rotation));
ctx.translate(-safeArea / 2, -safeArea / 2); ctx.translate(-canvas.width / 2, -canvas.height / 2);
// draw rotated image and store data. // draw rotated image and store data.
ctx.drawImage( ctx.drawImage(
image, image,
safeArea / 2 - image.width * 0.5, 0,
safeArea / 2 - image.height * 0.5 0,
); image.width,
const data = ctx.getImageData(0, 0, safeArea, safeArea); image.height,
-crop.x * scale,
// set canvas width to final desired crop size - this will clear existing context -crop.y * scale,
canvas.width = crop.width; image.width * scale,
canvas.height = crop.height; image.height * scale
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - crop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - crop.y)
); );
} }

Loading…
Cancel
Save