摘要:剩下的,就是把精力集中于實(shí)現(xiàn)核心功能參考線和吸附。以下根據(jù)拖拽的事件周期,,分別闡述。但是考慮到吸附功能是需要對(duì)元素的位置具備完全地控制能力,因?yàn)槌醪經(jīng)Q定只提供的使用方式。
大概在2017年7月,我司計(jì)劃開發(fā)一款可視化建站的項(xiàng)目。由于團(tuán)隊(duì)初建人手短缺,當(dāng)時(shí)只有一年工作經(jīng)驗(yàn)的我被“趕鴨子上架”,開始了為期一年半的折騰之旅。在眾多復(fù)雜的交互中,有一項(xiàng)需求是“拖拽對(duì)齊吸附及顯示參考線”,當(dāng)時(shí)也希望在社區(qū)尋找解決方案。很可惜,除了一些簡(jiǎn)單的DEMO外,并沒(méi)有可用于生產(chǎn)環(huán)境的實(shí)踐。一年半過(guò)去了,項(xiàng)目接近尾聲不那么忙。我逐步整理出自己在工作中的解決方案,于是就有了這個(gè)開源項(xiàng)目react-dragline。
示例開門見山,首先上一個(gè)簡(jiǎn)單的例子。
import { DraggableContainer, DraggableChild } from "react-dragline" const children = [ { id: 1, position: { x: 100, y: 10 } }, { id: 2, position: { x: 400, y: 200 } }, ] const containerStyle = { height: 600, position: "relative", } const childStyle = { width: 100, height: 100, cursor: "move", background: "#8ce8df", } export default function Example() { return ({ children.map(({ id, position }) => ( ) })) }
然后你就可以動(dòng)手拖一拖體驗(yàn)一下啦~ 在線DEMO戳我
關(guān)于調(diào)用方式,起初參考了react-sortable-hoc,計(jì)劃使用HOC的寫法,但感覺(jué)使用HOC會(huì)把代碼從JSX中分離,復(fù)雜度不高的情況下有些過(guò)度設(shè)計(jì),所以這選擇了這更傳統(tǒng)的寫法。位置屬性是通過(guò)絕對(duì)定位實(shí)現(xiàn)的,因此需要使用者自行為DraggableContainer加上定位屬性relative/absolute/fixed,本意是檢測(cè)到?jīng)]有定位屬性時(shí)自動(dòng)加上relative,但是這種方式在服務(wù)端渲染的場(chǎng)景下會(huì)有丑陋的“跳動(dòng)”(因?yàn)橹挥性诳蛻舳瞬拍軝z測(cè)DOM嘛),因此就把這項(xiàng)功能給去了。更多的options都寫在README里了,出自我的“中式英語(yǔ)”大家閱讀起來(lái)也沒(méi)什么難度。
實(shí)現(xiàn)原理關(guān)于原理,拖拽功能是基于react-draggable的Uncontrolled組件DraggableCore,統(tǒng)一使用left和top作為x,y坐標(biāo)的映射。DraggableContainer和DraggableChild之間的通信是通過(guò)React.cloneElement實(shí)現(xiàn)的。剩下的,就是把精力集中于實(shí)現(xiàn)核心功能參考線和吸附。以下根據(jù)拖拽的事件周期onstart,ondrag,onstop分別闡述。
onstart在拖拽初始中獲取每個(gè)DraggableChild的坐標(biāo)、寬高、索引等信息??梢詫⑺械?b>DraggableChild分為兩類,target為當(dāng)前拖拽的元素,compares為其余的元素,可以稱為對(duì)照組(為了方便行為,下文中target即代表拖拽目標(biāo)元素,compare即代表當(dāng)前與target比較的元素)。為什么不在componentDidMount中就獲取好呢?因?yàn)檫@些元素的信息可能會(huì)變得,比如說(shuō)增刪。相比起這細(xì)微的性能損失,維護(hù)信息變化的成本顯然要高得多。
ondrag核心代碼主要在ondrag的過(guò)程中,我們需要不斷的去比較target和compares之間的距離是否小于閾值threshold(默認(rèn)5px)??紤]過(guò)是否需要加上debounce,但是似乎對(duì)靈敏度還是有些影響,不是一個(gè)太好的選擇。
吸附功能的實(shí)現(xiàn)相對(duì)簡(jiǎn)單,坐標(biāo)和對(duì)照組的某元素的距離小于閾值threshold時(shí),讓其等于對(duì)照組的坐標(biāo)即可:
// a 為對(duì)照組某元素的坐標(biāo) if (Math.abs(a - x) < threshold + 1) { x = a }
參考線的實(shí)現(xiàn)略微復(fù)雜一些,以Y軸方向?yàn)槔?,最初的?shí)現(xiàn)是分別取target元素和compare元素上下位置(Element.getBoundingClientRect),組成一個(gè)包含四個(gè)值的數(shù)組[t, b, T, B](target用小寫字母表示,compare用大寫字母表示),最大差值(排序取首尾值相減)即為參考線的長(zhǎng)度,取最小值作為參考線的起點(diǎn)。
這么做似乎也沒(méi)有什么問(wèn)題,實(shí)際上是有一些細(xì)微的誤差的。使用DOM元素的位置信息計(jì)算具有一定的滯后性,DOM表示的是當(dāng)前的位置,而計(jì)算的是拖拽下一幀的位置,這樣“細(xì)微的誤差”也就可以解釋了。解決方式也很簡(jiǎn)單,將數(shù)組中的t和b替換為y和y + height即可。
結(jié)束了嗎? 并沒(méi)有。最初的設(shè)計(jì)是將計(jì)算x和y是否需要吸附和參考線在統(tǒng)一流程里,因?yàn)橛形讲庞袝?huì)出現(xiàn)參考線,避免了重復(fù)的計(jì)算。然而,當(dāng)target元素同時(shí)和X軸和Y軸兩個(gè)compare元素吸附時(shí),Y軸的參考線是會(huì)受到Y(jié)軸吸附的影響(X軸同理)。見下圖:
當(dāng)水平方向上兩個(gè)綠色的元素吸附時(shí),Y軸的參考線也必須“突然地增加了一段”。因此后續(xù)又做了一次代碼封裝粒度更小的重構(gòu),以在計(jì)算完成x,y吸附之后再對(duì)參考線作出一次修正。
拖拽結(jié)束就比較簡(jiǎn)單了,將參考線的和一些其它狀態(tài)清除就好了。
收獲與總結(jié)在整理這些項(xiàng)目的過(guò)程中,除了核心代碼本身,還有一些我覺(jué)得更為寶貴的收獲。
在構(gòu)建方式上,我們?nèi)粘5捻?xiàng)目(Application)開發(fā)都是把源碼和第三方依賴等打包成“可執(zhí)行文件”,即可以直接扔到瀏覽器上跑JavaScript代碼。但是在打造一款第三方項(xiàng)目(Library)時(shí),這樣做是顯然不可行的。試想一下,如果一個(gè)項(xiàng)目有10個(gè)第三方依賴,而每個(gè)依賴都引入classnames,如果這些第三方依賴包都把classnames打包到源碼中,那對(duì)于使用者來(lái)說(shuō),豈不是有10份重復(fù)的classnames代碼?實(shí)際上我們需要做的是“只編譯,不打包”。可否記得你在使用npm install的時(shí)候安裝數(shù)量都是遠(yuǎn)遠(yuǎn)大于寫在package.json內(nèi)依賴的數(shù)量?沒(méi)錯(cuò),所有依賴及依賴的依賴...都是由用戶統(tǒng)一安裝,這樣就可以避免了上述“10份重復(fù)的代碼”的問(wèn)題。另外,一般考慮到瀏覽器用戶,確實(shí)會(huì)提供一份把依賴也打包進(jìn)源碼的UMD文件。
關(guān)于前端測(cè)試,這也是我之前了解較少的領(lǐng)域。一般前端業(yè)務(wù)變化頻繁,生命周期相對(duì)較短,不太具備持續(xù)迭代的可能,因此寫測(cè)試倒說(shuō)不上是一個(gè)性價(jià)比高的選擇。但是對(duì)于需要持續(xù)迭代的底層UI(組件庫(kù)),單元測(cè)試的必要性還是很高的。因此我也假模假樣地基于jest和enzyme寫了一些測(cè)試用例,以保證后續(xù)迭代的不會(huì)因?yàn)榇中拇笠舛鴮?duì)之前功能有所影響。
細(xì)心的朋友可能會(huì)發(fā)現(xiàn),我在DraggableChild中使用的defaultPosition而不是position,這里就涉及到一些uncontrolled components的知識(shí)。一般來(lái)說(shuō),合格的React組件是需要提供position和defaultPosition兩種使用方式的。但是考慮到吸附功能是需要對(duì)元素的位置具備完全地控制能力,因?yàn)槌醪經(jīng)Q定只提供defaultPosition的使用方式。
react-dragline算是我的第一個(gè)不那么玩具的開源項(xiàng)目了,歡迎大家交流拍磚~
原文首發(fā)于我的博客https://www.vq0599.com/p/44,轉(zhuǎn)載請(qǐng)注明。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/109173.html
摘要:基于實(shí)現(xiàn)的移動(dòng)端的可吸附懸浮按鈕預(yù)覽地址移動(dòng)端源碼地址安裝使用 基于react實(shí)現(xiàn)的移動(dòng)端的可吸附懸浮按鈕 預(yù)覽地址(移動(dòng)端): https://kkfor.github.io/suspe... 源碼地址: https://github.com/kkfor/susp... 安裝 npm install suspend-button -S 使用 import React, { Compo...
摘要:基于實(shí)現(xiàn)的移動(dòng)端的可吸附懸浮按鈕預(yù)覽地址移動(dòng)端源碼地址安裝使用 基于react實(shí)現(xiàn)的移動(dòng)端的可吸附懸浮按鈕 預(yù)覽地址(移動(dòng)端): https://kkfor.github.io/suspe... 源碼地址: https://github.com/kkfor/susp... 安裝 npm install suspend-button -S 使用 import React, { Compo...
摘要:類似于吸附效果代碼修改組件之間的沖突檢測(cè)首先是組件之間的沖突檢測(cè),組件與組件的邊界檢測(cè)需要一個(gè)標(biāo)記進(jìn)行判斷。是否開啟元素對(duì)齊當(dāng)調(diào)用對(duì)齊時(shí),用來(lái)設(shè)置組件與組件之間的對(duì)齊距離,以像素為單位。如果發(fā)現(xiàn)什么或者可以將代碼優(yōu)化的地方請(qǐng)勞煩告知我。 Vue 用于可調(diào)整大小和可拖動(dòng)元素的組件并支持組件之間的沖突檢測(cè)與組件對(duì)齊 更新2.0版本 說(shuō)明:組件基于vue-draggable-resizabl...
閱讀 2460·2021-11-22 14:56
閱讀 1278·2019-08-30 15:55
閱讀 3286·2019-08-29 13:29
閱讀 1452·2019-08-26 13:56
閱讀 3690·2019-08-26 13:37
閱讀 634·2019-08-26 13:33
閱讀 3425·2019-08-26 13:33
閱讀 2319·2019-08-26 13:33