일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- vue 컴포저블 함수
- reflow
- vuedraggable
- cloud firestore id auto increment
- d3 지도 타입스크립트
- nuxt universal rendering
- Learning React
- d3 지도
- 다자 이해관계자 모델
- 화살표 함수 중괄호
- $fetch
- 화살표 함수 {}
- 함수형 프로그래밍
- vue draggable 차트 안나옴
- last-modified
- git
- commonjs와 ecmascript modules(esm)
- usefetch
- d3 지도 확대/축소
- component is already mounted please use $fetch instead.
- d3 지도 툴팁
- vue3 drag and drop
- 참조형 props의 default
- repaint
- firebase id 자동
- ecmascript modules(esm)
- ToDo
- vue composable 함수
- 인터넷 거버넌스
- 참조형 default
- Today
- Total
빵 입니다.
Drag and Drop 해보자(w/ 라이브러리) 본문
D&D 기능을 구현했습니다.
직접 만들어볼까~ 하다가 구현해야 할 기능 공수에 비해 시간적 여유가 부족해서 라이브러리를 사용했는데요.
3가지 꽤괜 라이브러리를 찾았습니다.
📌 SortableJS VS VueDraggable VS Shopify Draggable
SortableJS와 Shopify Draggable는 Vanilla JS 기반이고, VueDraggable는 Vue/Nuxt에 최적화되어 있습니다.
특징을 간략하게 소개하자면...
◾ SortableJS
animation, ghostClass 등 다양한 옵션을 제공하고, VueDraggable보다 커스텀의 범위가 더 자유롭습니다.(당연, Vue와 상관없이 작동함.)
다만, 배열을 자동으로 업데이트 해주지 않기 때문에 onEnd 이벤트 발생 시 splice()로 배열 직접 업데이트해야 합니다.
◾ VueDraggable
VueDraggable는 SortableJS 기반입니다.
SortableJS과 마찬가지로 animation, ghostClass 등 다양한 옵션을 제공하고, 커스텀이 가능합니다.
SortableJS와 가장 큰 다른 점은 VueDraggable은 v-model을 지원하기 때문에 별도로 splice()를 사용하지 않아도 배열이 자동으로 업데이트됩니다.
SortableJS 기반이기 때문에 옵션에 대한 가이드도 SortableJS를 확인하시면 됩니다.
https://github.com/SortableJS/Sortable#event-object-demo
◾ Shopify Draggable는
Shopify Draggable는 Draggable 기능을 찾다가 알게 된 라이브러리인데요.
드래그한 아이템 복제 및 스타일링 가능하지만, animation이 기본 지원되지 않기 때문에 CSS로 직접 구현해야 합니다.
위 두 라이브러리와 가장 큰 다른 점은 Draggable 되는 건 똑같으나 Swap 방식으로 자리가 변경됩니다.
순서 변경이 아니라 자리 교체가 됩니다.
그럼 여기서 잠깐.
Draggable과 Swap의 차이점은?
📌 Draggable 기능 VS Swap 기능
Draggable은 순서를 변경한다고 생각하면 됩니다.
아이템과 아이템 사이에 다른 아이템를 끌어다 넣을 수 있습니다.
* 빨간색은 내가 잡고 있는 것(grab)
* 초록 화살표는 내가 이동하고 싶은 위치


Swap은 아이템 교체입니다.
아이템을 끌어다 교체할 아이템에 올리면 두 아이템의 위치가 교체됩니다.
* 빨간색은 내가 잡고 있는 것(grab)
* 초록색은 이동하고 싶은 위치


📌 이슈 사항
각 아이템엔 여러 차트 데이터(ChartJS)가 들어가 있는데, 아이템을 드래그할 때 차트가 노출되지 않는 이슈를 발견했습니다.
다행히 해결 방법을 찾았습니다.
ChartJS는 <canvas />에 차트를 그리는데, 드래그가 시작될 때 해당 <canvas />의 내용을 가져와서 복제된 드래그 아이템의 canvas에 다시 그리면 됩니다.
그러나 산 넘어 산이라고...
차트는 잘 그려지는데, 아이템 내에서 사용하는 UI 프레임워크 컴포넌트 내의 내용이 노출되지 않는 이슈도 있었습니다.
디자인된 scroll 영역 사용을 위해 Quasar 프레임워크의 컴포넌트를 사용하고 있는데, 아이템을 드래그할 때 해당 컴포넌트 내의 내용이 노출되지 않았습니다.
차트는 canvas로 처리하고 프레임워크는 다르게 분기해서 처리할까 하다가 한번에 처리할 수 있는 방법을 생각해 보았습니다.
그리고 해결 방법을 찾았습니다.
아이템 내용을 이미지화 해서 보여주는 방법인데요.
SortableJS에서 제공하는 onClone 메서드를 이용하면 드래그를 시작한 뒤 원본 아이템 요소에 접근이 가능합니다.
htmlToImage 라이브러리를 이용해 원본 아이템(DOM)을 이미지화하고, 해당 이미지를 복제된 드래그 아이템에 append 합니다.
📌 구현
저는 Vue3 기반의 프로젝트를 운영하기 때문에 VueDraggable을 설치했습니다.
🧿 요구사항
◾ 아이템간 순서 변경(Swap X)
👉🏻 VueDraggable 사용으로 충족
◾ 드래그 중인 아이템의 잔상으로 보여지지 않고, 온전한 아이템 형태로 보여져야 한다.
👉🏻 dragOptions의 forceFallback: true, fallbackOnBody: true 설정

