关于draggable:拖拽组件-dndkit

dnd-kit 我称之为拖拽组件中的王者 git链接文章地址 用过react-dnd和react-beautiful-dnd都没有dnd-kit组件灵便以及裸露的数据多(也可能是我钻研的不够深) 应用场景及可实现性能实现画布类拖拽 元素是否被拖拽元素区域是否被拖入(data数据利用)元素区域是否被拖出(data数据利用)元素区域可接管哪些类型的元素拖入(data数据利用)定位元素拖拽(碰撞算法内可做解决)拖拽类组件大差不差,有dragContext、drop、drag组件,通俗了解context实现数据共享,drop为可被拖拽的区域,drag为可被拖拽的元素应用形式看文章,不做解说 <DndContext // dndkit自带碰撞算法,不传则默认为rectIntersection // collisionDetection={rectIntersection} // collisionDetection={pointerWithin} /** * 碰撞算法优化 * 自定义碰撞算法 */ collisionDetection={customCollisionDetectionStrategy} ‘ /** * 传感器配置 */ sensors={sensors} onDragStart={res => { console.log({ onDragStartMove: res }); // 本人打印看吧 }} onDragEnd={(res: DragEndEvent) => { console.log({ onDragEnd: res }); // 拖拽完结(会返回拖拽的以后元素信息及抛弃(搁置)到指定地位的元素信息,可能为null) }}> {children} <DragOverlay> // 自定义的标识 draggingId - 以后正在被拖拽的元素id {draggingId ? ( <div style={{ // 被拖拽元素款式 }}></div> ) : null} </DragOverlay><DndContext>碰撞算法collisionDetection自定义碰撞算法,dndkit会提供一些数据,用户能够在数据根底上进行解决 const customCollisionDetectionStrategy = (rects) => { /** * active 以后拖拽元素及其data * collisionRect 碰撞元素data * pointerCoordinates 以后指针坐标 * droppableContainers 所有dropabble对象->arr data: {current: {…}} id: "" key: "Droppable-0", // dndkit给对象减少的字段,可用于排序,数值越大代表以后元素嵌套最深(最上层) node: {current: '以后元素'} rect: {current: bottom: 0, height: 96, left: 0, right: 0, top: 0, width: 1920} */ const { active, collisionRect, pointerCoordinates, droppableContainers } = rects; // ... 数据处理,需返回碰撞到的元素数组(进行排序)第一个为以后碰撞到的元素 return arr; }传感器 const sensors = useSensors(useSensor(MouseSensor));// MouseSensor dndkit提供的传感器,默认是应用所有传感器,我这里只应用了mouse传感器,碰撞检测返回的数据可能和以后传感器有关联?我这里没做尝试,但碰撞算法的确是依靠于鼠标地位做的解决应用drag或drop时可带额定参数在应用别的拖拽组件时,很多数据处理会很麻烦,dndkit提供了data形式,能够在所有办法里或碰撞算法里返回这些data,不便数据处理 ...

April 15, 2022 · 1 min · jiezi

vue2-拖动排序-vuedraggable组件

