드래그시 나타나는 지구본 없애기(From 크롬, 웨일)

Keinn51·2023년 2월 8일
0
post-thumbnail

회사에서 드래그를 사용하는 프로젝트를 진행 중, 드래그 시 나타나는 지구본을 없애달라는 요청을 받았습니다. 짱구 옆에 나타나는 저 지구본 맞습니다. 결론적으로는 해결했습니닿ㅎㅎㅎㅎ 제가 이 문제를 해결하기 위해서 어떤 노력을 했는지 적어봅니다.

TL; DR
지구본 같이 생긴 이미지가 생겨서 보여졌다가, 원래 위치로 돌아갑니다😡
image를 preload하면 됩니다!
주저리 주저리 써 놓은게 많아서, 결론부터 보고 싶으시면 번뜩이는 아이디어 챕터를 참고하십시오!

당최 왜 저런 일이 발생하는지 몰랐어서 힘들었습니다. 실제로 이 글의 처음 완성본은, 방법이 없다는 것이 결론이었습니다ㅋㅋㅋ 이 문제를 어떻게 해결했는지 과거를 돌이켜봅니다🕖

🔎 문제를 더 정확히 바라보기

회사 프로젝트에다가 바로 실험을 진행하기에는 커스텀하기 힘듭니다. 토이 프로젝트를 만들어서 실험을 진행해보려고 합니다. 귀여운 짱구를 데려와서 실험해봅시다.

draggable=true 의 상황

엘리멘트에 draggable=true을 해주면 발생하는 먼저 상황을 봅니다. 앞서 말한대로 반투명 이미지가 발생합니다.

  • index.html (이하 html은 모두 index.html 입니다)
<img id="jg-img" draggable="true" src="./jg.png" alt="jg" />
  • main.js (이하 js는 모두 main.js 입니다)
document.getElementById("jg-img").addEventListener("dragstart", (e) => {
  console.log("here");
});
document.getElementById("jg-img").addEventListener("dragover", () => {});
document.getElementById("jg-img").addEventListener("dragend", () => {});

기본적으로 이런 반투명 이미지가 보입니다. 이 반투명 이미지가 제가 진행하고 있는 프로젝트에서는 사용할 필요가 없더군요. 이 것을 삭제하기 위해서 hideDragDefaultImg 함수를 사용합니다. 직접 제작한 것은 아니고, stack overflow 신께서 알려주신 겁니다. 확실히 소화하고 사용했어야 했다는 생각이 드는군요.

드래그 이미지를 없애기 위한 함수

드래그 시에 기본 이미지를 없애기 위해서 hideDragDefaultImg 라는 함수를 사용하고 있었습니다. 아마 이 것 때문인 것 같아요! 이 함수를 이제부터 사용해볼 겁니다.

const hideDragDefaultImg = (e) => {
  let emptyImg = new Image();
  emptyImg.src =
    "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
  e.dataTransfer.setDragImage(emptyImg, 0, 0);
};

해당 image source는 아무 것도 보이지 않는 투명 이미지입니다.

함수를 div에 실험

<div id="jg-img" draggable="true"></div>
#jg-img {
  background-image: url("./jg.png");
  width: 240px;
  height: 300px;
}
const hideDragDefaultImg = (e) => {
	...
};

document.getElementById("jg-img").addEventListener("dragstart", (e) => {
  hideDragDefaultImg(e);
  console.log("here");
});
document.getElementById("jg-img").addEventListener("dragover", () => {});
document.getElementById("jg-img").addEventListener("dragend", () => {});

background-image 를 사용했기 때문에 겉보기에는 img와 크게 다르지 않은 요소가 나옵니다. 이제 이 요소로 실험을 해봅니다.

저를 괴롭히고 있는 지구본이 발생합니다. 그 것도 한 번의 클릭만에 말이지요. 🧐

drag start만의 문제인 것이 확실합니다. drag over 이벤트를 붙이지 않아도 발생합니다. 그런데 이 것이.. 나오다가 안 나오다가 합니다. 아주 슬퍼요 div 태그만의 문제이지 않을까 생각이 되어서, img 태그를 가지고도 실험해보았습니다.

hideDragDefaultImg을 이미지에 실험

이미지 태그를 가지고 실험을 해봅시다. main.js 는 앞서 쓴 코드와 같습니다.

