1️⃣ 이미지 업로드

이미지 선택 시 저장되는 형식

// hooks/useImageDragDrop.ts

// 파일 객체 자체는 별도 배열에 저장
newFiles.push(file);

// 미리보기용으로는 FileReader를 사용해 데이터 URL로 변환해 저장
const reader = new FileReader();
reader.onloadend = () => {
  newImages.push(reader.result as string);  // Base64 데이터 URL
  // ...
};
reader.readAsDataURL(file);

2️⃣ 업로드시 이미지 파일 최적화

3️⃣ 이미지 드래그 드랍

⭐ react-dnd 사용

https://react-dnd.github.io/react-dnd/about

npm install react-dnd react-dnd-html5-backend  

// 핸드폰 적용시 추가 설치
npm install react-dnd-touch-backend react-dnd-multi-backend rdndmb-html5-to-touch

import { useDrag, useDrop } from 'react-dnd'

// 썸네일 이미지 컴포넌트
const ImagePreviewContainer = styled.div<{ isDragging: boolean }>`
  ${tw`relative w-[4.5rem] h-[4.5rem] rounded-md object-cover shrink-0 cursor-move`}
  opacity: ${(props) => (props.isDragging ? 0.4 : 1)};
  border: ${(props) => (props.isDragging ? '2px dashed #9ca3af' : 'none')};
  touch-action: none; /* 이 설정이 모바일에서 중요합니다 */
  -webkit-touch-callout: none; /* iOS에서 길게 터치시 메뉴 방지 */
  -webkit-user-select: none; /* Safari */
  -moz-user-select: none; /* Firefox */
  -ms-user-select: none; /* Internet Explorer/Edge */
  user-select: none; /* Non-prefixed version */
`

// 모바일 환경 감지
const isMobile = () => {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent,
  )
}

// 사용할 백엔드 결정 (HTML5 또는 Touch)
const getBackend = () => {
  return isMobile() ? TouchBackend : HTML5Backend
}

interface DragCollectedProps {
  isDragging: boolean
}

const [{ isDragging }, drag] = useDrag<DragItem, unknown, DragCollectedProps>(
  {
    type: 'IMAGE_ITEM',
    item: () => {
      return { index, id: `image-${index}`, type: 'IMAGE_ITEM' }
    },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  },
)

const [, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
  accept: 'IMAGE_ITEM',
  collect(monitor) {
    return {
      handlerId: monitor.getHandlerId(),
    }
  },
  hover(item) {
    if (!ref.current) return

    const dragIndex = item.index
    const hoverIndex = index

    // 같은 아이템 위에 드랍하는 경우 무시
    if (dragIndex === hoverIndex) return

    // 위치 변경 실행
    moveImage(dragIndex, hoverIndex)

    // 드래그 인덱스 업데이트
    item.index = hoverIndex
  },
})

// ref를 드래그와 드롭 모두에 연결
drag(drop(ref))
// html에서 이런식으로 사용

return (
  <ImagePreviewContainer ref={ref} isDragging={isDragging}>
    <img
      src={image}
      alt={`thumbnail-${index}`}
      className="w-full h-full object-cover rounded-md"
    />
    <RemoveImageBtn
      onClick={(e: React.MouseEvent<HTMLDivElement>) => {
        e.stopPropagation()
        removeImage(index)
      }}
    >
      <XmarkCircleSolid
        width="1.5rem"
        height="1.5rem"
        color={colors.darkGray}
      />
    </RemoveImageBtn>
  </ImagePreviewContainer>
)