一、安装插件 npm install -D vuedraggable二、在需要排序的界面中引入组件 <script>import draggable from 'vuedraggable'export default { name: 'HelloWorld', components: { draggable },三、在模板中使用 <draggable v-model="groups" @chang="change" @start="start" @end="end" :move="move"> <div v-for="(item, index) in groups" :key=index> item {{item}} </div></draggable>四、实现change、start、end、move方法,v-model绑定的数据就是排序后的数据,可以传递给后台修改数据库数据 methods: { change (event) { console.log('change', event) }, start (event) { console.log('start', event) }, end (event) { console.log('end', event, this.groups) }, move (event, orgin) { console.log('move', event, orgin) }}五、完整代码,参考网址 <template> <div> 排序 <draggable v-model="groups" @chang="change" @start="start" @end="end" :move="move"> <!-- <transition-group> --> <div v-for="(item, index) in groups" :key=index> item {{item}} </div> <!-- </transition-group> --> </draggable> </div></template><script>import draggable from 'vuedraggable'export default { name: 'HelloWorld', components: { draggable }, data () { return { groups: [ 1, 2, 3, 4, 5 ] } }, methods: { change (event) { console.log('change', event) }, start (event) { console.log('start', event) }, end (event) { console.log('end', event, this.groups) }, move (event, orgin) { console.log('move', event, orgin) } }}</script><style scoped></style>https://github.com/SortableJS... ...

August 8, 2019 · 1 min · jiezi

原生JS快速实现拖放drag-and-drop效果

拖放是很常见的一种交互效果,很多时候我们都会借助于第三方的控件来实现,其实用原生js实现起来也非常的方便。接下来我们就用原生js和css快速实现这样的拖放效果: HTMLHTML的内容很简单,就是五个空的容器和一个可以被拖拽的元素: html: <body> <div class="droppable"> <div class="draggable" draggable="true"></div> </div> <div class="droppable"></div> <div class="droppable"></div> <div class="droppable"></div> <div class="droppable"></div></body>注意点:1. 容器的的class为droppable,用于接收被拖拽的元素,可被拖拽的元素class为draggable,同时设置draggable属性为true,表示该元素可以被拖拽。2. 默认情况下,只有图片、链接还有被选中的文字能被拖拽,其他元素需要设置draggable为true才能被拖拽。所以为了凸显draggable的用法,这里使用<div>而不是<image>来作为被拖拽的元素。CSS在实现样式的时候,除了实现静态的样式,一些过渡状态也需要增加样式以提升视觉体验:1. 元素被拖动的过程中增加边框等效果;2. 当元素被拖动到容器上方时,容器也应增加样式表明容器可以接收一个被拖拽的元素。 css: body { background-color: darksalmon;}.draggable { background-image: url('http://source.unsplash.com/random/150x150'); position: relative; height: 150px; width: 150px; top: 5px; left: 5px; cursor: pointer;}.droppable { display: inline-block; height: 160px; width: 160px; margin: 10px; border: 3px salmon solid; background-color: white;}.dragging { border: 4px yellow solid;}.drag-over { background-color: #f4f4f4; border-style: dashed;}.invisible { display: none;}注意点:1. 图片来源于https://source.unsplash.com/的随机图片;2. .dragging为draggable元素正在被拖动的状态,增加黄色border;3. .drag-over为draggable元素被拖动到容器上方时容器的状态,增加灰色虚线border。JS最后,我们需要通过js监听draggable和droppable的相关的事件。 ...

June 22, 2019 · 1 min · jiezi

【详】JS实现拖拽元素互换位置

写在前面的废话大家好,我是练习js时长接近两年半的个人练习生–李大雷算了,直接 鸡,你太美应用场景很多时候,我们需要让用户来自定义自己想要的菜单顺序,或者一些按钮的排序,那么这个时候,怎么给用户自定义顺序呢?拖拽无疑是最简单易懂的,因为玩过手机的都知道怎么拖动桌面的app来改变位置。那么要怎么做呢?最简单的方式肯定是用H5的拖放啦一些你需要了解的基础知识首先我们先来看看,这两个单词,drag–拖,drop–放,从这里就很容易看出来,这里的操作逻辑了。我们来看看有哪些事件可以给我们使用。被我们拖的元素(按住鼠标)ondragstart - 用户开始拖动元素时触发ondrag - 元素正在拖动时触发ondragend - 用户完成元素拖动后触发释放拖拽元素时触发的事件(松开鼠标)ondragenter - 当被鼠标拖动的对象进入其容器范围内时触发此事件ondragover - 当某被拖动的对象在另一对象容器范围内拖动时触发此事件ondragleave - 当被鼠标拖动的对象离开其容器范围内时触发此事件ondrop - 在一个拖动过程中,释放鼠标键时触发此事件我们来举例子说明一下<div class=“A” draggable=“true” ondragstart=“fn()” ondrag=“fn()” ondragend=“fn()"></div><div class=“B” ondragenter=“fn()” ondragover=“fn()” ondragleave=“fn()” ondrop=“fn()"></div>假设有div A和div B,当我按住A,开始拖动(A dragstart触发一次)(drag在你移动的时候不断触发),然后你经过了B(B触发了dragenter事件),然后你在B里疯狂摩擦(那就疯狂触发B的dragover,这句话怎么越读越不对劲?),然后你从B中出来(那就触发了B的dragleave),然后又进入B中(并且放开鼠标,那么就会触发B的drop和A的dragend);对于A来说,它的事件就前面3个,对于B来说,它的事件就是后面4个;A是攻,那么B就是受了。当然你也可以自攻自受,就像孟德尔的自交豌豆一样我们下面做的拖拽也是自攻自受的情况,因为你可能拖动A和B交换,也可能拖动B来和A交换位置。一些需要注意的点:如果只需要拖动外层div,请务必把子元素的draggable属性设置为false(如果子元素里面有默认可拖动元素,则需要把里面的可拖动元素的属性设置为false);不然会引起很多奇怪的现象(比如你想拖一个包含图片的div,结果只把图片拖出来了);链接和图片是默认可以拖动的;ondragenter和ondragleave可能会触发多次,如果你把A拖动到B里,B一个大div设置了enter和leave事件,但是它里面还有很多子div,那么每进出一个子div,都会触发一次enter和leave事件。开始操刀这个标题的cao是第一声。经过我们上面的一顿基础知识学习以后呢,我们就很容易想清楚这个实现逻辑。把A设置为可以拖动,当A拖动到B的时候,我们就互换A和B两个dom节点。至于怎么互换呢?我们可以直接调换两个节点的内容,或者我们调换两个dom节点的位置两种方法,这里我用的是第一种方法,第二种留给大家去尝试啦1. 我们先写一个大概的样式2. html结构如下<div class=“card” draggable=“true” ondrag=“handleDrag(event,this)” ondragstart=“handleDragStart(event,this)” ondragover=“handleDragOver(event,this)” ondragend=“handleDragEnd(event,this)” ondrop=“handleDrop(event,this)” ondragenter=“handleDragEnter(event,this)"> <span class=“card-name”> ${title} </span> <div class=“card-img”> <img src="${src}” draggable=“false” alt=”"> </div></div>3. 开始写逻辑,请仔细查看注释//先定义两个变量来保存源元素,以及目标元素,还有记录一下上次交换的dom//为什么要这一步呢?往后面看let fromDom = null, toDom = null, lastDom = null;//开始拖拽function handleDragStart(e, dom) { //开始拖拽的时候,把来源保存下来 fromDom = dom;}//拖拽中function handleDrag(){ console.log(‘如果你有业务逻辑的话,你可以写,但是我没有,抱歉’)}//拖到了另一个div中,这个时候的dom就是另一个元素了哦function handleDragEnter(e, dom) { //保存目标元素 toDom = dom; if(fromDom == lastDom){ //第一次调换 //为什么要分为几次调换位置呢? //想一下,如果我刚A和B调换了位置,那么就是B和A了但是此时我的鼠标还没有松开! //那么我又移动到C,那么互换的位置就是B和C了,但是其实我一开始拖拽的是A,我只想换AC只是不小心路过了B! //因此我们这里就要使用一个lastDom来记录上次路过交换的DOM,同时也要区分第几次调换。 swapDom(lastDom, toDom); //记录新的‘上一个dom’ lastDom = toDom; }else{ //这个防止enter多次触发 if(lastDom == toDom){return;} //第N+1次调换,要先把上一个div的东西还原回去,再跟第三个div互换 swapDom(fromDom,lastDom); swapDom(fromDom,toDom); //记录新的‘上一个dom’ lastDom = toDom; }}//在B中移动function handleDragOver(e, dom) { //默认无法把元素放置到其他元素当中,如果这个不写,无法交换div的innerHTML值,所以需要阻止默认事件,这一步很重要!! e.preventDefault();}//放手function handleDragEnd(e,dom){ //拖拽时松开鼠标就会会触发dragend事件,这个dom是拖拽的节点。 //重置toDom,下次拖拽就是新拖拽了,fromDom和lastDom会在dragStart的时候重置 toDom = null;}//有上面那个,其实这个可以省略了。function handleDrop(e, dom) { //只有在可放置的元素上面松开鼠标才会触发drop事件,所以这个dom是被放置的dom节点。 //重置toDom,下次拖拽就是新拖拽了,fromDom和lastDom会在dragStart的时候重置 toDom = null;}//交换dom内容function swapDom(from, to) { let temp = a.innerHTML; a.innerHTML = b.innerHTML; b.innerHTML = temp;}总结其实我们用不上那么多事件回调,主要的是 开始拖拽保存来源,进入目标时,保存目标,并且经过判断后交换,交换完以后,我们就把目标重置,完事逻辑比较简单,不过写动态css比较麻烦(因为我们需要一些css的效果来分辨哪个是被你拖动的,那个又互换了位置之类的,有比较好的用户体验),刚开始写经常傻傻分不清是来源dom还是目标dom辣鸡源码此部分适合新手玩家,因为自己只是随意写写,并没有写得很规范,希望大家不要学习!<!DOCTYPE html><html><head> <title></title> <style> body { margin: 0; } .box { display: flex; justify-content: flex-start; flex-wrap: wrap; } .card { flex: 1; min-width: 26%; max-width: calc(33.3% - 40px); height: 200px; margin: 30px 10px; position: relative; padding: 10px; box-shadow: 0 2px 5px 0 #999; border-radius: 5px; border: 2px dashed transparent; } .card-name { position: absolute; top: 10px; left: 10px; line-height: 20px; height: 20px; } .card-img { position: relative; padding-top: 20px; box-sizing: border-box; width: 100%; height: 100%; overflow: hidden; } .card-img img { width: 100%; height: 100%; } .dragging-over * { pointer-events: none; } </style></head><body> <div class=“box”> </div></body><script>const htmlArr = [ { title: ‘示例1-风景’, src: ‘https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2756575517,833879878&fm=200&gp=0.jpg’ }, { title: ‘示例2-风景’, src: ‘https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=328517395,2303970886&fm=26&gp=0.jpg’ }, { title: ‘示例3-风景’, src: ‘https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1554369684535&di=1c1dbfbd4545ad0a05e12cbbbfe3eeef&imgtype=0&src=http%3A%2F%2Fpic41.nipic.com%2F20140601%2F18681759_143805185000_2.jpg’ }, { title: ‘示例4-风景’, src: ‘https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1554369684534&di=d6e34af6fce6564f9df6c4eecc27d2ce&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2F4d086e061d950a7b9138ff1000d162d9f3d3c9d1.jpg’ },]let fromDom = null, toDom = null, lastDom = null;function handleDragStart(e, dom) { lastDom = fromDom = dom; dom.style.border = “2px dashed #999”; dom.style.opacity = 0.4;}function handleDrop(e, dom) { //只有在可放置的元素上面松开鼠标才会触发drop事件 console.log(‘drop’); dom.style.opacity = “”; fromDom = null; toDom = null;}function handleDragEnd(e,dom){ //拖拽时松开鼠标就会会触发dragend事件 console.log(’end’); dom.style.border = “2px dashed transparent”; dom.style.opacity = “”; toDom = null;}function handleDragEnter(e, dom) { toDom = dom; if(fromDom == lastDom){ //第一次调换 swapDom(lastDom, toDom); lastDom = toDom; }else{ //第N+1次调换,要先把上一个div的东西还原回去,再跟第三个div互换 //这个防止enter多次触发 if(lastDom == toDom){return;} swapDom(fromDom,lastDom); swapDom(fromDom,toDom); lastDom = toDom; }}function handleDragOver(e, dom) { //默认无法把元素放置到其他元素当中,所以需要prevent e.preventDefault(); e.dataTransfer.effectAllowed = “move”;}function swapDom(a, b) { //a和b的innerHTML互换 let temp = a.innerHTML; a.innerHTML = b.innerHTML; b.innerHTML = temp;}//生成dom结构function createDom(arr) { let body = document.getElementsByClassName(‘box’)[0]; let html = []; for (let i = 0, len = arr.length; i < len; i++) { html.push(template(arr[i].title, arr[i].src)); } body.innerHTML = html.join(’’);}//html模板,根据该模板动态生成dom节点function template(title, src) { let tpl = &lt;div class="card" draggable="true" ondragstart="handleDragStart(event,this)" ondragover="handleDragOver(event,this)" ondragend="handleDragEnd(event,this)" ondrop="handleDrop(event,this)" ondragenter="handleDragEnter(event,this)"&gt; &lt;span class="card-name"&gt; ${title} &lt;/span&gt; &lt;div class="card-img"&gt; &lt;img src="${src}" draggable="false" alt=""&gt; &lt;/div&gt; &lt;/div&gt; return tpl;}window.onload = function() { createDom(htmlArr);}</script></html>一些你可能不感兴趣的后语其实在没有这个drag之前,是用鼠标事件来实现的,这里就简单讲讲思路好了,懒得写了注册mousedown事件,在mousedown触发的时候注册mousemove事件,根据鼠标移动的位置来定位点击的dom,也就是让这个元素跟着你的鼠标移动(你的dom得绝对定位哦),这里比较麻烦的就是一些边界的判定,因为你的鼠标能到边界,但是你的div不一定可以(div面积比较大),而且根据业务不同,你也可能有不同的操作,这里因人而异啦~在mousedown里也注册mouseup事件,mouseup的作用就是把mousemove事件清空,因为要每一次鼠标按下去的时候才能有mousemove事件。至于交换的话,上面也有说了。谢谢大家,希望大家写代码不要像cxk。 ...

April 8, 2019 · 2 min · jiezi