> Hello World !!!

     

@syaku

JS Dragula autoScroll with React

JS Dragula autoScroll with React

칸반을 개발중인데 카드를 보이지 않는 곳에 드래그하면 스크롤이 따라 움직이게 구현해야 했다.

현재 DnD  Dragula  React 로 컨버전한 라이브러리를 사용하고 있다.

https://github.com/bevacqua/dragula

https://github.com/bevacqua/react-dragula

Dragula 란 자바스크립트로 개발된 Drop & Drag 쉽게 구현할 수 있는 라이브러리이며 React 로 컨버전된 패키지도 지원하고 있다.

카드를 드래그 하는 시점에 이벤트를 얻기 위해 기본적으로 Dragula 에서 구현되어 있는 이벤트 부터 확인한다.

  • drag : 아이템을 드래그하면 호출된다.
  • dragend : 드래그 작업을 마치면 호출된다. drop , cancel, remove 도 작업에 포함된다.
  • drop : 드래그를 놓으면 호출된다.
  • cancel : 드래그를 취소하면 호출된다.
  • remove : DOM el  container 에서 제거되면 호출된다.
  • cloned : DOM el 이 복제되면 호출된다.

위 이벤트는 한번만 호출된다.

마우스를 드래그하면 좌표가 변경될때 마다 이벤트가 호출되어야 하는 데... ... ... 아 ...

다음 나머지 이벤트는 그나마 쓸만한다.

  • shadow : 드래그하면 DOM el 과 같은 el 이 투명한 그림자가 생성된다. 그림자의 위치가 변경될때마다 호출된다.
  • out : container 를 벗어나게 드래그할때 마다 호출된다. over 후에 나가야한다.
  • over : container 안으로 드래그할때 마다 호출된다. out 후에 들어와야한다.

container 높이가 400 이고 카드 아이템의 총 높이의 합이 800이라고 한다면. container 는 스크롤을 이용하여 400 높이 안에서 카드 아이템을 모두 넣을 수 있다.

현재 스크롤 위치가 200 이고 카드를 400 이상으로 드래그 했을때 스크롤도 같이 움직여줘야 아래로 카드를 드래그할 수 있다.

out 이벤트와 over 이벤트를 이용하여 카드가 벗어나면 timeout 을 이용해 조금식 스크롤 위치를 조정하고 over 하면 timeout  clear 하는 방식으로 구현하였다.

아래는 React 에서 사용한 소스이지만 약간만 수정하면 일반적인 JS 에서도 사용할 수 있다.

import React from "react";
import { render } from "react-dom";
import shortid from "shortid";
import dragula from "react-dragula";
import "react-dragula/dist/dragula.min.css";
import "bootstrap/dist/css/bootstrap.css";
class App extends React.Component {
constructor() {
super();
this.dnd = null;
this.mirrorContainer = null;
this.drake = null;
this.initDrake = this.initDrake.bind(this);
this.autoScrollDrake = this.autoScrollDrake.bind(this);
this.clearScrollTime = this.clearScrollTime.bind(this);
}
componentDidMount() {
this.initDrake();
}
componentDidUpdate() {
if (this.drake) this.drake.destroy();
this.initDrake();
}
initDrake() {
this.drake = dragula([this.dnd], {
revertOnSpill: true,
moves: (el, container, handle) => {
console.log(handle);
return handle.dataset.type === "move";
},
mirrorContainer: this.mirrorContainer
});
this.drake.on("drop", () => {});
this.drake.on("out", (el, container) => {
this.clearScrollTime(this.scrollTime);
console.log(
this.mirrorContainer.querySelector(`[data-id=${el.dataset.id}]`)
);
this.autoScrollDrake(
container,
this.mirrorContainer.querySelector(`[data-id=${el.dataset.id}]`)
);
});
this.drake.on("over", () => {
this.clearScrollTime(this.scrollTime);
});
this.drake.on("dragend", () => {
this.clearScrollTime(this.scrollTime);
});
this.drake.on("cancel", () => {
this.clearScrollTime(this.scrollTime);
});
}
autoScrollDrake(container, mirrorContainer) {
const scroll = container;
const { height: clientHeight } = container.getBoundingClientRect();
const { scrollTop } = container;
const { y } = mirrorContainer.getBoundingClientRect();
if (clientHeight < y) {
this.scrollTime = setInterval(() => {
scroll.scrollTop += 10;
}, 20);
}
if (scrollTop > y) {
this.scrollTime = setInterval(() => {
scroll.scrollTop -= 10;
}, 20);
}
}
clearScrollTime() {
if (this.scrollTime) clearInterval(this.scrollTime);
}
render() {
return (
<div className="container">
<h1>Dragula AutoScroll with React</h1>
<div
className="panel panel-default"
style={{
height: 300,
overflowX: "hidden",
overflowY: "auto"
}}
ref={dnd => {
this.dnd = dnd;
}}
>
<ul className="list-group">
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Cras justo odio
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Dapibus ac facilisis in
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Morbi leo risus
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Porta ac consectetur ac
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Vestibulum at eros
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Cras justo odio
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Dapibus ac facilisis in
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Morbi leo risus
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Porta ac consectetur ac
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Vestibulum at eros
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Cras justo odio
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Dapibus ac facilisis in
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Morbi leo risus
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Porta ac consectetur ac
</li>
<li
data-id={shortid.generate()}
data-type="move"
className="list-group-item"
>
Vestibulum at eros
</li>
</ul>
</div>
<div
ref={mirrorContainer => {
this.mirrorContainer = mirrorContainer;
}}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));
view rawindex.js hosted with ❤ by GitHub

IE 에서는 테스트하지 못했다.

dnd js javascript dragula react autoScroll drag drop