1.UI(一) - NGUI和UGUI比较
2.tinymce富文本上传插件的拖拽拖拽编写
3.SortableJS原理分析(源码)
4.如何高效、简单实现菜单拖拽排序?
5.模板网站与定制网站的排序排序区别
UI(一) - NGUI和UGUI比较
在选择UI系统作为游戏项目主框架时,NGUI和UGUI是源码源码两个主要选项。本文将从多个方面对两者进行比较,拖拽拖拽以帮助开发者做出决策。排序排序
首先,源码源码所有网页都有源码吗考虑图集处理功能。拖拽拖拽NGUI需要手动使用工具拼接,排序排序而UGUI在开发期间可以将直接作为元素使用,源码源码打包时自动拼接成图集,拖拽拖拽且自带alpha拆分功能,排序排序操作更为便捷。源码源码
组件支持方面,拖拽拖拽NGUI提供了丰富的排序排序组件选择,如Localization System、源码源码ScrollView、UIButton、UIToggle等,涵盖多语言、滚动、按钮、切换选择、进度条、下拉列表、输入框、快捷键绑定、导航绑定、排列、表格、动画、锚点、摄像头、拖拽等功能。相比之下,UGUI组件数量较少,主要涵盖文字、贴图、按钮、切换与选择、滑动条、下拉框、输入框、二进制源码减源码画布、面板、滑动视图、遮挡块等。
可定制程度方面,NGUI通过源代码支持,允许开发者随时根据需求进行修改。而UGUI采用开源C#代码,开发者可以进行修改,但需要重新打包成DLL文件后替换原文件,操作稍显复杂。
输入事件处理方式不同。NGUI通过摄像头发出射线碰撞,识别输入事件并按碰撞顺序处理层级。UGUI根据输入点位置RaycastTarget,判断事件应交由哪个UI元素处理。
层级显示控制方面,NGUI依靠Panel depth和RenderQueue控制。UGUI则通过距离摄像机的前后位置或排序顺序设置层级。
字体制作方面,NGUI不支持动态字体,而UGUI支持动态字体,可以直接使用字体文件。
社区完善程度上,NGUI由商业运营,氛围较好。UGUI官方自运营,后端强大,拥有更多资源支持。
性能方面,NGUI和UGUI均表现良好,不同测试网站的统计结果均显示其性能可靠。
在综合比较后,NGUI和UGUI各有特色,没有绝对的好与差。项目选择应根据个人和项目需求而定。对于Unity3D 4.x项目,NGUI可能是更合适的选择,而新项目通常会选用最新版本的Unity3D,因此UGUI应用更为广泛。
建议开发者选择自己熟悉的下载的源码源码没有sql文件UI系统,并尝试不熟悉的系统以扩展技能。对于希望更深入自定义和修改源码以服务于特定游戏逻辑或性能定制需求的开发者,NGUI可能是一个更理想的选择。然而,UGUI也提供了开源源码,允许在现有基础上扩展和重载。
最终,决策应基于项目需求、团队技能、资源可用性以及个人喜好。在不断学习和适应新趋势的同时,选择适合自己项目需求的UI系统,将是推动项目成功的关键。
tinymce富文本上传插件的编写
在开发新后台过程中,针对文章编辑页面的富文本编辑需求,替换使用了tinymce。然而,官方插件中未包含本地上传功能。通过网络搜索,虽找到多种解决方案,但大都涉及较大改动,重新设计界面,并且官方提供的弹框组件具备鼠标拖拽功能,令人颇为纠结。最终,决定自行开发一个本地上传插件,旨在保持与官方组件风格的一致性。
项目已推送至GitHub,以下是插件实现的效果预览。
![插件效果](image)
深入分析官方组件的组织结构,发现每个组件由逻辑代码文件(plugin.js)、索引文件(index.js)构成,而编辑器中的组件添加通过插件管理器进行。打开操作弹框时的关键逻辑在于内容定义,以及如何在弹框内部插入自定义内容。
![组件结构](image.png)
为了整合自定义对话框内容,引入CSS样式,避免修改原始tinymce样式文件,选择将样式代码导入JavaScript文件中,再追加至HTML字段。假设样式文件为style.js,印尼溯源码和不带溯源码代码结构如下:
![样式导入](image.png)
HTML字段内容仅控制弹窗内的显示内容,因此,确认与取消按钮的事件处理需在open函数中配置。插件命名时需注意,当发布至npm供他人使用时,应忽略tinymce前缀,例如命名为tinymce-imageupload,实际使用时则写为imageupload。
上传功能基于FormData对象实现,确保兼容不同后端返回的响应数据结构。在插件开发中增加转换函数,以适应各种响应格式,用户在初始化tinymce时可配置转换函数,例如新增字段imageupload_converCb。
因项目未引入jQuery,所有操作基于原生DOM。在预览框中加入拖拽排序功能面临挑战,尝试原生API时感到头疼。该功能计划作为后期插件更新的一部分,或者在无法忍受的情况下,考虑引入jQuery。
文章中可能存在的不准确之处,欢迎业界同仁指正。项目源码已推送至GitHub,欢迎关注并Star。同时,该插件已发布至npm,通过命令行安装:npm install tinymce-imageupload --save-dev。
SortableJS原理分析(源码)
前言
SortableJS是基于H5拖拽API实现的一个轻量级JS拖拽排序库,它适用于以下一些场景:
容器项目拖动排序:容器列表内的子项目,通过拖动进行位置调换,且具有动画效果;
容器间的项目移动:将一个容器列表中的子项目,拖动到另一个容器列表中(移动/克隆)。
不论是容器内元素顺序排序,或是两个容器内的元素进行移动,本质上是在通过操作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,options){ 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,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});5最后本文以探索SortableJS拖拽思路为主线,去了解业界开源拖拽库的设计与思路。感谢阅读。
原文:/post/如何高效、简单实现菜单拖拽排序?
本文主角是ItemTouchHelper,它是RecyclerView对于item交互处理的一个辅助类,主要用于拖拽以及滑动处理。通过接口实现的方式,达到配置简单、逻辑解耦、职责分明的效果,并且支持所有的布局方式。实现主要包含自定义一个类,实现ItemTouchHelper.Callback接口,并在实现方法中根据需求简单配置。接口包含三个必须实现的方法:getMovementFlags、onMove和onSwiped。getMovementFlags用于创建交互方式,交互方式分为两种,最后通过makeMovementFlags把结果返回回去,makeMovementFlags接收两个参数,dragFlags和swipeFlags,即上面拖拽和滑动组合的标志位。onMove方法在拖拽时回调,这里主要对起始位置和目标位置的item做一个数据交换,然后刷新视图显示。onSwiped方法在滑动时回调,这个回调方法里主要是做数据和视图的更新操作。
接下来就是把这个辅助类绑定到RecyclerView。上面接口实现部分我们已经简单写好了,逻辑也挺简单,总共不超过行代码。绑定只需要调用attachToRecyclerView就好了。至此,简单的效果就已经实现了。下面开始优化和进阶的部分。
为了优化,我们添加了设置分割线的功能。RecyclerView网格布局实现等分,我们一般先是自定义ItemDecoration,然后调用addItemDecoration来实现的。但是我在实现效果的时候遇到一个问题,因为我加了布局切换的功能,在每次切换的时候,针对不同的布局分别设置layoutManager和ItemDecoration,这就导致随着切换次数的增加,item的间隔就越大。addItemDecoration,顾名思义是添加,通过查看源码发现RecyclerView内部是有一个ArrayList来维护的,所以当我们重复调用addItemDecoration方法时,分割线是以递增的方式在增加的,并且在绘制的时候会从集合中遍历所有的分割线绘制。部分源码显示了这一过程。
既然知道了问题所在,也大概想到了3种解决办法:1.调用addItemDecoration前,先调用removeItemDecoration方法remove掉之前所有的分割线;2.调用addItemDecoration(@NonNull ItemDecoration decor, int index),通过index来维护。实际上并不太行...因为始终都有两个分割线实例。我们再来梳理一下:我想到另外一个办法,不对RecyclerView做处理了,既然两种布局都有分割线,是不是可以把分割线合二为一了,然后根据LayoutManager去绘制不同的分割线?理论上是可行的,事实上也确实可以...自定义分割线:
为了提升用户体验,我们添加了选中放大/背景变色的功能。这里用到ItemTouchHelper.Callback中的两个方法,onSelectedChanged和clearView。我们需要在选中时改变视图显示,结束时再恢复。onSelectedChanged方法在拖拽或滑动发生改变时回调,这时我们可以修改item的视图。clearView方法在拖拽或滑动结束时回调,这时我们要把改变后的item视图恢复到初始状态。
在实际需求中,我们可能还需要实现固定位置的功能。定义一个固定值,并设置不同的背景色和其他菜单区分开。在onMove方法中判断,只要是固定位置就直接返回false。虽然第一个菜单无法交换位置了,但是它还是可以拖拽的。为了进一步提升用户体验,我们可以让固定位置不可以拖拽。ItemTouchHelper.Callback中有两个方法可以实现这一点:这俩方法默认都是true,所以即使不能交换位置,但默认也是支持操作的。我们可以通过重写isLongPressDragEnabled方法把它禁掉,然后再非固定位置的时候去手动开启。
在进行拖拽操作时,下标其实是变化的,在做相应的操作时,要取实时位置。不管是拖拽还是滑动,其实本质都是对Adapter内已填充的数据进行操作,实时数据通过Adapter获取即可。如果想要实现重置功能,直接拿最开始的原始数据重新塞给Adapter即可。
在看源码时,找对一个切入点,往往能达到事半功倍的效果。这里就从绑定RecyclerView开始吧。实例化ItemTouchHelper,然后调用其attachToRecyclerView方法绑定到RecyclerView。在这一过程中,代码其实有点意思的,解读一下:关键点在于通过触摸和手势识别来处理交互显示。通过MotionEvent来判断并调用相应的回调方法,如select、checkSelectForSwipe和moveIfNecessary,最终通过invalidate()方法实时刷新界面。
总结源码,本质工作都是由源码帮我们做了,我们只需要在回调里根据结果处理业务逻辑即可。通过上述步骤,我们可以实现高效的菜单拖拽排序功能,提升用户体验。
模板网站与定制网站的区别
模板网站和定制型网站的区别:
所谓模板网站就是有一个模板才能做,照着一个网站一模一模一样的来做。模板网站现在流行的做法又分两种,手动模板网站和智能建站。手动模板网站其实也叫定制型网站了。全部照着一模一样的来做。也是手动做的。智能建站模板是通过网络软件。一个智能建站系统来做。
所谓定制型网站就是根据公司的行业,公司的特色,VI的要求来定制设计。有利公司的品牌推广,注重品牌形象,适合公司的长远发展。有利于以后网站的升级改版。有利于网站的售后服务。
对于智能建站和定制型网站。如果只是一要一个网址,网站只做一个百度推广以后不用了。可以选择智能建站。智能建站对于网站的推广,品牌形象的定位是没有任何效果的。经济实惠的可以选择模仿网站建设。