회사에서 드래그를 사용하는 프로젝트를 진행 중, 드래그 시 나타나는 지구본을 없애달라는 요청을 받았습니다. 짱구 옆에 나타나는 저 지구본 맞습니다. 결론적으로는 해결했습니닿ㅎㅎㅎㅎ
제가 이 문제를 해결하기 위해서 어떤 노력을 했는지 적어봅니다.
TL; DR
지구본 같이 생긴 이미지가 생겨서 보여졌다가, 원래 위치로 돌아갑니다😡
image를 preload하면 됩니다!
주저리 주저리 써 놓은게 많아서, 결론부터 보고 싶으시면 번뜩이는 아이디어 챕터를 참고하십시오!
당최 왜 저런 일이 발생하는지 몰랐어서 힘들었습니다. 실제로 이 글의 처음 완성본은, 방법이 없다는 것이 결론이었습니다ㅋㅋㅋ 이 문제를 어떻게 해결했는지 과거를 돌이켜봅니다🕖
회사 프로젝트에다가 바로 실험을 진행하기에는 커스텀하기 힘듭니다. 토이 프로젝트를 만들어서 실험을 진행해보려고 합니다. 귀여운 짱구를 데려와서 실험해봅시다.
엘리멘트에 draggable=true
을 해주면 발생하는 먼저 상황을 봅니다. 앞서 말한대로 반투명 이미지가 발생합니다.
<img id="jg-img" draggable="true" src="./jg.png" alt="jg" />
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 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 태그를 가지고도 실험해보았습니다.
이미지 태그를 가지고 실험을 해봅시다. main.js
는 앞서 쓴 코드와 같습니다.
<img id="jg-img" draggable="true" src="./jg.png" alt="jg" />
이런 방식으로 50번이나 해봤는데 지구본은 보이지 않았습니다. 이미지 태그에서는 발생하지 않는군요. 왜 그런 걸까요…? 일단 hideDragDefaultImg
라는 함수를 까보기로 합니다.
(스포) 이 때 img 태그로 인해 이미지가 load 된 상태여서 안 나타납니다 ^_^
const hideDragDefaultImg = (e) => { // (1)
let emptyImg = new Image(); // (3)
emptyImg.src = // (4)
"data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
e.dataTransfer.setDragImage(emptyImg, 0, 0); // (2)
};
너무 화가난 나머지, 한 줄 한줄 살펴보려고 마음을 먹었습니다.
위에 적힌 숫자의 순서대로 살펴봅니다.
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가 무엇인지에 대한 정의를 알았읍니다.
MDN의 사전적 정의는 다음과 같습니다.
The DragEvent.dataTransfer property holds the drag operation's data (as a DataTransfer object).
➡ DragEvent.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
객체는 말이죠.
이 객체를 잘 이용하면 제가 처한 상황을 해결할 수 있을 것이라고 생각됩니다. 각 프로퍼티와 메서드의 정의는 MDN을 참고하기로 하고, 대충 제가 이해한 것들을 적어봅니다.
속성 하나하나를 다 이해하기는 어렵고, 대입해보고 되는 것을 찾아보기로 합니다.
드래그 시 data를 전송
할 수 있습니다. 아마 text를 위주로 보내는 것 같습니다.선택된 드래그 타입
을 가져오거나 새롭게 설정할 수 있습니다. move, none, link 등을 설정할 수 있는 것 같으나, 이번 구현에 필요하지는 않은 듯합니다.허용되는 효과
를 지정합니다. 이건가 싶었지만 이 것은 data에 관련된 것인 것 같아요.가슴이 아프게도, 드래그되는 이미지와 관련된 것은 setDragImage 밖에 없는 듯합니다.
껄껄껄~ 결국 이미지를 설정하는 곳으로 와버렸습니다.
제가 사용하는 이미지가 아마 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
로 설정해줍니다. 그래봤자 안 되는군요ㅋㅋㅋ
하..
그런데 문득 궁금증이 생겼습니다.
짱구 이미지는 미리 불러온 이미지라는 것입니다!
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는 Image 태그에 priority 어트리뷰트를 붙여야 하는 듯합니다.
data:image
가 제가 불러주는 이미지인데요. preload가 잘 되고 있습니다🥹
1px * 1px
크기의 투명 이미지를 set해주면 될까 싶지만, 포인터에 지구본처럼 생긴 친구가 계속 붙습니다.
이미지를 preload 해주면, 지구본이 사라집니다.
제 생각에는 처음 드래그 시 이미지를 불러오지 못해서 지구본이 뜨는 것 같습니다.
preload에 힌트를 얻어 문제를 해결했습니다 ㅎㅎ 감사합니다