Vue3問(wèn)題:如何實(shí)現(xiàn)組件拖拽實(shí)時(shí)預(yù)覽功能?
本文主要內(nèi)容分三部分,第一部分是需求分析,第二部分是實(shí)現(xiàn)步驟,第三部分是問(wèn)題詳解。
如果您只需要解決問(wèn)題,請(qǐng)閱讀第一、二部分即可。
如果您有更多時(shí)間,進(jìn)一步學(xué)習(xí)問(wèn)題相關(guān)知識(shí)點(diǎn),請(qǐng)閱讀至第三部分。
1、需求分析
實(shí)現(xiàn)一個(gè)CMS內(nèi)容管理系統(tǒng),在后臺(tái)進(jìn)行內(nèi)容編輯,在官網(wǎng)更新展示內(nèi)容。
關(guān)于后臺(tái)的編輯功能,大致分為兩部分:組件拖拽預(yù)覽、組件內(nèi)容編輯實(shí)時(shí)預(yù)覽。
對(duì)于組件拖拽預(yù)覽,用戶(hù)可以在含有各種功能組件的列表中,選擇需要的組件進(jìn)行拖拽。將組件拖拽到預(yù)覽畫(huà)布中后,可以在畫(huà)布中預(yù)覽組件的內(nèi)容。
對(duì)于組件內(nèi)容編輯實(shí)時(shí)預(yù)覽,用戶(hù)可以點(diǎn)擊編輯按鈕,顯示對(duì)應(yīng)組件的內(nèi)容編輯信息。當(dāng)修改組件內(nèi)容時(shí),在畫(huà)布中可以實(shí)時(shí)預(yù)覽組件的變化。
2、實(shí)現(xiàn)步驟
(1)關(guān)于拖拽組件庫(kù)的選擇
關(guān)于拖拽組件庫(kù),在github上有很多,最熱門(mén)的當(dāng)屬vuedraggable這個(gè)庫(kù)。
它基于Sortable.js,擁有完整的中文文檔,所以很多朋友在做拖拽功能時(shí),都會(huì)優(yōu)先考慮它。
但是,我在使用的過(guò)程中,在組件拖拽、取消組件拖拽這里,遇到了一些小問(wèn)題。不知道是我操作的問(wèn)題,還是庫(kù)本身存在BUG,所以最終沒(méi)有選用它。
于是我發(fā)現(xiàn)了,一個(gè)更加好用的拖拽庫(kù)vue-draggable-next。
它的出現(xiàn)是參考了vuedraggable這個(gè)庫(kù),所以說(shuō)很多用法很相似,但是使用起來(lái)較之更加友善??偨Y(jié)為一個(gè)詞,自由。
下面我們的拖拽功能實(shí)現(xiàn),就是利用了vue-draggable-next這個(gè)庫(kù)。
如果你對(duì)拖拽底層原理感興趣,并且有空余時(shí)間,勞請(qǐng)閱至第三部分拖拽原理總結(jié)。
(2)拖拽功能代碼實(shí)例
模版和邏輯中代碼都分為了三部分:組件列表、預(yù)覽畫(huà)布、內(nèi)容編輯。
布局時(shí),樣式根據(jù)需求自定義,此處只是提供了功能的基本邏輯實(shí)現(xiàn)。
使用時(shí),關(guān)于拖拽組件的需求,根據(jù)文檔中提供的屬性和事件的描述,靈活配置完善。
vue-draggable-next庫(kù)文檔地址:https://github.com/anish2690/vue-draggable-next。
安裝依賴(lài):
npm install vue-draggable-next
//or
yarn add vue-draggable-next
模板代碼:
<template>
<div style="display: flex;cursor: pointer;">
<!-- 組件列表 左側(cè) -->
<div style="width:30vw;padding:30px;height:300px;border: 1px solid #000;text-align: center">
<h1>組件列表</h1>
<VueDraggableNext :list="componentNameList" :group="{ name: 'people', pull: 'clone', put: false }" :sort="false">
<div v-for="element in componentNameList" :key="element.name">
<div style="padding: 10px;background: #ccc;margin-bottom: 10px">
<el-button> {{ element.name }}</el-button>
</div>
</div>
</VueDraggableNext>
</div>
<!-- 預(yù)覽畫(huà)布 中間 -->
<div style="width:30vw;padding:30px;height:300px;border: 1px solid #000;text-align: center">
<h1>預(yù)覽畫(huà)布</h1>
<VueDraggableNext :list="componentList" group="people" :sort="false">
<div v-for="(element, index) in componentList" :key="element.name">
<div>
<el-button @click="changeComponent(element)" size="small">編輯</el-button>
<el-button @click="deleteComponent(index)" size="small">刪除</el-button>
</div>
<component :is="element.component" :details="element.details"></component>
</div>
</VueDraggableNext>
</div>
<!-- 內(nèi)容編輯 右側(cè) -->
<div style="width:30vw;padding:30px;height:300px;border: 1px solid #000;text-align: center">
<h1>內(nèi)容編輯</h1>
<div v-for="(nowDetails, index) in nowComponentDetail.details" :key="index">
{{ nowDetails.key }}: <el-input clearable v-model="nowDetails.value" />
</div>
</div>
</div>
</template>
邏輯代碼:
<script setup>
import {markRaw, reactive, ref, watch, nextTick} from "vue";
// 引入需要拖拽的組件
import About from "@/views/AboutView.vue"
// 引入拖拽庫(kù)
import { VueDraggableNext } from "vue-draggable-next";
// 組件列表數(shù)據(jù)
const componentNameList = [
{
// 組件ID
componentId:0,
// 組件名
name:'關(guān)于組件',
// 組件描述
description: '關(guān)于組件',
// 組件信息
component: markRaw(About),
// 組件需要編輯內(nèi)容
details: About.props.details.default
},
{
componentId:1,
name:'關(guān)于組件1',
description: '關(guān)于組件1',
component:markRaw(About),
details: About.props.details.default
},
];
// 預(yù)覽畫(huà)布數(shù)據(jù)
let componentList = reactive([]);
watch(componentList, () => {
console.log(componentList, 'componentList')
})
// 畫(huà)布中刪除的點(diǎn)擊事件
const deleteComponent = (index) => {
componentList.splice(index, 1);
};
// 內(nèi)容編輯數(shù)據(jù)
let nowComponentDetail = ref({});
// 畫(huà)布中編輯的點(diǎn)擊事件
const changeComponent = (element) => {
nowComponentDetail.value = element;
};
</script>
(3)功能組件代碼實(shí)例(參考)
此處提供了組件列表中,任意功能組件的編寫(xiě)實(shí)例,組件的具體功能根據(jù)需求自定義。
特別注意,組件Props中details對(duì)象屬性的結(jié)構(gòu)寫(xiě)法,要靈活應(yīng)用。
模版代碼:
<template>
<div style="padding: 10px;background: pink;margin-bottom: 10px">
{{ props.details.content }}
</div>
</template>
邏輯代碼:
<script setup>
import { defineProps } from "vue";
const props = defineProps({
details: {
type: Object,
default: {
content: {
key: '內(nèi)容',
value: "我是About",
},
}
}
})
</script>
3、問(wèn)題詳解
(1)拖拽實(shí)現(xiàn)的底層原理
拖拽的實(shí)現(xiàn)原理可以簡(jiǎn)單描述為以下幾個(gè)步驟:
- 觸發(fā)拖拽:通過(guò)監(jiān)聽(tīng)鼠標(biāo)或觸摸事件,當(dāng)用戶(hù)按下鼠標(biāo)左鍵或觸摸屏幕時(shí),表示開(kāi)始拖拽操作。
- 記錄拖拽信息:在拖拽開(kāi)始時(shí),記錄拖拽起始位置(鼠標(biāo)或觸摸點(diǎn)的坐標(biāo)),以及需要拖拽的元素的初始位置(相對(duì)于文檔的坐標(biāo))。
- 更新拖拽元素位置:在拖拽過(guò)程中,通過(guò)監(jiān)聽(tīng)鼠標(biāo)移動(dòng)或觸摸滑動(dòng)事件,計(jì)算鼠標(biāo)或觸摸點(diǎn)的當(dāng)前位置,并根據(jù)鼠標(biāo)或觸摸點(diǎn)的位置變化,更新拖拽元素的位置。這可以通過(guò)修改元素的 CSS 屬性(如 left 和 top)或使用 CSS 變換(如 translateX 和 translateY)來(lái)實(shí)現(xiàn)。
- 處理拖拽結(jié)束:當(dāng)用戶(hù)釋放鼠標(biāo)左鍵或觸摸結(jié)束時(shí),表示拖拽結(jié)束。此時(shí)可以執(zhí)行一些操作,如更新拖拽元素的最終位置、觸發(fā)事件或回調(diào)函數(shù)等。
以下是拖拽實(shí)現(xiàn)的基本原理代碼實(shí)例:
// 獲取拖拽元素
const draggableElement = document.getElementById('draggable');
// 記錄拖拽起始位置和拖拽元素的初始位置
let startX, startY, initialX, initialY;
// 監(jiān)聽(tīng)鼠標(biāo)按下事件
draggableElement.addEventListener('mousedown', dragStart);
draggableElement.addEventListener('touchstart', dragStart);
// 監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件
document.addEventListener('mousemove', drag);
document.addEventListener('touchmove', drag);
// 監(jiān)聽(tīng)鼠標(biāo)釋放事件
document.addEventListener('mouseup', dragEnd);
document.addEventListener('touchend', dragEnd);
// 拖拽開(kāi)始事件處理程序
function dragStart(event) {
event.preventDefault();
if (event.type === 'touchstart') {
startX = event.touches[0].clientX;
startY = event.touches[0].clientY;
} else {
startX = event.clientX;
startY = event.clientY;
}
initialX = draggableElement.offsetLeft;
initialY = draggableElement.offsetTop;
}
// 拖拽事件處理程序
function drag(event) {
event.preventDefault();
if (event.type === 'touchmove') {
const currentX = event.touches[0].clientX - startX;
const currentY = event.touches[0].clientY - startY;
draggableElement.style.left = initialX + currentX + 'px';
draggableElement.style.top = initialY + currentY + 'px';
} else {
const currentX = event.clientX - startX;
const currentY = event.clientY - startY;
draggableElement.style.left = initialX + currentX + 'px';
draggableElement.style.top = initialY + currentY + 'px';
}
}
// 拖拽結(jié)束事件處理程序
function dragEnd() {
// 執(zhí)行拖拽結(jié)束后的操作
}
(2)關(guān)于vue-draggable-next 庫(kù)的功能總結(jié)
vue-draggable-next 庫(kù)特點(diǎn)和功能的補(bǔ)充說(shuō)明:
- 基于 Vue 3:vue-draggable-next 是為 Vue.js 3 版本設(shè)計(jì)的拖拽庫(kù),充分利用了 Vue 3 的響應(yīng)式系統(tǒng)和組合式 API。
- 輕量級(jí)和簡(jiǎn)單易用:該庫(kù)的代碼量較少,易于理解和使用。它提供了一組簡(jiǎn)單的指令和組件,可以輕松地將拖拽功能集成到 Vue.js 應(yīng)用程序中。
- 支持多種拖拽模式:vue-draggable-next 支持多種拖拽模式,包括自由拖拽、列表拖拽、網(wǎng)格拖拽等。您可以根據(jù)需求選擇適合的拖拽模式。
- 自定義拖拽樣式和行為:該庫(kù)允許您自定義拖拽元素的樣式和行為。您可以定義拖拽時(shí)的樣式、占位符元素、限制拖拽的范圍等。
- 事件和回調(diào)支持:vue-draggable-next 提供了一組事件和回調(diào)函數(shù),可以在拖拽過(guò)程中監(jiān)聽(tīng)和處理各種事件。例如,您可以監(jiān)聽(tīng)拖拽開(kāi)始、拖拽結(jié)束、拖拽元素排序等事件,并執(zhí)行相應(yīng)的操作。
- 支持觸摸設(shè)備:該庫(kù)對(duì)觸摸設(shè)備提供了良好的支持,可以在移動(dòng)設(shè)備上實(shí)現(xiàn)流暢的拖拽交互。