◾ 드래그 시 자연스럽게 순서 교체가 되어야 한다.(animation 효과)
👉🏻 기본 제공
🧿 드래그 옵션
const dragOptions = {
draggable: 'drag-item', // 드래그 대상 item의 클래스 명시
animation: 200, // 정렬 시 항목을 이동하는 애니메이션 속도
ghostClass: 'origin-item', // 드롭될 영역 아이템의 클래스
dragClass: 'dragging-item', // 드래그 중인 아이템의 클래스
forceFallback: true,
// true를 설정하면 HTML5에서 제공하는 기본 Drag and Drop 기능을 사용하지 않고,
// JS 기반의 드래그 시스템을 강제로 사용하게 됩니다.
fallbackOnBody: true
// 복제된 DOM 요소를 문서 본문에 추가합니다.
// 👉🏻 드래그 중인 아이템을 <body> 태그 안에 복제하여 Append 합니다.
}
❗ 참고
forceFallback를 false로 설정하면 HTML5에서 제공하는 기본 Drag and Drop 기능을 사용하기 때문에,
fallbackOnBody: true로 설정해도 드래그 중인 아이템을 복제(온전한 형태)하지 않습니다.
* HTML5에서 제공하는 기본 Drag and Drop 기능은 드래그 중인 아이템의 온전한 형태를 보여주지 않고, 상하에 Opacity 효과를 줍니다.
🧿 HTML
<draggable
v-model="items"
v-bind="dragOptions"
tag="div"
class="drag-wrap"
item-key="name"
@clone="onClone"
>
<template #item="{ element }">
<div class="drag-item">{{ element }}</div>
</template>
</draggable>
🧿 드래그 이벤트
const onClone = (event: SortableEvent) => {
setTimeout(() => {
const target = event.item
const draggingItem = document.querySelector('.dragging-item')
if (draggingItem) {
draggingItem.innerHTML = ''
const img = new Image()
htmlToImage
.toPng(target)
.then((dataUrl) => {
img.src = dataUrl
draggingItem.appendChild(img)
})
.catch((error) => {
console.error(error)
})
}
}, 0)
}
📌 전체 코드
<script setup lang="ts">
import draggable from 'vuedraggable'
import type { SortableEvent } from 'sortablejs'
import * as htmlToImage from 'html-to-image'
const items = ref<{ id: number; name: string; activation: boolean }[]>([])
const dragOptions = {
draggable: 'drag-item',
animation: 200,
ghostClass: 'origin-item',
dragClass: 'dragging-item',
forceFallback: true,
fallbackOnBody: true
}
const onClone = (event: SortableEvent) => {
setTimeout(() => {
const target = event.item
const draggingItem = document.querySelector('.dragging-item')
if (draggingItem) {
draggingItem.innerHTML = ''
const img = new Image()
htmlToImage
.toPng(target)
.then((dataUrl) => {
img.src = dataUrl
draggingItem.appendChild(img)
})
.catch((error) => {
console.error(error)
})
}
}, 0)
}
const onDragEnd = () => {
return items.value
}
</script>
<template>
<draggable
v-model="items"
v-bind="dragOptions"
tag="div"
class="drag-wrap"
item-key="name"
@clone="onClone"
>
<template #item="{ element }">
<div class="drag-item">{{ element }}</div>
</template>
</draggable>
</template>
👀 참고 사이트
SortableJS
https://sortablejs.github.io/Sortable/
VueDraggable
https://sortablejs.github.io/Vue.Draggable/
Shopify Draggable
https://shopify.github.io/draggable/
차트 canvas 복붙
https://codesandbox.io/p/sandbox/vue-template-forked-q3lzcs?file=%2Fsrc%2Fcomponents%2FDraggableGrid.vue%3A86%2C5
'프론트엔드 > Vue' 카테고리의 다른 글
Composable 함수 (0) | 2025.03.21 |
---|---|
D3.js 지도 그리기(w/ 확대ㆍ축소 기능과 툴팁) (0) | 2025.03.11 |
참조형 props의 default 값 설정하기 (0) | 2025.01.16 |
Chart.js 툴팁 레이블 색상 변경, 레전드 레이블 색상 변경 (0) | 2024.07.31 |
Chart.js 축 font-size 조절 (0) | 2024.07.30 |