1.SortableJS原理分析(源码)
2.可拖拽、拖拽缩放、效果线拖旋转组件网格效果及使用方法
3.vue可拖拽悬浮按钮组件
4.vue3功能最强的源码拖拽库-dnd起步教程
5.最强拖拽组件 Pragmatic-drag-and-drop
6.可拖拽、缩放、拽编作源旋转组件之 - 多元素组合与拆分功能
SortableJS原理分析(源码)
前言
SortableJS是辑设计制基于H5拖拽API实现的一个轻量级JS拖拽排序库,它适用于以下一些场景:
容器项目拖动排序:容器列表内的拖拽c 缩小图片 源码子项目,通过拖动进行位置调换,效果线拖且具有动画效果;
容器间的源码项目移动:将一个容器列表中的子项目,拖动到另一个容器列表中(移动/克隆)。拽编作源
不论是辑设计制容器内元素顺序排序,或是拖拽两个容器内的元素进行移动,本质上是效果线拖在通过操作DOM来实现。
下面我们先熟悉一下SortableJS基本使用。源码
示例1、拽编作源HTML结构:
<divclass="row"><divid="leftContainer"class="list-groupcol-6"><divclass="list-group-item">Item1</div><divclass="list-group-item">Item2</div><divclass="list-group-item">Item3</div><divclass="list-group-item">Item4</div><divclass="list-group-item">Item5</div><divclass="list-group-item">Item6</div></div><divid="rightContainer"class="list-groupcol-6"><divclass="list-group-itemtinted">Item1</div><divclass="list-group-itemtinted">Item2</div><divclass="list-group-itemtinted">Item3</div><divclass="list-group-itemtinted">Item4</div><divclass="list-group-itemtinted">Item5</div><divclass="list-group-itemtinted">Item6</div></div></div>2、辑设计制为容器实例化:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});现在,就可以在容器内进行排序拖动,或者拖动左侧容器元素,添加到右侧容器中。
思路分析在看源码之前,还是需要对H5拖拽用法有一定了解,如果不熟悉,直接去看源码很容易就放弃。
若你对H5拖拽API比较熟悉,就可以根据SortableJS的视图呈现效果,想出个大概思路。
拖拽,首先要搞清楚两个词汇对象:
拖动元素:作为拖拽元素被拖起(下文叫dragEl);
目标元素:作为拖拽元素即将被放置时的参照物(下文叫target);
在SortableJS中,拖拽离不开以下几个事件:
dragstart:作为拖拽元素,按下鼠标开始拖动元素时触发(拖拽周期只触发一次);
dragend:作为拖拽元素,当鼠标松开拖放元素时触发(拖拽周期只触发一次);
dragover:作为拖拽元素,当拖动元素进行移动,会持续触发,需要在这里取消默认事件,否则元素无法被拖动(松开时元素的预览幽灵图又回去了);
drop:作为目标元素,当鼠标松开拖放元素时触发(拖拽周期只触发一次);
下面我们一起去分析SortableJS具体实现。
源码实例构造函数从上面的示例使用上得知,SortableJS是一个构造函数,接收容器元素和配置项:
constexpando='Sortable'+(newDate).getTime();functionSortable(el,磁力链接 源码 csdnoptions){ this.el=el;//rootelementthis.options=options=Object.assign({ },options);el[expando]=this;constdefaults={ group:null,sort:true,//默认容器可以排序animation:0,removeCloneOnHide:true,//将一个容器元素拖动至另一个容器后,默认setData:function(dataTransfer,dragEl){ dataTransfer.setData('Text',dragEl.textContent);}};//参数合并for(varnameindefaults){ !(nameinoptions)&&(options[name]=defaults[name]);}//规范group_prepareGroup(options);//绑定原型方法为私有方法for(varfninthis){ if(fn.charAt(0)==='_'&&typeofthis[fn]==='function'){ this[fn]=this[fn].bind(this);}}//绑定指针触摸事件,类似mousedownon(el,'pointerdown',this._prepareDragStart);on(el,'dragover',this);on(el,'dragenter',this);}初始化示例做了以下几件事件:
将传入的参数与提供的默认参数进行合并;
规范传入的group格式;
将原型上的方法绑定在实例对象上,便于使用;
绑定pointerdown、dragover、dragenter事件,其中pointerdown可以看作是dragstart事件,做了一些拖拽前的准备工作。
group用于两个容器元素的相互拖拽场景,规范group核心代码如下:
function_prepareGroup(options){ functiontoFn(value,pull){ returnfunction(to,from){ letsameGroup=to.options.group.name&&from.options.group.name&&to.options.group.name===from.options.group.name;if(value==null&&(pull||sameGroup)){ returntrue;}elseif(value==null||value===false){ returnfalse;}elseif(pull&&value==='clone'){ returnvalue;}else{ returnvalue===true;}};}letgroup={ };letoriginalGroup=options.group;if(!originalGroup||typeoforiginalGroup!='object'){ originalGroup={ name:originalGroup};}group.name=originalGroup.name;group.checkPull=toFn(originalGroup.pull,true);group.checkPut=toFn(originalGroup.put);options.group=group;}_prepareDragStart拖动前的准备工作当鼠标按下触发pointerdown事件时,会保存拖动元素的信息,提供后续使用,并且注册dragstart事件:
letoldIndex,newIndex;letdragEl=null;//拖拽元素letrootEl=null;//容器元素letparentEl=null;//拖拽元素的父节点letnextEl=null;//拖拽元素下一个元素letactiveGroup=null;//options.groupSortable.prototype={ _prepareDragStart(evt){ lettarget=evt.target,el=this.el,options=this.options;oldIndex=index(target);rootEl=el;dragEl=target;parentEl=dragEl.parentNode;nextEl=dragEl.nextSibling;activeGroup=options.group;dragEl.draggable=true;//设置元素拖拽属性on(dragEl,'dragend',this);on(rootEl,'dragstart',this._onDragStart);on(document,'mouseup',this._onDrop);},}on就是addEventListener,index方法用于获取元素在父容器内的索引:
functionon(el,event,fn){ el.addEventListener(event,fn);}functionoff(el,event,fn){ el.removeEventListener(event,fn);}functionindex(el){ if(!el||!el.parentNode)return-1;letindex=0;//返回元素节点之前的兄弟元素节点(不包括文本节点、注释节点)while(el=el.previousElementSibling){ if(el!==Sortable.clone)index++;}returnindex;}_onDragStart用于处理dragstart事件逻辑,_onDrop用于处理拖拽结束逻辑,比如这里执行了dragEl.draggable=true;,那么在mouseup鼠标松开后需将draggable=false。
这里有趣的一点是dragend事件,它的处理函数绑定的是this即Sortable实例本身,我们都知道实例对象是一个对象,怎么能作为函数使用呢?
其实addEventListener第二参数可以是函数,也可以是对象,当为对象时,需要提有一个handleEvent方法来处理事件:
Sortable.prototype={ handleEvent:function(evt){ switch(evt.type){ case'dragend':this._onDrop(evt);break;case'dragover':evt.stopPropagation();evt.preventDefault();break;case'dragenter':if(dragEl){ this._onDragOver(evt);}break;}},}到这里,整个拖拽流程功能函数都暴露在了眼前:
_onDragStart处理dragstart拖拽开始工作;
_onDragOver处理拖拽移动到别的元素时工作;
_onDrop处理鼠标拖动结束的收尾工作。
dragstart这里做了两件事情:
clone一个dragEl元素副本,用于两个容器项目移动时使用;
触发外部传入的clone和dragstart事件;
letcloneEl=null,cloneHidden=null;//clone元素_onDragStart(evt){ letdataTransfer=evt.dataTransfer;letoptions=this.options;cloneEl=clone(dragEl);cloneEl.removeAttribute("id");cloneEl.draggable=false;//设置拖拽数据if(dataTransfer){ dataTransfer.effectAllowed='move';options.setData&&options.setData.call(this,dataTransfer,dragEl);}Sortable.active=this;Sortable.clone=cloneEl;_dispatchEvent({ sortable:this,name:'clone'});_dispatchEvent({ sortable:this,name:'start',originalEvent:evt});},functionclone(el){ returnel.cloneNode(true);}_dispatchEvent会通过newwindow.CustomEvent构造一个事件对象,将拖拽元素的信息添加到自定义事件对象上,传递给外部的注册事件函数,大体代码如下:
functiondispatchEvent(...params){ //sortable没有传,就根据rootEl获取sortable。sortable=(sortable||(rootEl&&rootEl[expando]));if(!sortable)return;letevt,options=sortable.options,onName='on'+name.charAt(0).toUpperCase()+name.substr(1);//自定义事件,拿到事件对象,满足外部用户传入的事件正常使用if(window.CustomEvent){ evt=newCustomEvent(name,{ bubbles:true,cancelable:true});}else{ evt=document.createEvent('Event');evt.initEvent(name,true,true);}evt.to=toEl||rootEl;evt.from=fromEl||rootEl;evt.item=targetEl||rootEl;evt.clone=cloneEl;evt.oldIndex=oldIndex;evt.newIndex=newIndex;//执行外部传入的事件if(options[onName]){ options[onName].call(sortable,evt);}}可见,拖拽的核心逻辑不在dragstart中,下面我们去看dragenter的处理函数_onDragOver。
dragenterSortableJS的核心逻辑在_onDragOver中,拿容器内项目排序为例:当拖动dragEl元素,温控表源码移动到另一个元素上时,会发生两者的位置交换,可见,Sort的逻辑在这里。
首先,在实例化对象时绑定了dragover和dragenter事件,并且通过handleEvent将事件逻辑交由_onDragOver来处理:
on(el,'dragover',this);on(el,'dragenter',this);handleEvent:function(evt){ switch(evt.type){ case'dragover':evt.stopPropagation();evt.preventDefault();break;case'dragenter':if(dragEl){ this._onDragOver(evt);}break;}},在_onDragOver中,需要注意一点是:假如有两个容器,那就有两个newSortable实例对象,isOwner将为false,这是就需要判断拖动容器的activeGroup.pull(是否允许被移动)和group.put(是否允许添加拖动过来的元素)。
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});0上面的核心在于下面这一行代码:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});1如果拖拽元素的位置小于目标元素的位置,说明是从上往下拖动,那么将dragEl移动到target.nextSibling之前;
如果拖拽元素的位置大于目标元素的位置,说明是从下往上拖动,那么只需将dragEl移动到target之前即可;
整个移动过程均采用DOM操作insertBefore来实现。
另外如果是两个容器的场景(isOwner=false),并且拖动元素的容器activeGroup.pull=clone,需要将dragstart创建的clone元素渲染到容器中:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});2dropdrop主要做一些收尾工作,如将dragEl.draggable=false,移除绑定的mouseup、dragstart、dragend事件,触发用户传入的sort、end事件等。
不过注意,虽然起名叫drop,触发的事件确是dragend。
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});3动画如果想在拖动排序中有一定的animation动画效果,可以配置动画属性,属性值是动画持续时长:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});4动画的时机也是在dragenter中,大致的思路如下:
1、记录:记录容器子项位置信息
在操作DOM移动dragEl之前,记录容器内所有子项的位置;
进行DOM操作进行位置交换,DOM操作本身没有动画;
这时再去记录一次移动后的容器内所有子项的位置;
2、执行:有了上面几步的操作,接下来就可以根据移动前后的位置进行动画操作
通过translate先让元素立刻回到移动前的位置;
此时给元素自身设置过度效果transform;
这时候就可以通过translate让元素回到移动之后的位置。
大致实现如下:
newSortable(leftContainer,韩剧tv模板源码{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});5最后本文以探索SortableJS拖拽思路为主线,去了解业界开源拖拽库的设计与思路。感谢阅读。
原文:/post/可拖拽、缩放、旋转组件网格效果及使用方法
本文介绍基于 vue3.x + CompositionAPI + typescript + vite 的可拖拽、缩放、旋转组件的实现及使用方法。
在线示例和源码地址提供给读者自行查看。
在上一篇文章中,我们探讨了实现细节,并留有两个待解问题。为了帮助读者快速上手,我们主要分享网格拖拽、缩放比的实现以及 es-drager 组件的具体使用。
在移动操作时,我们通过校验 onMousemove 事件来实现网格功能。在网格缩放功能中,我们新增代码,确保在开启网格时,鼠标始终在小圆点按下的位置。实现这一功能的关键在于计算缩放大小,我们通过调用 calcGridResize 函数获取缩放后的尺寸。
接下来,我们介绍如何使用 es-drager 组件。首先,通过安装依赖并全局注册组件,然后在组件中直接使用。在浏览器中,我们可以通过 HTML 标签导入 es-drager,并使用全局变量 ESDrager。
es-drager 提供了丰富的事件,使用户能够实现更细粒度的操作。通过 snapToGrid 属性控制是否开启网格,gridX 和 gridY 分别表示网格的横纵大小。scaleRatio 属性用于设置缩放比例,确保在父标签或画布放大或缩小时,拖拽操作的精准性。
此外,下载链接源码软件我们还可以使用方位按键来控制元素的移动。开启网格后,移动将遵循网格规则,否则每次移动为 1 像素。对于不需要使用方向键移动的情况,可以禁用 disabledKeyEvent。
对于需要在组件中插入 echarts 图表的场景,我们同样支持。只需确保将 chart 元素的宽高设置为 %,并在监听 es-drager 的 resize 事件后调用 echarts 的 resize 方法,实现图表的自适应缩放。
最后,我们提供了一个详细的 API 概览,包括 Drager 属性、事件和插槽,帮助读者更全面地理解组件的使用方法。
vue可拖拽悬浮按钮组件
为了实现Vue可拖拽悬浮按钮组件,我们首先需要进行效果预览,了解组件的外观与功能。具体效果预览地址请访问:.../JYeontuCo... 关键代码实现包括阻止默认拖动选择事件、鼠标点击按钮事件、鼠标移动事件以及鼠标抬起事件。这些功能使组件具有高度的交互性与实用性。您可以通过npm进行组件的安装与引入,确保使用的版本为0.1.4及以上,以获得该组件的功能。安装与引入步骤如下:安装:执行命令 `npm install` 或 `yarn add`,根据您项目中使用的包管理器选择相应的命令。
引入:在Vue组件中引入已安装的组件。
引用组件:在Vue组件的模板部分引用已引入的组件资源。
更详细的组件使用说明请查阅文档地址:.../JYeontuCo... 如需查看组件源代码,请访问Gitee地址:gitee.com/zheng_yongtao... 若对组件感兴趣,亦可关注公众号:前端也能这么有趣,获取更多前端相关资讯与有趣的文章。 个人网站与****如下:Gitee:访问个人Gitee主页
个人博客:浏览个人博客文章
如果您有任何问题或建议,欢迎在公众号(前端也能这么有趣)留言,或通过Gitee与我联系。在这里,我分享的不仅仅是技术知识,也包含对羽毛球的热爱与个人成长的记录。感谢您对我的支持,期待我们的下一次交流。vue3功能最强的拖拽库-dnd起步教程
拖拽功能在Vue开发中不可或缺,Vue3中使用功能强大的dnd库,相较于vue-draggable,dnd在扩展性方面更胜一筹,推荐使用dnd库进行拖拽功能开发。
为了简化学习dnd的步骤,我们可以按照以下上手思路进行。首先,直接从dnd的示例代码出发,理解dnd的运行流程,这通常只需很少的代码量,实际操作一遍就能深刻理解dnd的功能。接下来,对照dnd的官方示例,根据自身业务需求,下载官方示例代码并进行改造,以适应具体的业务场景。使用此方法,能够提高业务实现的效率,同时加快学习上手速度。当然,如果觉得有用的话,支持一下也是鼓励写作的动力。
下面是一个简单的dnd流程示例,用于实现将一个拖拽源移动到容器内的功能:
首先,创建可以被拖拽的元素(Drag)。
其次,监控拖拽源的状态变化,例如在拖拽过程中,查看拖拽源的实时位置或状态信息。
然后,创建接受拖拽源的容器(Drop),并实时监听容器内的情况,例如更新拖拽源在容器内的位置,以此实现拖拽功能的完整过程。
若要更深入理解dnd的运作流程,可以对照实现过程中的代码细节回顾整个流程。要实现特定业务逻辑,可查阅dnd官方文档中提供的示例代码库,找到与自己业务需求相似的例子,通过下载并修改官方源代码,以适应实际项目需求。这种方法能够快速上手dnd,并熟练掌握其应用技巧。
综上所述,dnd库提供了一种高效、灵活的拖拽功能解决方案,在Vue3中使用dnd不仅可以简化开发过程,还能提高代码的可维护性和可扩展性。在实际开发中,灵活运用dnd的特性与方法,可以帮助开发者更快地构建出高质量的拖拽功能,满足各种应用需求。
最强拖拽组件 Pragmatic-drag-and-drop
普拉提克-拖拽组件 树酱向大家介绍一款新开源的前端拖拽组件,名为Pragmatic-drag-and-drop,简称Pragmatic。这款组件近期在热门开源项目中表现出色。 Pragmatic是由Atlassian公司出品的一款关注性能的拖放库,旨在提升前端用户体验。其“实用主义”的名字让人印象深刻。Atlassian公司除了Pragmatic,还有其他知名产品,如Trello、Jira等。 Pragmatic支持多种形态的拖拽场景,功能丰富,尤其在与同类开源库如react-beautiful-dnd对比时,显示出了更多的应用场景。实际演示效果表现出色。 Pragmatic在不同框架(如react、svelte、vue、angular)中使用灵活,不局限于特定技术栈。参考不同框架的使用方法,比如在react中的应用示例。 体积方面,Pragmatic的核心包更轻更小,仅为4.7kB,相较于同类开源库,体积优势明显。 性能方面,Pragmatic在TTI(可交互时间)指标上表现出色,秒杀其他类库。TTI指标用于衡量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。 官方文档提供了丰富的示例和使用指南,便于开发者深入了解和应用Pragmatic。感兴趣的读者可访问文档链接:ponents/pragmatic-drag-and-drop/examples。 项目源代码在Github上开源,地址为:/atlassian/pragmatic-drag-and-drop。可拖拽、缩放、旋转组件之 - 多元素组合与拆分功能
介绍
基于 vue3.x + CompositionAPI + typescript + vite 的可拖拽、缩放、旋转的组件
在线示例
源码地址
这节主要来分享如何使用es-drager,根据现有功能实现多个元素组合与拆分功能
es-drager的更新
es-drager 的1.x版本支持移动端啦
另外最近还在使用es-drager开发一个低代码编辑器(还未成型),也算是一个es-drager的综合使用案例吧,老铁们可以先到 编辑器案例 中查看
本章内容使用svg绘制网格
在开始讲组合之前,先来介绍一下如何使用svg画一个指定大小的网格。前面的demo都是使用css的方式,感觉还是不太灵活,有一定的局限性
这里直接抽离成了一个 vue 组件
可以看到,如果不加属性的话,整个网格组件还是挺简单的
使用时直接包裹在画布元素里即可,当然我们也可以传入指定网格的大小
元素组合与拆分选中区域
组合前,我们需要选中需要组合的元素,类似下图这样的效果
注意:由于这个onMouseDown是画布触发时调用的, 因此e.target 获取的是画布元素
有了这个组件,该如何使用呢?
先上使用代码,后面有详细解释
步骤解析
移动选中的元素
移动多个区域选中的元素,类似下面的效果
要计算每个元素的移动距离,就需要es-drager提供的一些事件了
上面多了 areaSelected 记录是否是区域选择状态,那么在什么情况它的值才是true呢?
这时我们就要监听 Area 组件的 up 事件了
只有区域选中了元素,areaSelected才能是true,然后点击其它区域是设置为false
组合与拆分
完成上面的工作后,我们来看看如何将多个元素组合成一个,为了方便渲染我们先封装一个Group组件
Group 组件
这个组件的功能就是循环显示所有组合的元素
下面分别解释这两个函数
最后
本节只是对多个元素的组合与拆分的简单实现,对于组合后的旋转与缩放我想在后面的文章中介绍。
最后来看看在drawio中元素组合与拆分的效果
drawio在实现组合后缩放会有一点小问题,大家看下图
当然我们的目标是尽可能实现理想的组合后的缩放与旋转
ItemTouchHelper实现RecyclerView拖拽&合并的效果
左侧栏展示的是一个RecyclerView,通过手势拖拽可以实现排序功能,还能将图标合并为文件夹,类似于桌面应用图标合并的交互体验。
实现此功能的核心是利用ItemTouchHelper.Callback。它能够快速处理拖拽排序和滑动移除效果,但为了达到合并效果,需要对此进行改造。
核心思路在于:
1. 重写chooseDropTarget()方法,当两个viewHolder重叠部分满足合并条件时,暂存这两个viewHolder。不满足则清空暂存变量。
2. 在拖拽结束时,即手抬起时,若暂存变量非空,则触发合并逻辑。
深入源码:
理解ItemTouchHelper的实现原理,是改造的基础。拖动viewHolder时,触摸事件由mOnItemTouchListener#onTouchEvent()分发,处理MotionEvent.ACTION_MOVE事件后,通过moveIfNecessary(ViewHolder viewHolder)实现位置交换。
具体实现:
1. 重写chooseDropTarget(),在重叠状态时暂存viewHolder,并确保在重叠情况下返回null,以阻止默认行为。
2. 重写onSelectedChanged(),在拖动结束时判断是否需要触发合并操作。
接口封装:
为了更好地实现功能,封装了三个接口:IDragAdapter、IDragItem、IDragHandler,分别对应适配器、列表拖拽项和拖拽处理,便于根据场景实现特定功能。
具体实现:
1. IDragItem接口描述列表拖拽项接口,由RecyclerView的具体ViewHolder实现,用于判断是否可以拖动、合并和显示拖拽状态。
2. IDragAdapter接口描述适配器接口,由RecyclerView的具体Adapter实现。
3. IDragHandler接口描述拖拽处理接口,用于拖拽条件判断、回调监听、合并处理逻辑。接入拖拽功能时,需实现此接口,并通过DragTouchCallback#setDragHandler()赋值。
使用方法:
实现IDragHandler#onMergeData()接口方法,绑定RecyclerView使用DragTouchCallback。在完成IDragHandler注册并绑定recyclerView后,即可实现拖拽、合并效果。
总结:
文章提供了一个从原理理解到具体实现的完整流程,包括源码分析、接口设计和使用指南。git项目中有相关Demo演示,感兴趣的开发者可以参考学习。
项目地址:[GitHub链接]。感谢阅读至此,演示效果包括合并文件夹和合并相加。
2024-12-23 07:12552人浏览
2024-12-23 07:112343人浏览
2024-12-23 06:422894人浏览
2024-12-23 05:581982人浏览
2024-12-23 05:432896人浏览
2024-12-23 05:402061人浏览
中国消费者报天津讯记者 万晓东)近日,天津市南开区市场监管局对一药店销售口罩两次涨价的违法行为作出罚款50万元的决定。据悉,天津市南开区市场监管综合行政执法支队的执法人员在对辖区的一家药店检查时,发现
1.武汉手机直播系统武汉手机直播系统 淘宝直播电商直播抖音直播快手王者荣耀主播线上参与公益“短视频+直播”呼吁大家一块做好事、南方洪水灾害、决胜脱贫攻坚战……注定成为值得铭记的一年。在种种挑战与考
1.通信原理板块——线性分组码之循环码2.写出下列的其他进制:32=0 =0x.75=0 =0x 分别求出26和-26的原码,反码和补码3.若用9位机器表示十进制-101,则源码表示形式为:通信原理板