CSS&JS/⚡Thinkers

vanilla js로 sortable list 구현하기

arancia_ 2021. 12. 9. 16:11

원본 코드 : https://codepen.io/vtno/pen/MXmpoy

 

Sortable List Vanilla JS

...

codepen.io

원본코드 전부 그대로인 상태에서 수정한것들은

  1. 맨 마지막 / 처음으로 드래그 할 때 에러 뿜는것만 수정
  2. module 형식으로 분리

만 했습니다. ㅠ.ㅠ 원 코드 작성자에게 엄청난 감사를 보내며...

원래 jQuery ui에서 Sortable을 제공하는데 이걸 vanilla js로 어떻게 구현하나... 혼자 앓다가 검색 해보니 역시 세상은 넓고 능력자는 많다.

 

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
<link rel="stylesheet" type="text/css" href="./style.css"/>
<script src="./main.js" type="module"></script>
</head>
<body>
<!-- https://jqueryui.com/sortable/#connect-lists -->
<!-- https://codepen.io/vtno/pen/MXmpoy -->
 
    <!-- HIDDEN -->
    <div class='list-hidden'></div>
 
    <!-- REAL -->
    <div class="list">
        <div class="list-item" draggable=true>
            <div class="content">
                <input type="checkbox"/>
                <span>HELLO!!!! 001</span>
            </div><!-- content -->
        </div><!-- list-item -->
        <div class="list-item" draggable=true>
            <div class="content">
                <input type="checkbox"/>
                <span>WORLD!!!! 002</span>
            </div><!-- content -->
        </div><!-- list-item -->
        <div class="list-item" draggable=true>
            <div class="content">
                <input type="checkbox"/>
                <span>HELLO WORLD!!!! 003</span>
            </div><!-- content -->
        </div><!-- list-item -->
        <div class="list-item" draggable=true>
            <div class="content">
                <input type="checkbox"/>
                <span>!!!! 004</span>
            </div><!-- content -->
        </div><!-- list-item -->
    </div><!-- list -->
 
</body>
</html>
cs

 

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@charset "utf-8";
.list {
    background: #4286f4;
    width: 500px;
    padding: 1rem;}
 
.list-hidden {
    position: absolute;
    top: -9999px;
    left: -9999px;}
 
.ghost { opacity: .4; }
 
.list-item {
    background: #8bb3f4;
    padding: 0.5rem;
    border: 1px solid white;
    margin: 1px 0;}
 
.placeholder {
    display: none;
    background: #303742;
    pointer-events: none;}
cs

 

main.js

1
2
3
4
5
6
import { sortable } from "./script.js";
 
const list = document.querySelector('.list')
 
////실행///
sortable(list);
cs

getPosition.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// let dragIndex, dragSource
export function getMouseOffset(evt) {
    const targetRect = evt.target.getBoundingClientRect()
    const offset = {
        x: evt.pageX - targetRect.left,
        y: evt.pageY - targetRect.top
    }
    return offset
}//getMouseOffset
 
export function getElementVerticalCenter(el) {
    const rect = el.getBoundingClientRect()
    return (rect.bottom - rect.top) / 2
}//getElementVerticalCenter
cs

script.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import {getMouseOffset,getElementVerticalCenter} from './getPostion.js'
 
export function sortable(rootEl) {
    let dragEl;
 
    // Making all siblings movable
    const all_list_item = rootEl.children; 
    [].slice.call(all_list_item).forEach(list_item =>{
        list_item.draggable = true;
    });
 
    // Sorting starts
    rootEl.addEventListener('dragstart'function (evt) {
        dragEl = evt.target; // Remembering an element that will be moved
 
        // Subscribing to the events at dnd
        rootEl.addEventListener('dragover', _onDragOver);
        rootEl.addEventListener('dragend', _onDragEnd);
 
        setTimeout(()=>{
            // If this action is performed without setTimeout, then
            // the moved object will be of this class.
            dragEl.classList.add('ghost');
        }, 0)
    }); //dragstart
 
    // Function responsible for sorting
    function _onDragOver(evt) {
        evt.preventDefault();
        evt.dataTransfer.dropEffect = 'move';
 
        const target = evt.target;
 
        // console.log(
        // `내가 드래그한건 ${dragEl.innerText}
        // 드래그 하며 지나치는 타겟은 ${target.innerText}`);
 
        if (target && target !== dragEl && target.tagName == 'DIV') {
            // Sorting
            const offset = getMouseOffset(evt)
            const middleY = getElementVerticalCenter(evt.target)
            const targetIDX = [].indexOf.call(all_list_item,target);
            if (offset.y > middleY) {
                targetIDX !== -1 && rootEl.insertBefore(dragEl, target.nextSibling);
            } else {
                targetIDX !== -1 && rootEl.insertBefore(dragEl, target);
            }
        }//if
    }//_onDragOver
 
    // End of sorting
    function _onDragEnd(evt) {
        evt.preventDefault();
        dragEl.classList.remove('ghost');
        rootEl.removeEventListener('dragover', _onDragOver);
        rootEl.removeEventListener('dragend', _onDragEnd);
    }//_onDragEnd
}//sortable
cs