<img id="jg-img" draggable="true" src="./jg.png" alt="jg" />

이런 방식으로 50번이나 해봤는데 지구본은 보이지 않았습니다. 이미지 태그에서는 발생하지 않는군요. 왜 그런 걸까요…? 일단 hideDragDefaultImg 라는 함수를 까보기로 합니다.

(스포) 이 때 img 태그로 인해 이미지가 load 된 상태여서 안 나타납니다 ^_^

🧐 hideDragDefaultImg 함수 까보기

const hideDragDefaultImg = (e) => {             // (1)
  let emptyImg = new Image();                   // (3)
  emptyImg.src =                                // (4)
    "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
  e.dataTransfer.setDragImage(emptyImg, 0, 0);  // (2)
};

너무 화가난 나머지, 한 줄 한줄 살펴보려고 마음을 먹었습니다.
위에 적힌 숫자의 순서대로 살펴봅니다.

(1) 매개변수로 받는 event는 무엇인가

console.log("what is event?\n", e);

콘솔을 찍어서 확인해봅니다.

DragEvent라는 클래스의 Instance가 매개변수로 받아집니다. 아래는 DragEvent의 MDN 정의입니다.

The DragEvent interface is a DOM event that represents a drag and drop interaction.
➡ DragEvent 인터페이스는 드래그-드롭 상호 작용을 나타내는 DOM 이벤트입니다.

interface의 Oxford 사전적 정의는 이렇습니다.

전기 신호의 변환(變換)으로 중앙 처리 장치와 그 주변 장치를 서로 잇는 부분. 또는, 그 접속 장치.

중요한 단어는 서로 잇는 부분이라는 것 같습니다.
이 상황에서는 드래그 이벤트를 사용하는 저와, 드래그 대상을 이어주는 부분이겠지요?
MDN의 정의를 다시 저 나름대로 해석해볼게요. 아닐 수도 있습니다

DragEvent는 드래그-드롭 상황에서 드래그 대상에 대한 정보를 저에게 알려주는 이벤트 객체

이제 DragEvent가 무엇인지에 대한 정의를 알았읍니다.

(2) DragEvent.dataTransfer

MDN의 사전적 정의는 다음과 같습니다.

  • DragEvent.dataTransfer

The DragEvent.dataTransfer property holds the drag operation's data (as a DataTransfer object).
➡ DragEvent.dataTransfer 속성은 드래그 작업의 데이터(DataTransfer 객체)를 가집니다.

DataTransfer라는 객체가 또 따로 있네요.

  • DataTransfer

The DataTransfer object is used to hold the data that is being dragged during a drag and drop operation. It may hold one or more data items, each of one or more data types.
➡ DataTransfer 객체는 드래그 앤 드롭 작업 중에 드래그되는 데이터를 보관하는 데 사용됩니다. 각각 하나 이상의 데이터 유형인 하나 이상의 데이터 항목을 보유할 수 있습니다.

이제 잘 알겠습니다. dataTransfer 객체는 말이죠.

  • 드래그 되는 데이터 정보를 가지고 있습니다.
  • 드래그 드롭이 될 때의 effect를 설정 가능합니다.
  • 드래그 될 때 dragImage를 설정해줄 수 있습니다.

이 객체를 잘 이용하면 제가 처한 상황을 해결할 수 있을 것이라고 생각됩니다. 각 프로퍼티와 메서드의 정의는 MDN을 참고하기로 하고, 대충 제가 이해한 것들을 적어봅니다.

속성 하나하나를 다 이해하기는 어렵고, 대입해보고 되는 것을 찾아보기로 합니다.

  • getData, setData, clearData ➡ dataTransfer을 이용하면 드래그 시 data를 전송할 수 있습니다. 아마 text를 위주로 보내는 것 같습니다.
  • dropEffect ➡ 현재 선택된 드래그 타입을 가져오거나 새롭게 설정할 수 있습니다. move, none, link 등을 설정할 수 있는 것 같으나, 이번 구현에 필요하지는 않은 듯합니다.
  • effectAllowed ➡ 드래그 작업에 허용되는 효과를 지정합니다. 이건가 싶었지만 이 것은 data에 관련된 것인 것 같아요.
  • 등등 다른 것들은 변수명에서 용도를 짐작할 수 있는 것들입니다.

