reactjs 如何获得裁剪的图像上传

mm5n2pyu  于 12个月前  发布在  React
关注(0)|答案(1)|浏览(93)

我想让用户上传和裁剪图像为他们的个人资料图像。我的用户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>

7gcisfzg

7gcisfzg1#

方法'canvas.toDataURL(“image/jpeg”);'返回图像的base64字符串。您可以将其作为字符串发送到服务器,然后可以将其转换为文件,或存储为字符串。

相关问题