我想让用户上传和裁剪图像为他们的个人资料图像。我的用户React容易裁剪和获得裁剪的图像。我按照this tutorial。但我不知道如何获得裁剪的图像文件,所以我可以上传到服务器。
下面是代码的结果:
的数据
我如何才能得到裁剪的图像文件,以便我可以上传到服务器?
代码:
import React, { useState, useCallback, useReducer, useEffect } from "react";
import PropTypes from "prop-types";
import Cropper from "react-easy-crop";
import "./imageLoader.css";
import CustomButton from "@components/Button";
function image64ToImage(base64:any) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = base64;
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(img);
};
});
}
function cropImage(image:any, x:any, y:any, newWidth:any, newHeight:any) {
const canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
const ctx = canvas.getContext("2d");
if (!ctx) {
return;
}
ctx.drawImage(image, x, y, newWidth, newHeight, 0, 0, newWidth, newHeight);
return canvas.toDataURL("image/jpeg");
}
async function cropImage64(base64:any, x:any, y:any, newWidth:any, newHeight:any) {
const img = await image64ToImage(base64);
return cropImage(img, x, y, newWidth, newHeight);
}
function ImageCroper({ image, setCropedImage}:{image:any, setCropedImage:any}) {
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState<number >(1);
const [coord, setCoord] = useState<any>(null);
const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
setCoord(croppedAreaPixels);
}, []);
const cutImage = () => {
cropImage64(image, coord.x, coord.y, coord.width, coord.height).then(
(croppedImage) => {
setCropedImage(croppedImage);
}
);
};
return (
<div className="cropper">
<div className="cropper-container">
<Cropper
image={image}
crop={crop}
zoom={zoom}
cropShape="round"
showGrid={false}
aspect={1}
onCropChange={setCrop}
onCropComplete={onCropComplete}
onZoomChange={setZoom}
/>
</div>
<div className="cropper-controls">
<input
className="cropper-zoom-range"
type="range"
value={zoom}
min={1}
max={3}
step={0.01}
aria-labelledby="Zoom"
onChange={(e) => {
setZoom(+e.target.value);
}}
/>
<CustomButton variant="red" icon="none"
w="150px" onClick={() => cutImage()} text="SIMPAN"/>
</div>
</div>
);
}
ImageCroper.propTypes = {
image: PropTypes.string.isRequired,
setCropedImage: PropTypes.func.isRequired
};
function ImageLoader({ image, callback, size}:{image:any, callback:any, size:any}) {
const [file, setFile] = useState<File | null>(null);
const [result, setResult] = useState<string | null>(null);
const [crop, toogleCrop] = useReducer((currCrop) => !currCrop, false);
const [imageState, setImage] = useState<string | null>(image);
useEffect(() => {
if (!file) {
return;
}
const reader = new FileReader();
reader.onloadend = () => {
setResult(reader.result as string);
toogleCrop();
};
reader.readAsDataURL(file);
}, [file]);
return (
<>
<label
className="logo"
style={{
width: size,
height: size,
backgroundSize: size,
backgroundImage: `url(${imageState})`,
}}
>
<input
type="file"
accept="image/*"
style={{
display: "none",
}}
onChange={(e) => {
e.preventDefault();
const selectedFile = e.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
}
}}
/>
</label>
{crop ? (
<ImageCroper
image={result || ""}
setCropedImage={(newImage) => {
setImage(newImage);
toogleCrop();
callback(newImage);
}}
/>
) : null}
</>
);
}
ImageLoader.propTypes = {
image: PropTypes.string,
callback: PropTypes.func.isRequired,
size: PropTypes.number.isRequired,
};
export default ImageLoader;
字符串
用于从imageLoader提交图像的表单:
const Setting = () => {
const [memberData, setMemberData] = useState<MemberDataProps>();
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [expiredDate, setExpiredDate] = useState('');
const [isPhoneInvalid, setIsPhoneInvalid] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [newPicture, setNewPicture] = useState<any[]>([]);
console.log(newPicture);
const toast = useToast();
const navigate = useNavigate();
const handlePhoneNumber = (value: string) => {
const isNumber = value.match(/^[0-9\b]+$/);
setPhoneNumber(value);
if (isNumber) {
setIsPhoneInvalid(false);
} else {
setIsPhoneInvalid(true);
}
if (value.length < 11) {
setIsPhoneInvalid(true);
}
};
const handleSubmit = async () => {
const formData = new FormData();
formData.append('name', name);
formData.append('phoneNumber', phoneNumber);
if (newPicture) {
formData.append('profilePhoto', newPicture[0]);
}
formData.append('subscribeNewsletter', String(isSubscribe));
try {
await fetchFormData({
method: 'PATCH',
path: 'member/data',
data: formData,
}).then((response) => {
if (!response.error) {
toast({
title: 'Berhasil',
description: 'Profil Anda telah berhasil diperbaharui',
status: 'success',
duration: 9000,
isClosable: true,
});
getMemberData();
} else {
toast({
title: 'Error',
description: response.message,
status: 'error',
duration: 9000,
isClosable: true,
});
}
});
} catch (error) {
toast({
title: 'Error',
description: error.message,
status: 'error',
duration: 9000,
isClosable: true,
});
}
};
const getMemberData = async () => {
try {
await fetchRequest({ method: 'GET', path: 'member' }).then((response) => {
if (!response.error) {
setMemberData(response);
setPhoneNumber(response.phoneNumber);
setExpiredDate(
dayjs(response.membershipValidEnd).format('DD MMMM YYYY')
);
setEmail(response.email);
setName(response.name);
setIsSubscribe(response.subscribeNewsletter);
}
});
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getMemberData();
}, []);
return (
<Box>
<Box h="30px" paddingLeft={{ base: '10px', lg: '3%' }} w="100%">
<CustomBreadcrumb breadcrumbs={BREADCRUMB_PENGATURAN} />
</Box>
<Stack
w={{ xl: '100%' }}
direction={['column', 'row']}
spacing={{ md: '50px', base: '30px' }}
paddingLeft={{ base: '3%' }}
paddingRight={{ base: '3%' }}
paddingTop={{ base: '0px', lg: '20px' }}
>
<Box
w={{ md: '20%' }}
h="400px"
display={{ md: 'inline', base: 'none' }}
/>
<Box w="100%">
<VStack w="100%" marginBottom="10px">
<ImageLoader
size={155}
image={memberData?.photoUrl ||
'https://www.seekpng.com/png/detail/966-9665317_placeholder-image-person-jpg.png'}
callback={(image) => setNewPicture(image)}
/>
<VStack align="start">
<Text className="fontSemiBold" fontSize={{ lg: 'md', base: 'sm' }}>
Name
</Text>
<InputGroup paddingTop="5px" zIndex={0}>
<InputLeftElement
pointerEvents="none"
children={<Image src={imgUsernameIcon} w="30%" />}
paddingTop="5px"
/>
<Input
type="text"
placeholder="Masukkan Nama"
_focus={{ boxShadow: 'none', borderColor: RED }}
fontSize={{ lg: 'md', base: 'sm' }}
value={name}
onChange={(e: any) => setName(e.target.value)}
errorBorderColor={RED}
isInvalid={name === ''}
/>
</InputGroup>
<CustomButton
text="SIMPAN"
variant="red"
icon="none"
w="150px"
onClick={handleSubmit}
/>
</Box>
型
1条答案
按热度按时间7gcisfzg1#
方法'canvas.toDataURL(“image/jpeg”);'返回图像的base64字符串。您可以将其作为字符串发送到服务器,然后可以将其转换为文件,或存储为字符串。