현재 활동하고있는 대학생 연합 IT창업동아리 SOPT의 3주 단기해커톤에서 ‘드래그미' 서비스의 웹 프론트 개발자로 함께하게 되었다. 드래그미 서비스의 메인 기능은 서비스 명에서도 알 수 있듯이 드래그 기능이다. 나는 그 중에서도 타임 블럭들을 드래그 하면 시간을 계획 할 수 있는 컴포넌트를 담당하여 개발했다.
완성된 컴포넌트는 다음과 같다.
드래그 앤 드랍 기능을 Mouse Event로 사용하게 된 이유에는, 먼저 Drag Event가 해당 컴포넌트의 기능과 적합하지 않다고 생각했기 때문이다.
먼저, javascript의 Drag Event에 대해서 살펴보자면
다음과 같이 7가지가 있다.
위에서도 확인 가능하듯이, 자바스크립트의 Drag Event는 어떠한 한 대상을 잡고, 그것을 drop 하는 과정에서 일어나는 event를 감지하기 위한 event들이었다.
따라서, 내가 구현하려는 드래그 기능을 개발하기 위해서는, dragstart로 드래그를 시작하고, 처음 드래그 하는 블럭이 다른 블럭 위로 진입할때 drag enter 이벤트를 감지하여 그 블럭의 색을 바꿔주는 방식으로 구현을 해야했다.
하지만 위와 같은 방식으로 드래그를 구현하면, 여러가지 문제점들이 발생했는데,
나는 과정을 거쳐 Drag Event는 드래그앤 드랍에는 적합한 이벤트 였지만 현재 내가 구현하는 단순히 ‘드래그'로만 이루어진 기능에는 적합하지 않다고 판단했다.
그래서 나는 Mouse Event로 드래그 기능을 구현하기로 하였다!
먼저 나는 마우스 이벤트로 드래그 기능을 구현하기 위해 다음과 같은 세가지 mouse event를 사용했다.
mouse down
마우스 왼쪽 버튼을 누를 때 발생
mouse over
마우스가 컴포넌트 안에 들어올때 발생
mouse up
마우스 왼쪽 버튼을 누르고있다가 떼어냈을때 발생
드래그 이벤트와 비교하자면, mouse down은 drag start가 되는것이고, mouse over는 drag enter 혹은 over , mouse up은 drag end가 되는것이다.
전체적인 로직은 다음과 같다.
다음과 같은 마우스 이벤트의 로직을 커스텀 훅으로 작성하여 사용하였다.
import React, { useState } from 'react';
interface DragBlockHookArg {
handleSubmit: () => void;
}
const INITIAL_BLOCK = -1;
function useDragBlock({ handleSubmit }: DragBlockHookArg) {
const [startBlock, setStartBlock] = useState(INITIAL_BLOCK);
const [endBlock, setEndBlock] = useState<number>(INITIAL_BLOCK);const { startBlock, endBlock, ...dragInfo } = useDragBlock({ handleSubmit });
return (
<Styled.Root id={scheduleInfo?._id} {...dragInfo}>
{timeArr.map((el: number) => (
<TimeBlock
id={el}
key={el}
isUsed={scheduleInfo.isCompleted || false}
startBlock={startBlock}
endBlock={endBlock}
isDraged={isDraged(el)}
/>
))}
</Styled.Root>
);
const [isDragging, setIsDragging] = useState(false);
const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target instanceof HTMLDivElement && e.target.id.length < 3) {
setStartBlock(parseInt(e.target.id));
setEndBlock(parseInt(e.target.id));
setIsDragging(true);
}
};
const onMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {
if (isDragging) {
if (e.target instanceof HTMLDivElement && e.target.id.length < 3) {
setEndBlock(parseInt(e.target.id));
}
}
};
const onMouseUp = () => {
handleSubmit();
setIsDragging(false);
setStartBlock(INITIAL_BLOCK);
setEndBlock(INITIAL_BLOCK);
};
return {
onMouseDown,
onMouseOver,
onMouseUp,
startBlock,
endBlock,
};
}
export default useDragBlock;
각각의 블럭이 모여 한 줄을 이루는 Blocks 컴포넌트에 해당 이벤트를 붙여 이벤트 위임을 통해서 이벤트를 감지하고 로직을 실행할 수 있게 하였다. useEffect를 이용하여, endBlock이 변경 될때마다 그 사이에 있는 블럭들의 background color을 변경하도록 하였다.
const { startBlock, endBlock, ...dragInfo } = useDragBlock({ handleSubmit });
return (
<Styled.Root id={scheduleInfo?._id} {...dragInfo}>
{timeArr.map((el: number) => (
<TimeBlock
id={el}
key={el}
isUsed={scheduleInfo.isCompleted || false}
startBlock={startBlock}
endBlock={endBlock}
isDraged={isDraged(el)}
/>
))}
</Styled.Root>
);
function TimeBlock(props: timeType) {
const { id, isUsed, startBlock, endBlock, isDraged } = props;
const [draged, setDraged] = useState(isDraged);
useEffect(() => {
if (startBlock <= id && id <= endBlock) {
isUsed ? setDraged('done') : setDraged('plan');
}
if (startBlock >= id && id >= endBlock) {
setDraged('');
}
if (startBlock === id && endBlock === id) {
if (draged === '') {
isUsed ? setDraged('done') : setDraged('plan');
} else {
setDraged('');
}
}
}, [endBlock]);
return <Styled.Block id={`${id}`} hourEnd={id % 4 === 3} draged={draged} />;
}
일단.. 나도 이러고 싶지는 않았지만… 딱히 방법이 생각나지 않았다. 아주 수많은 삽질 끝에 선택한 방법.
처음에는 startBlock과 endBlock없이 단순히 mouse enter 이벤트가 발생하면, 그 블럭의 background color을 변경하는 로직을 작성하였다
하지만 여기서 큰 문제 발생!!!!!!!!!!!
💡 자바스크립트의 이벤트 감지는, 아주 미세한 시간의 간격을 두고 감지를 한다. 해당 감지 시간에 이벤트가 발생을 했다면 이벤트 함수가 실행되는 구조그래서, 마우스를 빠르게 드래그를 했을때, 미세한 시간 간격 사이에 이벤트가 발생을 하게 되면 그 이벤트 발생을 감지하지 못하고 해당 컴포넌트를 뛰어 넘어버리는 것이다. 그래서 블럭 사이에 구멍이 숭숭 남..
이 문제는 자바스크립트 이벤트 감지의 특성이라 해결해보려 노력 했지만, 결국 endBlock 변수를 두고 useEffect를 사용하여 만약에 중간에서 이벤트를 감지하지 못했더라도 다음 endBlock을 감지하였을때 start와 end 사이의 블럭을 다 색칠하여 중간에 빈 블럭이 없게끔 해주었다.
이 방법이 완전한 해결방법은 아니라고 생각한다. 애초에 useEffect가 계속해서 실행되기에 좋지 못한 방식 같고 브라우저에 cost가 굉장한 작업 같다. 이 문제에 대해서 좀 더 고민하고 더 좋은 해결방법을 찾는 과정이 필요할 것 같다.