<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          動態(tài)列表組件 - 拖拽排序功能設計與實現(xiàn)

          共 6128字,需瀏覽 13分鐘

           ·

          2022-02-17 08:56

          大廠技術(shù)??高級前端??Node進階

          點擊上方?程序員成長指北,關(guān)注公眾號

          回復1,加入高級Node交流群

          最終效果

          原生實現(xiàn)原理

          關(guān)于拖拽

          標簽的圖片默認是可以拖動的(效果如上圖)

          然而其他的標簽(div等)是不能被拖動的,鼠標點擊選擇后移動沒有拖拽效果,需要添加屬性draggable="true" 使得元素可以被拖動。

          拖放過程觸發(fā)事件

          • 在拖動目標上觸發(fā)事件(源元素) :

            • ondragstart[1]- 用戶開始拖動元素時觸發(fā)
          • ondrag[2]- 元素正在拖動時觸發(fā)
          • ondragend[3]- 用戶完成元素拖動后觸發(fā)
          • 釋放目標時觸發(fā)的事件:

            • ondragenter - 當被鼠標拖動的對象進入其容器范圍內(nèi)時觸發(fā)此事件
          • ondragover[4]- 當某被拖動的對象在另一對象容器范圍內(nèi)拖動時觸發(fā)此事件
          • ondragleave[5]- 當被鼠標拖動的對象離開其容器范圍內(nèi)時觸發(fā)此事件
          • ondrop[6]- 在一個拖動過程中,釋放鼠標鍵時觸發(fā)此事件

          ps.需要注意是 ondragend 和 ondragover 的默認事件 Reset the current drag operation to "none".也就是說它的默認行是當ondragover觸發(fā)時,移動會失效(產(chǎn)生殘影),所以想讓一個元素可放置,需要阻止默認行為。

          element.ondragover?=?event?=>?{??????
          ????event.preventDefault();?????//?...?
          }

          一個簡單js原生拖拽demo

          思考思路

          image.png

          ondragstart

          獲得源元素

          ondrag

          進行拖拽過程中的源元素樣式改變

          ondragend

          移除多余樣式,拿到ondragover中最后獲得的目標元素index,改變列表數(shù)組

          ondragover

          以較短時間內(nèi)生成快照,記錄源元素的拖動軌跡經(jīng)過了哪些元素,用來生成最終的目標元素,給途經(jīng)元素增加位移動畫樣式

          問題解決

          如何進行數(shù)據(jù)傳遞

          思路1:為每個item設置className=index${index},通過Number(event.target.classList.slice("5"))來取值,

          可以達到目標效果,但實現(xiàn)不太優(yōu)雅,所以放棄這個思路

          思路2:為每個item設置屬性data-index={index},通過event.target.dataset.index來取值,符合代碼的語義性,故采用這個方法

          嵌套DOM造成ondragover重復觸發(fā)

          ondragover函數(shù)會在源元素移動到目標元素范圍內(nèi)且不停止拖拽時反復觸發(fā),如果源元素非單層DOM結(jié)構(gòu)時,每一層DOM都會觸發(fā)ondragover,造成數(shù)據(jù)混亂

          下圖控制臺第一個紅框的數(shù)據(jù)是拖拽item1時打印,第二個紅框是拖拽item2時打印,可以發(fā)現(xiàn)item1的兩層DOM結(jié)構(gòu)觸發(fā)了兩次打印:

          html>

          <html?lang="zh">
          ??<head>
          ????<meta?charset="UTF-8"?/>

          ????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>

          ????<meta?http-equiv="X-UA-Compatible"?content="ie=edge"?/>

          ????<title>Documenttitle>

          ????<style>
          ??????.wrapper?{
          ????????display:?flex;
          ????????border:?1px?solid?orangered;
          ????????padding:?10px;
          ??????}

          ??????.col?{
          ????????border:?1px?solid?#808080;
          ????????height:?500px;
          ????????width:?200px;
          ????????margin:?0?10px;
          ????????padding:?10px;
          ??????}

          ??????.item?{
          ????????border:?1px?solid?#808080;
          ????????margin:?5px?0;
          ??????}
          ????
          style>
          ??head>

          ??<body>
          ????<div?class="wrapper">
          ??????<div?class="col1?col">
          ????????<div?class="item"?id="item1"?draggable="true">
          ??????????<div>item1div>
          ????????div>

          ????????<div?class="item"?id="item2"?draggable="true">item2div>
          ??????div>
          ????div>

          ????<script>
          ??????let?cols?=?document.getElementsByClassName("col");

          ??????for?(let?col?of?cols)?{
          ????????col.ondragover?=?(e)?=>?{
          ??????????e.preventDefault();

          ??????????console.log(e.target);
          ????????};
          ??????}
          ????
          script>
          ??body>
          html>

          解決這個問題的方法就是放棄使用e.target,改用e.currentTarget

          target和currentTarget的概念:

          1、target發(fā)生在事件流的目標階段,而currentTarget發(fā)生在事件流的整個階段(捕獲、目標和冒泡階段)

          2、只有當目標流處于目標階段的時候才相同。

          3、而當事件流處于捕獲和冒泡階段時,target指向被點擊的對象,而currentTarget指向當前事件活動的對象,通常是事件的祖元素。

          拖拽結(jié)束后出現(xiàn)重影

          所謂重影就是我們在拖動到指定位置后,頁面我們拖動的元素先慢慢漂移到了初始位置,隨后才更新到了我們指定的位置

          屏幕錄制2021-03-15 下午9.gif

          解決方法是在上層父元素上添加方法

          onDragOver``={(e:) => {

          e.``preventDefault``();

          }}>

          阻止默認的禁止拖拽行為(慢慢漂移到了初始位置就是禁止默認拖拽行為的體現(xiàn))

          合適的拖拽過渡動畫

          讓畫面出現(xiàn)一個上移或下移"讓位"的效果

          .drag-up?{
          ??animation:?dragup?ease?0.1s?1;

          ??animation-fill-mode:?forwards;
          }

          .drag-down?{
          ??animation:?dragdown?ease?0.1s?1;

          ??animation-fill-mode:?forwards;
          }

          @keyframes?dragup?{
          ??from?{
          ????margin-top:?10px;
          ??}

          ??to?{
          ????margin-top:?50px;
          ??}
          }

          @keyframes?dragdown?{
          ??from?{
          ????margin-bottom:?10px;

          ????margin-top:?50px;
          ??}

          ??to?{
          ????margin-bottom:?50px;

          ????margin-top:?10px;
          ??}
          }

          其他第三方庫

          名稱react-dndreact-beautiful-dndreact-sortable-hoc
          包體積較大大(>100k)較小
          TS支持
          維護情況良好良好一年以上未曾更新
          傳參方式event.target.datasetevent.target.datasetref
          使用難度學習成本較高學習成本較低學習成本較低
          核心實現(xiàn)html5 drag APIhtml5 drag APIhtml5 mouse API
          倉庫地址https://github.com/react-dnd/react-dndhttps://github.com/atlassian/react-beautiful-dndhttps://github.com/clauderic/react-sortable-hoc/issues

          其實除了最后一個庫,其他兩個比較著名的庫體積都不是很小,所以簡單需求原生實現(xiàn)還是有必要的

          react-dnd

          https://codesandbox.io/s/github/react-dnd/react-dnd/tree/gh-pages/examples_hooks_js/01-dustbin/single-target?from-embed=&file=/src/Box.jsx:437-440(體驗demo)

          React DnD建立在 HTML5 drag and drop API[7]之上。因為它可以對已拖動的DOM節(jié)點進行屏幕快照,并將其用作“拖動預覽”,這樣你就不必在光標移動時進行任何繪制相關(guān)的操作,同時這個API也是處理文件刪除事件的唯一方法。

          但是,HTML5拖放API也有一些缺點。它在觸摸屏上不起作用,并且與其他瀏覽器相比,它在IE上提供的定制化更少。

          這就是為什么在React DnD中以可插入方式實現(xiàn)HTML5 drag and drop API的原因。你不必非得用它,你完全可以根據(jù)原生的觸摸事件,鼠標事件等來封裝自己的實現(xiàn)。這種可插拔的實現(xiàn)在React DnD中稱為Backends。該庫現(xiàn)在只在HTML backend中使用,未來可能會有更多地方會用到。

          backends的作用類似于React的綜合事件系統(tǒng):它們都是把瀏覽器差異抽象出來并處理本地的DOM事件。不同的是,Backends并不依賴React或者React的綜合事件系統(tǒng)。底層實現(xiàn)中,backends做的就是原生的Dom事件轉(zhuǎn)換成React DnD能處理的內(nèi)部 Reduxactions

          react-beautiful-dnd

          https://react-beautiful-dnd.netlify.app/?path=/story/single-vertical-list--basic(體驗demo)

          react-sortable-hoc

          http://clauderic.github.io/react-sortable-hoc/#/react-virtualized/elements-of-varying-heights?_k=dbj6xa(體驗demo)

          核心代碼

          const?[dragged,?setDragged]?=?useState<any>();

          const?[over,?setOver]?=?useState<any>();

          const?[draggable,?setDraggable]?=?useState(false);

          const?dragStart?=?(e:?any)?=>?{
          ??e.currentTarget.style.backgroundColor?=?"#fafafa";

          ??setDragged(e.currentTarget);
          };

          const?dragEnd?=?(e:?any)?=>?{
          ??e.preventDefault();

          ??e.target.style.display?=?"flex";

          ??e.target.classList.remove("drag-up");

          ??over.classList.remove("drag-up");

          ??e.target.classList.remove("drag-down");

          ??over.classList.remove("drag-down");

          ??const?from?=?cloneDeep(value[dragged.dataset.index]);

          ??const?to?=?cloneDeep(value[over.dataset.index]);

          ??splice(dragged.dataset.index,?1,?to);

          ??splice(over.dataset.index,?1,?from);

          ??e.target.style.opacity?=?"1";

          ??e.target.style.backgroundColor?=?"";
          };

          const?dragOver?=?(e:?any)?=>?{
          ??e.preventDefault();

          ??const?dgIndex?=?dragged.dataset.index;

          ??const?taIndex?=?e.currentTarget.dataset.index;

          ??const?animateName?=?dgIndex?>?taIndex???"drag-up"?:?"drag-down";

          ??if?(over?&&?e.currentTarget.dataset.index?!==?over.dataset.index)?{
          ????over.classList.remove("drag-up",?"drag-down");
          ??}

          ??if?(!e.currentTarget.classList.contains(animateName))?{
          ????e.currentTarget.classList.add(animateName);

          ????setOver(e.currentTarget);
          ??}
          };

          {items.map((item,?index)?=>?{
          ??return?(???draggable={draggable}
          ???data-index={index}
          ???onDragStart={e?=>?{
          ?????dragStart(e);
          ???}}
          ???onDrag={(e:?any)?=>?{
          ?????e.preventDefault();
          ?????e.target.style.opacity?=?'0';
          ???}}
          ???onDragOver={dragOver}
          ???onDragEnd={e?=>?{
          ?????dragEnd(e);
          ???}}>
          //你的列表數(shù)組數(shù)據(jù)???
          )
          }

          Node 社群



          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學習感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學習、共建。下方加 考拉 好友回復「Node」即可。



          如果你覺得這篇內(nèi)容對你有幫助,我想請你幫我2個小忙:

          1. 點個「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客?www.inode.club?讓我們一起成長

          點贊和在看就是最大的支持??

          參考資料

          [1]

          ondragstart: https://www.runoob.com/jsref/event-ondragstart.html

          [2]

          ondrag: https://www.runoob.com/jsref/event-ondrag.html

          [3]

          ondragend: https://www.runoob.com/jsref/event-ondragend.html

          [4]

          ondragover: https://www.runoob.com/jsref/event-ondragover.html

          [5]

          ondragleave: https://www.runoob.com/jsref/event-ondragleave.html

          [6]

          ondrop: https://www.runoob.com/jsref/event-ondrop.html

          [7]

          HTML5 drag and drop API: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

          瀏覽 67
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩av电影免费在线观看 | 97色色大香蕉 | 午夜无码中文字幕 | 小黄片免费 | 黄色动漫成人视频在线观看 |