说起来,最近接了个活儿,一个后台管理的页面,甲方老哥要求很高,要那种数据看板一样的东西。关键是啥?他希望里头那些小卡片、小模块,都能让用户自己拖来拖去,想放哪儿就放哪儿,自己保存布局。我当时一听,这不是挺带劲儿的嘛拖拽功能,小意思。
本站为89游戏官网游戏攻略分站,89游戏每日更新热门游戏,下载请前往主站地址:www.gm89.icu
我这人也懒嘛想着直接找现成的UI库,看看有没有那种开箱即用的拖拽组件。结果一搜,还真不少。什么Vue Draggable,SortableJS,拉过来试了试。是能用,也挺方便的。但是,用着用着就发现问题了。要么就是样式跟我的项目风格不搭嘎,改起来费劲,跟个烫手山芋似的;要么就是功能太重了,我这儿就想实现个最基本的自由拖动,它给我带了一大堆我用不着的功能,代码量噌噌地往上跑,看着就心烦。
还有些组件,限制太多,比如说只能在列表里头拖拽排序,不能跨区域自由放置。我这需求是想放哪儿就放哪儿,跟画画一样,那可真是把我给搞蒙了。琢磨来琢磨去,寻思着这样下去不是办法,老是“削足适履”,不如干脆自己撸一个算了。那时候也没多想,就想着搞个简单的,能满足我当前需求的就行,然后就叫它“vdrag”,V嘛就代表了这是我的版本,或者说,挺V5的。
说干就干,我这个人一旦决定了,就喜欢立马动手。我把这个拖拽功能的实现,分成了最核心的几步,说白了,就是围绕着鼠标那三个动作:按下、移动、抬起。
抓住它!—— 鼠标按下(mousedown)
我先从一个最简单的div开始下手,给它加了个鼠标按下的事件监听。一按下去,我得知道几件事:这个要拖动的元素现在在屏幕的什么位置?鼠标当前点在屏幕的什么位置?光知道这些还不够,我还得算出鼠标点在元素上的那个“相对位置”,也就是鼠标离元素左上角有多远。我当时就用和拿到了鼠标在屏幕上的坐标,然后通过元素的offsetLeft和offsetTop拿到了元素相对于父容器的左上角坐标。一减,这个偏移量就搞定了。我会把这个偏移量记下来,待会儿移动的时候可全靠它了。
拖着它跑!—— 鼠标移动(mousemove)
鼠标按下去之后,接下来就是关键的移动了。我可不能只监听在元素上,因为鼠标一不小心就可能拖出元素的边界,那样就断了,拖拽体验会非常差。我在document上监听了鼠标移动事件。只要鼠标按着不放,在文档的任何地方移动,我都能捕捉到。在mousemove事件里,我根据鼠标当前的clientX和clientY,减去之前算好的那个偏移量,这样就得到了被拖动元素的新位置。然后我就把这个新位置赋值给元素的和。别忘了,得给它加上'px',并且确保这个元素是position: absolute;,不然它可动弹不了。
放开它!—— 鼠标抬起(mouseup)
到了一步,鼠标一抬起,拖拽就得结束了。这个动作也是在document上监听的。一旦捕捉到mouseup事件,我就立马把之前在document上添加的mousemove和mouseup事件监听器都给移除了。为啥要移除?因为如果鼠标抬起来了,你还在监听移动事件,那可就乱套了,不移除的话,拖拽功能还会继续瞎跑,指不定什么时候又被触发了。这样一来,被拖动的元素就稳稳地定格在新位置了。
遇到的小麻烦和怎么解决的
刚开始的时候,拖拽是能拖了,但是一拖起来,页面上的文字、图片,都跟着被选中了,蓝蒙蒙的一片,看着可烦人了。我赶紧在mousedown事件里,加了一句,这招儿特别好使,一下就把浏览器默认的选中行为给禁止了。瞬间世界清爽了。
还有就是,拖着拖着,那些小卡片有时候会跑出屏幕外面,跑到姥姥家去了,这样可不行。我就得加上边界判断。我得算清楚这个元素能向左向右,向上向下跑的最大距离。比如,不能让left值小于0,也不能让它超过父容器的宽度减去自身的宽度。top值也是同样的道理。每次计算出新位置后,我都得拿这些边界值给它“修正”一下,保证它老老实实呆在我的“地盘”里。
后来又遇到一个问题,如果页面上有很多个可拖拽的元素,那我怎么知道当前拖动的是哪一个?我就给所有需要拖拽的元素都加上了一个特殊的类名,比如说.draggable-item。然后在mousedown事件里,我就通过来判断是不是我定义的那个可拖拽的元素,如果是,那就执行拖拽逻辑,如果不是,就直接跳过。这样就实现了多个元素的独立拖拽,互不影响。
把它变成“vdrag”小工具
为了让这个拖拽功能用起来更方便,我琢磨着把它封装成一个小的工具函数。就取名叫vdrag()。它只需要传入一个DOM元素作为参数,然后就能让这个元素拥有拖拽的能力。我还给它加了点可选的参数,比如可以设置一个拖拽的“手柄”(handle),只有点击手柄才能拖动;或者提供一些回调函数,比如拖拽开始(onDragStart)、拖拽进行中(onDragging)和拖拽结束(onDragEnd)的时候,可以执行一些自定义的逻辑。这样一来,在项目里任何需要拖拽的地方,我只需要一行代码调用vdrag()函数,把要拖的元素扔进去,就能搞定一切,感觉一下子就高级了。
这一趟自己动手实现“vdrag”下来,虽然一开始觉得有点儿麻烦,花了不少时间去调试各种细节,但是亲手把这个拖拽功能从无到有地敲出来,感觉真是学到了不少东西。不光是学会了怎么处理鼠标事件,怎么精准计算元素位置,更重要的是,培养了我解决问题的那股子劲儿。遇到功能需求,不总是急着去找轮子,而是先想想自己能不能造一个简单够用的,哪怕是一个小小的“螺丝钉”,也是自己亲手打造的。各位新手朋友,别怕自己动手去折腾,很多时候,最简单的,反而是最适合自己的,而且过程中学到的东西,那可是实实在在装进自己脑子里的。