React Image Uploader

이미지 업로드 / avatar package

Posted by ETHAN KIM on June 05, 2022 · 8 mins read
React

프로세스

  • 사용자 프로필 사진 등록
    1. 업로드 미리보기
    2. 저장과 함께 사진 등록


수정사항

  • 기존방식
    1. 리액트 formData를 php 서버에 업로드
    2. 서버 측 임시폴더로 파일을 저장
    3. 저장된 임시파일명을 리액트 서버로 response
    4. img src로 저장된 이미지 출력
    5. 회원정보 저장시 이미지파일과 회원정보 저장
    6. 임시 이미지파일 삭제


  • 문제점
    1. 사용자가 여러번 업로드 실행 시 임시파일 전부 삭제가 어려움
    2. 이미지파일 크기 및 해상도에 따른 출력문제


  • 프로세스 수정
    1. 리액트 formData(이미지파일)을 아바타 패키지를 통해 웹브라우저상에 저장 및 출력
    2. 리사이징된 이미지 및 회원정보를 서버로 송신
    3. 회원정보 및 이미지 저장


실습 환경

  • Linux(CentOS7) / front-end : react / back-end : PHP


구현 결과


./src/route/Login/Auth.js

  • 컴포넌트 UI구성
  • formData input 생성
  • avatar 패키지 import 하여 컴포넌트 불러오기
  • 이미지파일 업로드 시 avatar컴포넌트를 통해 출력
//------------------------------ MODULE -------------------------------------
import { useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { Title, Motion } from "component";
import {apiCall} from "lib";
import AvatarEditor from 'react-avatar-editor';

//------------------------------ CSS ----------------------------------------
const StyledAuth = styled.div`
    grid-template-rows: 5% 85%;
    overflow: hidden;
`;
const StyledContent = styled.div`
    margin:8%;
    grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 2fr 8fr 1fr;
    grid-row-gap:2%;
`;
const StyledTitle = styled.div`
    grid-column: 1 / span 3;
    justify-content:left;
    align-content:flex-end;
    font-size:0.7em;
`;
const StyledInputBox = styled.div`
    grid-column: 1 / span 3;
    border-radius:0.5em;
    border: solid 1px #aaa;
    padding: 1%;    
    align-content:center;
    grid-template-columns: 12fr 2fr;
    height:60%;
`;
const StyledInput = styled.input`
`;
const StyledToggle1 = styled.div`
    align-items:center;
    ${(props) => props.gender == 1 ? 
        'background:#E73737; color:white; border: solid 0.1em #E73737;' :
        'background:white; color:#888; border: solid 0.1em #aaa;'
    }
    width:70%;
    font-size:0.8em;
    border-radius:0.5em;
`;
const StyledToggle2 = styled.div`
    align-items:center;
    ${(props) => props.gender == 2 ? 
        'background:#E73737; color:white; border: solid 0.1em #E73737;' :
        'background:white; color:#888; border: solid 0.1em #aaa;'
    }    
    width:70%;
    font-size:0.8em;
    border-radius:0.5em;
`;
const StyledUploadTitle = styled.div`
    grid-column: 1 / span 2;
    justify-content:start;
    font-size:0.7em;
    margin-bottom:3%;
    height:50%;
    align-self:center;
`
const StyledButton = styled.div`
    justify-self:right;
    align-content:center;
    color:white;
    background:#E73737;
    width:70%;
    font-size:0.8em;
    border-radius:0.5em;
    height:50%;
    align-self:center;
`;
const StyledImgBox = styled.div`
    grid-column: 1 / span 3;
    justify-content:center;
    align-content:center;
    padding:2%;
    height:80%;
`;
const StyledImg = styled.img`
    justify-self:center;
    align-self:center;
    border-radius:100%;
`;
const StyledSubmit = styled.div`
    grid-column: 1 / span 3;
    background:#E73737;
    align-content:center;
    color:white;
    font-size:0.8em;
    border-radius:0.5em;
`;

//------------------------------ COMPONENT ----------------------------------
const Auth = () => {
    //init
    const navigate = useNavigate();
    const imgInput = useRef();

    //state
    const [name, setName] = useState(null);
    const [birth, setBirth] = useState(null);
    const [gender, setGender] = useState(null);
    const [imgName, setImgName] = useState(null);
    
    //func
    const passLevel = () => {
        navigate('/Welcome');
    }

    const onImgInputBtnClick = (e) => {
        e.preventDefault();
        imgInput.current.click();
    }

    const onImgChange = async(e) => {
        setImgName(e.target.files[0]);
        /*
        const formData = new FormData();
        formData.append('tmpImg', e.target.files[0]);
        const params = {
            test : '123',
        }
        const result = await apiCall.post("/Join", formData );
        if(result.data) setImgName(result.data);   
        */
    }

    //render
    return (
        <>
        <Motion>
        <StyledAuth>
        <Title text="회원정보 입력"/>
        <StyledContent>
            <StyledTitle>사용자 이름 *</StyledTitle>            
            <StyledInputBox><StyledInput onChange={(e) => setName(e.target.value)}/></StyledInputBox>
            <StyledTitle>생년월일 *</StyledTitle>            
            <StyledInputBox><StyledInput onChange={(e) => setBirth(e.target.value)}/></StyledInputBox>            
            <StyledTitle>성별 *</StyledTitle>
            <StyledToggle1 gender={gender} onClick={()=>setGender(1)}>남자</StyledToggle1>
            <StyledToggle2 gender={gender} onClick={()=>setGender(2)}>여자</StyledToggle2>
            <StyledUploadTitle>사진 *</StyledUploadTitle>

            <input style= ref={imgInput} type='file' accept='image/*' onChange={onImgChange} />

            <StyledButton onClick={onImgInputBtnClick}>사진 업로드</StyledButton>
            <StyledImgBox>
            
                {imgName ? 
                    <>
                    <div>test</div>
                    <AvatarEditor
                        image={imgName}
                        width={150}
                        height={150}
                        border={0}
                        color={['255, 255, 255, 0.6']} // RGBA
                        scale={1.2}
                        rotate={0}
                    />
                    </> : null                
                }

            </StyledImgBox>
            <StyledSubmit onClick={passLevel}>회원가입 완료하기</StyledSubmit>
        </StyledContent>                 
        </StyledAuth>
        </Motion>
        </>
    );
}

export default Auth;