본문 바로가기

Dot Programming/Spring Clone

[스프링 웹앱 프로젝트 #26] 프로필 이미지 변경

26. 프로필 이미지 변경

아바타 이미지 잘라서 저장하기

필요한 프론트 라이브러리
> Cropper.JS
> npm install cropper
> npm install jquery-cropper

DataURL 이란?
  > data: 라는 접두어를 가진 URL로 파일을 문서에 내장 시킬 때 사용할 수 있다.
  > 이미지를 DataURL로 저장할 수 있다.

 

 

백엔드 로직 처리하기

1. Profile DTO에 profileImage 변수 추가하기

/**
 * setting form을 채울 Data (DTO)
 */
@Data
@NoArgsConstructor
public class Profile {

	// ...
    
    private String profileImage;

    public Profile(Account account) {
        // ...
        this.profileImage = account.getProfileImage();
    }
}

 

뷰 생성하기

2. Profile.html에 profile input태그를 추가

<div class="form-group">
     <input id="profileImage" type="hidden" th:field="*{profileImage}" class="form-control"/>
</div>

 

 

 

3. main/resources/static 경로 cropper설치하기

 

$ npm install cropper

$ npm install jquery-cropper

 

그리고 profile.html에 다시 profileImage를 보여주는 Card를 추가한다.

<!-- 프로필 이미지-->
<div class="col-sm-6">
   <div class="card text-center">
      <div class="card-header">
         프로필 이미지
      </div>
      <div id="current-profile-image" class="mt-3">
         <svg th:if="${#strings.isEmpty(profile.profileImage)}" class="rounded"
            th:data-jdenticon-value="${account.nickname}" width="125" height="125"></svg>
         <img th:if="${!#strings.isEmpty(profile.profileImage)}" class="rounded"
            th:src="${profile.profileImage}"
            width="125" height="125" alt="name" th:alt="${account.nickname}"/>
      </div>
      <div id="new-profile-image" class="mt-3"></div>
      <div class="card-body">
         <div class="custom-file">
            <input type="file" class="custom-file-input" id="profile-image-file">
            <label class="custom-file-label" for="profile-image-file">프로필 이미지 변경</label>
         </div>
         <div id="new-profile-image-control" class="mt-3">
            <button class="btn btn-outline-primary btn-block" id="cut-button">자르기</button>
            <button class="btn btn-outline-success btn-block" id="confirm-button">확인</button>
            <button class="btn btn-outline-warning btn-block" id="reset-button">취소</button>
         </div>
            <div id="cropped-new-profile-image" class="mt-3"></div>
         </div>
   </div>
</div>

 

위의 Card의 모습은 아래와 같이 나온다.

 

 

 

 

4. Button Js 추가 (Cropper)

 

이젠 조건에 따라 버튼의 여부와 그에 대한 동작을 설정해줘야한다.

 <link  href="/node_modules/cropper/dist/cropper.min.css" rel="stylesheet">
    <script src="/node_modules/cropper/dist/cropper.min.js"></script>
    <script src="/node_modules/jquery-cropper/dist/jquery-cropper.min.js"></script>
    <script type="application/javascript">
        $(function() {
            cropper = '';
            let $confirmBtn = $("#confirm-button");  // 확인 버튼
            let $resetBtn = $("#reset-button");  // 취소 버튼
            let $cutBtn = $("#cut-button");  // 자르기 버튼
            let $newProfileImage = $("#new-profile-image"); // 새로 선택한 이미지
            let $currentProfileImage = $("#current-profile-image"); // 현재 프로필 이미지
            let $resultImage = $("#cropped-new-profile-image");  // 선택한 이미지 자른 부분
            let $profileImage = $("#profileImage"); // 최종 프로필 이미지

            // 버튼 숨기기
            $newProfileImage.hide();
            $cutBtn.hide();
            $resetBtn.hide();
            $confirmBtn.hide();

            // 이미지 파일 선택하면 해당 파일 읽어오고 저장하는 로직
            // profile-image-file (프로필 이미지 변경 선택)
            $("#profile-image-file").change(function(e) {
                if (e.target.files.length === 1) {
                    const reader = new FileReader();
                    reader.onload = e => {
                        if (e.target.result) {
                            if (!e.target.result.startsWith("data:image")) {
                                alert("이미지 파일을 선택하세요.");
                                return;
                            }

                            let img = document.createElement("img");
                            img.id = 'new-profile';
                            img.src = e.target.result;
                            img.setAttribute('width', '100%');

                            $newProfileImage.html(img);
                            $newProfileImage.show();
                            $currentProfileImage.hide();

                            let $newImage = $(img);
                            $newImage.cropper({aspectRatio: 1});
                            cropper = $newImage.data('cropper');

                            $cutBtn.show();
                            $confirmBtn.hide();
                            $resetBtn.show();
                        }
                    };

                    reader.readAsDataURL(e.target.files[0]);
                }
            });

            // 취소 버튼 로직
            $resetBtn.click(function() {
                $currentProfileImage.show();
                $newProfileImage.hide();
                $resultImage.hide();
                $resetBtn.hide();
                $cutBtn.hide();
                $confirmBtn.hide();
                $profileImage.val(''); // 프로필 이미지 비어있게 저장
            });

            // 자르기 버튼 로직
            $cutBtn.click(function () {
                let dataUrl = cropper.getCroppedCanvas().toDataURL();

                if (dataUrl.length > 1000 * 1024) {
                    alert("이미지 파일이 너무 큽니다. 1024000 보다 작은 파일을 사용하세요. 현재 이미지 사이즈 " + dataUrl.length);
                    return;
                }

                let newImage = document.createElement("img");
                newImage.id = "cropped-new-profile-image";
                newImage.src = dataUrl;
                newImage.width = 125;
                $resultImage.html(newImage);
                $resultImage.show();
                $confirmBtn.show();

                $confirmBtn.click(function () {
                    $newProfileImage.html(newImage);
                    $cutBtn.hide();
                    $confirmBtn.hide();
                    $profileImage.val(dataUrl);
                });
            });
        });
    </script>

 

 

 

이미지 선택

 

이미지 자르기

 

이미지 확인 (업로드)

 

 

 

 

각 버튼에 대해 로직을 설정해주면 위의 그림과 같이 작동한다. 하지만 아직 해당 이미지가 DB에 저장되지는 않는다.

 

Back

5. UpdateProfile 로직에 ProfileImage부분 추가

 

AccountService.java

public void updateProfile(Account account, Profile profile) {
// ...
        account.setProfileImage(profile.getProfileImage());

    }

 

 

Front  뷰

Nav부분 image은 UserImage가 변경되어도 아직 반영이 안되므로 수정해준다.

 

nav Image

<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown"
                   aria-haspopup="true" aria-expanded="false">
                    <!-- authentication.name에 따라 각기 다른 이미지 생성 -->
                    <svg th:if="${#strings.isEmpty(account?.profileImage)}" th:data-jdenticon-value="${#authentication.name}"
                         width="24" height="24" class="rounded border bg-light"></svg>
                    <img th:if="${!#strings.isEmpty(account?.profileImage)}" th:src="${account.profileImage}"
                         width="24" height="24" class="rounded border"/>
</a>

 

 

 


참고

인프런 강의 - 스프링과 JPA 기반 웹 애플리케이션 개발