가슴이 아프게도, 드래그되는 이미지와 관련된 것은 setDragImage 밖에 없는 듯합니다.

(3, 4) 결국 이미지..

이미지를 사용한 방법

껄껄껄~ 결국 이미지를 설정하는 곳으로 와버렸습니다.

제가 사용하는 이미지가 아마 1px * 1px 의 이미지여서 그런 것 같습니다. 0px의 이미지를 사용하면 되는 것 아닌가 생각하실 수 있지만, 그렇게 이미지를 만들 수가 없더군요?

document에 이미지를 붙여놓지 않아서 그런 것일까 싶어서 아래처럼 붙여봤지만, 엄청난 지구본이 저를 반겨주었습니다

const hideDragDefaultImg = (e) => {
  let emptyImg = new Image();
  emptyImg.src =
    "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
  emptyImg.style.opacity = 0;
  document.body.appendChild(emptyImg);
  e.dataTransfer.setDragImage(emptyImg, 0, 0);
};

엘리먼트 사용 방법

그렇다면 이미지를 사용하는 방법이 아니라 다른 방법을 생각해보아야 합니다. 구글링을 열심히 해보니 엘리먼트를 사용해서 하는 방법도 있다고 합니다.

const hideDragDefaultImg = (e) => {
  const dragImgEl = document.createElement("span");
  dragImgEl.setAttribute(
    "style",
    "position: absolute; display: block; top: 0; left: 0; width: 0; height: 0;"
  );
  document.body.appendChild(dragImgEl);
  e.dataTransfer.setDragImage(dragImgEl, 0, 0);
}

오호 새로운 방식입니다. span tag를 만들고 이 span 태그를 dragImage로 설정해줍니다. 그래봤자 안 되는군요ㅋㅋㅋ


하..

🙄 번뜩이는 아이디어

그런데 문득 궁금증이 생겼습니다.

  • hideDragDefaultImg 함수를 사용하지 않은 이미지에는 왜 지구본이 생기지 않을까요?
  • 어쨋든 같은 이미지인데 말이죠?
  • 번뜩 들었던 생각💡 ➡ 짱구 이미지는 미리 불러온 이미지라는 것입니다!

미리 불러온 이미지?

const hideDragDefaultImg = (e) => {
  let emptyImg = new Image();
  emptyImg.src = "./jg2.png";
  emptyImg.style = "opacity: 0; width: 0px; height: 0px;";
  e.dataTransfer.setDragImage(emptyImg, 0, 0);
};

기존 짱구 이미지는 jg.png 였고 이를 복사하여 새로운 이미지인 jg2.png 를 만듭니다. 이 이미지를 드래그 시 보이는 이미지로 설정해서 진행해봅니다.

오호라... 드디어 발견했습니다. 눈물 좔좔 😂😂😂😂😂

image를 preload 해주지 않으면, 맨 처음의 드래그 시에 이미지를 불러오지 못해서 지구본이 뜨는 것 같습니다. (제 생각입니다)

<link rel="preload" as="image" href="./jg2.png" />

head 에서 preload를 하는 로직을 넣어주었으며, 이 방법을 사용하니 지구본이 더 이상 보이지 않았습니다. 👏👏👏👏

Next에서 preload를 하는 방법

출처에 따르면, next는 Image 태그에 priority 어트리뷰트를 붙여야 하는 듯합니다.

data:image가 제가 불러주는 이미지인데요. preload가 잘 되고 있습니다🥹

🌹 결론

  • 드래그 시 지구본이 나타나는 현상이 크롬과 웨일에서 발생합니다.
  • 이 것을 설정하는 방법은 dataTransfer.setDragImage 라는 메서드 뿐입니다.
  • 1px * 1px 크기의 투명 이미지를 set해주면 될까 싶지만, 포인터에 지구본처럼 생긴 친구가 계속 붙습니다.

이미지를 preload 해주면, 지구본이 사라집니다.
제 생각에는 처음 드래그 시 이미지를 불러오지 못해서 지구본이 뜨는 것 같습니다.

profile
책임질 수 있는 글을 쓰렴

2개의 댓글

comment-user-thumbnail
2023년 11월 25일

preload에 힌트를 얻어 문제를 해결했습니다 ㅎㅎ 감사합니다

1개의 답글