拖拽库github地址:github.com/bpuns/easy-…
拖拽库说明文档:bpuns.github.io/easy-dnd-do…
前言
一年前,我接到一个需求,需要开发一个低代码平台,调研了下市面上适用于React的拖拽库,本人都感觉使用起来过于复杂,并且体积都挺大的,我就想用原生的拖拽Api,但是原生Api在复杂场景下(无限嵌套,嵌套规则复杂),是不可用的,如果想了解具体遇到的问题,可以看 巨详细!帮你踩坑HTML5拖拽api(下) 这篇文章。因此,我就想基于 Drag and Drop api 封装的一套可以应用与复杂场景下的拖拽库
并且这个库最好不要和任何的前端框架绑定,这样方便移植和扩展,因为是自己封装的,因此可以掌握所有实现细节,开发过程中如果遇到了性能瓶颈,也能最快的找到解决方案,而且我也没完整的发布过一个开源库,正好学习下整个过程
拖拽库实现思路
拖拽上下文
页面中可能会有很多可以拖拽的区域,并且拖拽区域之间互不影响,那么就需要为每个拖拽区域创建一个上下文,那么就可以提供一个方法,用于上下文的创建,为了到时候方便扩展,可以传一个配置项作为参数。
interface ProviderConfig { }
/**
* 创建拖拽作用域下的基本数据
* @param {*} config 配置项
* @returns
*/
function createProvider(config?: ProviderConfig): Context;
拖拽类型
在现实生活中,我们之所以知道一个物品可以放到另外一个物品中,比如食物剩余的包装袋需要放到垃圾桶中,电脑需要放到电脑包里。所以抽到代码中,需要给可以拖拽的dom元素添加一个类型,给需要放置的元素设置允许放置的类型有哪些
// 创建拖拽对象
const drag = useDrag({
config: {
type: '类型'
}
})
// 创建放置对象
const drop = useDrop({
config: {
acceptType: [ '类型' ]
}
})
canDrop
光有类型不够,还需要给Drop添加一个方法,判断当前是否允许放置,比如当前Drop元素内部可能只能放一个元素
const drop = useDrop({
config: {
canDrop: () => true | false
}
})
Drag api设计
Drag Api的事件需要和h5事件保持一致
const drag = useDrag({
config: {
type: '类型',
context
},
dragStart(){
console.log('开始拖拽')
},
drag(){
console.log('拖拽中')
},
dragEnd(){
console.log('结束拖拽')
}
})
Drop api设计
Drop Api的事件需要和h5事件保持一致
const drop = useDrop({
config: {
context,
// 判断是否允许拖拽
canDrop: () => true | false,
// 使用Set替换数组
acceptType: new Set([ '类型' ]),
},
dragEnter(){
console.log('拖拽元素进入')
},
dragOver(){
console.log('拖拽元素在当前元素范围内移动')
},
dragLeave(){
console.log('拖拽元素离开')
},
drop(){
console.log('拖拽元素放置')
}
})
Vue 与 React 支持
因为在Vue和React中都有上下文的概念,那就好办了,提供个组件包裹需要拖拽的元素,用于传递上下文
- Vue
<template>
<dnd-provider>
<component />
</dnd-provider>
</template>
<script setup lang="ts">
import { DndProvider } from 'easy-dnd/vue'
</script>
- React
import { DndProvider } from 'easy-dnd/react'
function Example1() {
return (
<DndProvider>
<Component />
</DndProvider>
)
}
因为拖拽和放置都需要作用在dom上,那么可以Drag与Drop都可以提供个方法,用于dom的绑定
- Vue
<template>
<div :ref="drag.dragRef" class="a">
盒子A
</div>
</template>
<script setup lang="ts">
import { useDrag } from 'easy-dnd/vue'
const drag = useDrag({
config: {
type: 'A'
},
dragStart: () => {
console.log('A 开始拖拽')
},
dragEnd: () => {
console.log('A 结束拖拽')
}
})
</script>
- React
import { useDrag } from 'easy-dnd/react'
function A() {
// 创建拖拽实例
const drag = useDrag(() => ({
config: {
type: 'A'
},
// 拖拽开始的回调
dragStart: () => {
console.log('A 开始拖拽')
},
// 拖拽结束的回调
dragEnd: () => {
console.log('A 结束拖拽')
}
}))
return (
<div
// 与dom绑定
ref={drag.dragRef}
>
盒子A
</div>
)
}
编写打包脚本
随便找个打包工具,我这个库使用的是rollup
import { rollup } from 'rollup'
import typescript from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'
rollup({
// 入口配置
input: entry,
// 输出配置
output: {},
// 插件配置
plugins: [
// 代码压缩
terser({
ie8: false,
compress: true,
format: {
quote_style: 1
}
}),
// typescript支持
typescript()
],
// 配置要排除哪些文件,
external: []
})
详细配置文件可以看这里
使用vuePress编写Doc并发布到GitHub Pages上
一个开源库,一定需要一个说明文档,那么这里我使用的是 VuePress,官方的介绍如下
VuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown在新窗口打开 来书写内容(如文档、博客等),然后 VuePress 会帮助你生成一个静态网站来展示它们
文档编写完成之后,如果你有自己的服务器,可以把静态资源部署到自己的服务器上,像我这样的白嫖党,可以选择发布到 GitHub Pages 上,可以看Github的官方文档来部署你的说明文档
部署完成的效果就是下面这样啦
编写使用demo
打包脚本和文档有了之后,就需要准备demo了,因为有些人是不会看文档的,直接看demo基本上就可以很快的把一个库的使用方法学会,因此我准备了在 纯原生环境下使用,在Vue下使用,在React下使用的代码示例,点击这里 查看
npm发布
我选择再编写了个js脚本,帮我把package.json,README.md文件全部拷贝到打包文件夹下,这样我就不用编写 .npmignore文件了
- scripts/build.js
import { join } from 'node:path'
import process from 'node:process'
import { rollup } from 'rollup'
......
async function buildPackage(_package) {
console.log(`${name}:开始构建`)
// 构建代码 ......
console.log(`${name}:构建结束`)
}
; (async function () {
// 清空原有的输出路径
await fs.emptyDir(BUILD_PATH)
// 打包
await Promise.allSettled(packages.map(buildPackage))
// 复制需要提交到npm上的配置文件
copyNpmConfig()
})()
function copyNpmConfig() {
// 复制package.json
const desJson = JSON.parse(fs.readFileSync(join(PROJECT_PATH, 'package.json'), 'utf-8'))
const newPackageJson = {
name: desJson.name,
version: desJson.version,
keywords: desJson.keywords,
author: desJson.author,
repository: desJson.repository
}
fs.writeFileSync(join(BUILD_PATH, 'package.json'), JSON.stringify(newPackageJson, null, 2))
console.log('package.json完成')
// 复制README.md
fs.copyFileSync(
join(PROJECT_PATH, 'README.md'),
join(BUILD_PATH, 'README.md')
)
console.log('复制README.md完成')
}
最后就是调用推送命令发布。当然,如果你实在懒的话,也可以编写node脚本帮你提交
$ npm publish
最后
这个拖拽库是真实可用的,并不是整活,它已经用在了我私人项目中,并且已经使用了快1年多的时间,目前已经趋于稳定,欢迎大家使用并提出宝贵意见!!
后续展望
目前拖拽没有动画效果,并且还有基于这个库横向/纵向/网格拖拽的组件可以直接使用,后续可以加上