이미지 크롭 기능은 보통 소셜 미디어 같은 플랫폼이 아니라면 굳이 제공할 필요는 없지만, 링크 인 바이오 같이 나를 드러내는 플랫폼이라면 꽤나 필요하다고 생각한다. 나를 보여주는 것이 목적 중 하나이기 때문에, 어떤 이미지를 어느 영역을 보여줄 지가 중요하기 때문이다.
기능이 있고 없고의 차이를 살펴보면:
| 크롭 없음 | 크롭 있음 |
|---|---|
더 극적인 비율의 이미지라면 원하는 요소가 아예 보이지 않을 수도 있다. 이 경우에는 크롭 기능의 효과는 더욱 돋보일 것이다.
자, 크롭 기능을 넣기로 결정했다. 그렇다면 이제 크롭 UI를 어떻게 보여줘야 할까?
일반적으로는, Dialog로 처리할 수 있겠다. 이미지에 호버하면 크롭 버튼이 나타나고 이를 클릭하면 Dialog가 열린다. 구현은 간단하지만 편집 과정이 매끄럽지 않게 느껴진다. 또한, “내 이미지가 실제 화면에서 어떻게 보일지”와 편집 화면 사이의 연결감이 약하다.
| 크롭 버튼 클릭 전 | 크롭 버튼 클릭 후 |
|---|---|
그래서, 그래서 프로필 이미지 자체가 원래 위치에서 떠오르고, 주변 레이아웃은 움직이지 않는 floating UI로 프로필 이미지 버튼 자체에 크롭 UI를 연결하고자 했다.
흐름은, 크롭 버튼을 클릭하면 버튼의 overflow-hidden이 overflow-visible이 되면서 숨겨져있던 전체 이미지가 드러난다. (이때, 원본 이미지의 비율에 따라 가로 또는 세로로 확장되는 방향이 달라진다.)
처음에는 이미지 레이어 두 개를 사용해서 크롭할 때는 원본 이미지를 보여주고, 크롭 중이 아닐 때는 크롭 이미지를 보여줬다. 하지만 이는 shared layout 전환 중 이미지가 번쩍이거나 어긋나는 문제가 발생했다. 그리고 생각해보면 크롭 좌표를 사용해서 원본 이미지를 잘라서 보여주면 되기 때문에, 크롭 이미지 자체도 필요가 없다.
이는 단일 이미지 레이어로 전환하여 해결했다. 원본 이미지를 진실 원천으로 사용하고, 크롭 좌표를 통해서 버튼에 보여줄 원본 이미지 위치를 결정하는 것이다.
자세하게는:
- 현재 데이터 기준은:
- 원본 이미지:
imageSource - 화면에 사용할 이미지 키:
image - crop 영역:
imageCrop = { x, y, width, height } - 좌표 단위: 원본 이미지 기준의 정규화된 퍼센트
- 원본 이미지:
- 이미지 렌더링은 좌표를 CSS 이미지 스타일로 변환한다.
- 이미지
width와height를 crop 영역에 맞게 확대 left와top을 음수로 이동- 바깥 wrapper의
overflow-hidden으로 선택 영역만 노출
- 이미지
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}%`,
};
}
- 크롭 버튼을 클릭하면 이미지가 원래 위치에서 확장된다.
overflow-visible과clip-path전환을 사용해 원형 프로필 이미지가 원래 위치에서 자연스럽게 확장- 이때, 주변 레이아웃에는 영향을 주지 않아서 이미지가 다른 요소에 겹쳐도 요소들이 밀리지 않는다. (*가장 중요)
// 확장 이미지는 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는 아래와 같다. 단일 이미지를 사용하기 때문에 번쩍이는 문제도 해결되고, 편집 경험도 자연스러워진다.
*때문에 둥근 버튼 내부에 그림자가 갇히는 디테일은 수정 필요