9. 이미지 크롭의 필요성 - UX 의사 결정 & 기능 설계

이미지 크롭 기능은 보통 소셜 미디어 같은 플랫폼이 아니라면 굳이 제공할 필요는 없지만, 링크 인 바이오 같이 나를 드러내는 플랫폼이라면 꽤나 필요하다고 생각한다. 나를 보여주는 것이 목적 중 하나이기 때문에, 어떤 이미지를 어느 영역을 보여줄 지가 중요하기 때문이다.

기능이 있고 없고의 차이를 살펴보면:

크롭 없음크롭 있음
인물의 얼굴이 아닌 상의에 집중이 쏠려 인물 얼굴 전체가 보이지 않는다.인물의 얼굴 전체가 드러나게 되어 얼굴에 집중이 된다.

더 극적인 비율의 이미지라면 원하는 요소가 아예 보이지 않을 수도 있다. 이 경우에는 크롭 기능의 효과는 더욱 돋보일 것이다.

자, 크롭 기능을 넣기로 결정했다. 그렇다면 이제 크롭 UI를 어떻게 보여줘야 할까?

일반적으로는, Dialog로 처리할 수 있겠다. 이미지에 호버하면 크롭 버튼이 나타나고 이를 클릭하면 Dialog가 열린다. 구현은 간단하지만 편집 과정이 매끄럽지 않게 느껴진다. 또한, “내 이미지가 실제 화면에서 어떻게 보일지”와 편집 화면 사이의 연결감이 약하다.

크롭 버튼 클릭 전크롭 버튼 클릭 후

그래서, 그래서 프로필 이미지 자체가 원래 위치에서 떠오르고, 주변 레이아웃은 움직이지 않는 floating UI로 프로필 이미지 버튼 자체에 크롭 UI를 연결하고자 했다.

흐름은, 크롭 버튼을 클릭하면 버튼의 overflow-hidden이 overflow-visible이 되면서 숨겨져있던 전체 이미지가 드러난다. (이때, 원본 이미지의 비율에 따라 가로 또는 세로로 확장되는 방향이 달라진다.)

처음에는 이미지 레이어 두 개를 사용해서 크롭할 때는 원본 이미지를 보여주고, 크롭 중이 아닐 때는 크롭 이미지를 보여줬다. 하지만 이는 shared layout 전환 중 이미지가 번쩍이거나 어긋나는 문제가 발생했다. 그리고 생각해보면 크롭 좌표를 사용해서 원본 이미지를 잘라서 보여주면 되기 때문에, 크롭 이미지 자체도 필요가 없다.

이는 단일 이미지 레이어로 전환하여 해결했다. 원본 이미지를 진실 원천으로 사용하고, 크롭 좌표를 통해서 버튼에 보여줄 원본 이미지 위치를 결정하는 것이다.

자세하게는:

type Crop = {
  x: number;      // 시작 위치 %
  y: number;
  width: number;  // 보일 영역의 너비 %
  height: number; // 보일 영역의 높이 %
};

function getCropImageStyle(crop: Crop) {
  return {
    position: "absolute",

    // crop 영역이 작을수록 이미지를 확대
    width: `${(100 / crop.width) * 100}%`,
    height: `${(100 / crop.height) * 100}%`,

    // crop 시작점만큼 반대 방향으로 이동
    left: `${(-crop.x / crop.width) * 100}%`,
    top: `${(-crop.y / crop.height) * 100}%`,
  };
}
// 확장 이미지는 absolute로 처리하여 다른 요소의 레이아웃에 영향을 미치지 않는다.
<div className="relative isolate">
  {/* 원래 프로필 이미지 크기 */}
  <button className="relative size-28 overflow-visible">
    {/* 이미지 클리핑 영역 */}
    <div className="absolute inset-0 overflow-hidden rounded-full">
      <img
        className="absolute max-w-none"
        style={{
          position: "absolute",
          width: `${100 / crop.width * 100}%`,
          height: `${100 / crop.height * 100}%`,
          left: `${-crop.x / crop.width * 100}%`,
          top: `${-crop.y / crop.height * 100}%`,
        }}
      />
    </div>
  </button>
</div>

변경된 UI/UX는 아래와 같다. 단일 이미지를 사용하기 때문에 번쩍이는 문제도 해결되고, 편집 경험도 자연스러워진다.

*때문에 둥근 버튼 내부에 그림자가 갇히는 디테일은 수정 필요