亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

基于JS實現(xiàn)一個可拖拽的容器布局組件

 更新時間:2023年12月19日 11:00:18   作者:Yikuns  
這篇文章主要為大家詳細(xì)介紹了如何基于JavaScript實現(xiàn)一個可拖拽的容器布局組件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

1. 前言

某一天,產(chǎn)品經(jīng)理給我提了這樣一個需求:產(chǎn)品概覽頁是一個三列布局的結(jié)構(gòu),我希望用戶能夠自己拖動列與列之間的分割線,實現(xiàn)每列的寬度自定義,國際站用戶就經(jīng)常有這樣的需求。效果類似這樣:

就這?簡單啊,不就是拖拽嗎?使用開源拖拽庫,回調(diào)里面給相關(guān)容器設(shè)置一下寬度即可,幾行代碼就搞定了。...不對,這是新同學(xué)才應(yīng)該有的想法,但我是一個老前端啊,后來我又想了一下,如果我實現(xiàn)了上面的功能,那兩列布局、三列布局、不管幾列布局都應(yīng)該可以拖拽啊,那頁面左邊的菜單,右邊彈出的抽屜也可以讓用戶拖拽啊,嗯...那就做成一個組件吧,讓我們來優(yōu)雅的實現(xiàn)它。

2. 組件分析

我們先分析一下,不管是兩列布局、三列布局、菜單、抽屜,最后拖拽的其實都是一根線,所以首先我們需要封裝一個拖拽線條的組件,有了這個組件,再實現(xiàn)任何布局拖拽寬度自定義的功能就簡單很多了:

使用開源庫還是自己實現(xiàn),也是我考慮的一個問題,我最終還是選擇了自己實現(xiàn),原因主要有兩點:第一是現(xiàn)在的開源得三方包體積都比較大,我們的業(yè)務(wù)組件是項目必須引用的資源,資源當(dāng)然是越小越好;第二是我們這個功能比較簡單,自己實現(xiàn)代碼可控,還可以實用一些新特性讓性能做到最優(yōu)。

3. DragLine

DragLine組件主要包括哪些能力呢?

  • 內(nèi)置拖拽能力,可配置拖拽開始和結(jié)束的回調(diào)函數(shù)。
  • 內(nèi)置提示信息,可配置是否在第一次渲染時默認(rèn)進行“可拖拽”的信息提示。

廢話不多說,直接上拖拽線條組件DragLine的代碼:

// DragLine.js
import React, { useEffect, useRef, useState } from 'react';
import { Button, Tooltip } from 'antd';
import './index.scss';


const DragLine = ((props) => {
  const {
    gap = 16,
    onMouseMove,
    onMouseUp,
    style = {},
    tipKey,
    defaultShowTip = false,
    ...rest
  } = props;
  const [visible, setVisible] = useState(defaultShowTip);
  const ref = useRef(null);
  const eventRef = useRef({});

  const closeNavTips = () => {
    localStorage.setItem(tipKey, 'true'); // 設(shè)置標(biāo)記
    setVisible(false); // 關(guān)閉彈窗
  };

  // 拖拽結(jié)束
  const handleMouseUp = (e) => {
    document.body.classList.remove('dragging');
    onMouseUp && onMouseUp(e, ref.current);
    document.removeEventListener('mousemove', eventRef.current.mouseMoveHandler, false);
    document.removeEventListener('mouseup', eventRef.current.mouseUpHandler, false);
  };

  // 拖拽中
  const handleMouseMove = (e) => {
    onMouseMove && onMouseMove(e, ref.current);
  };

  // 開始拖拽
  const handleMouseDown = () => {
    closeNavTips();// 關(guān)閉拖拽提示框
    document.body.classList.add('dragging');
    eventRef.current.mouseMoveHandler = (e) => handleMouseMove(e);
    eventRef.current.mouseUpHandler = (e) => handleMouseUp(e);
    document.addEventListener('mousemove', eventRef.current.mouseMoveHandler, false);
    document.addEventListener('mouseup', eventRef.current.mouseUpHandler, false);
  };

  const line = (
    <div
      ref={ref}
      style={{
        '--drag-gap': `${gap}px`,
        ...style,
      }}
      className={`drag-line ${visible ? 'active' : ''}`}
      onMouseDown={handleMouseDown}
      {...rest}
    />
  );

  return visible ? (
    <Tooltip
      open
      placement="rightTop"
      title={(
        <div>
          <div style={{ marginBottom: 4 }}>拖動這根線試試~</div>
          <Button size="small" onClick={closeNavTips}>關(guān)閉</Button>
        </div>
      )}
    >
      {line}
    </Tooltip>
  ) : line;
});

export default DragLine;

對應(yīng)的css代碼如下:

/* index.scss */
.drag-line {
  width: 2px;
  margin: 0 calc((var(--drag-gap, 16px) - 2px) / 2);
  background: transparent;
  cursor: col-resize;

  &.active, &:hover {
    background: blue;
  }
}

.dragging {
  user-select: none; // 內(nèi)容不可選擇
}

上述代碼,拷貝后可以直接運行,我簡單說明其中幾點:

  • js文件53行,使用到了css變量,對應(yīng)css文件第4行,并通過calc函數(shù)可以實現(xiàn)很多復(fù)雜功能。
  • js文件42行,拖拽時給body增加類名,對應(yīng)css文件第14行,設(shè)置拖拽時body內(nèi)容不可選中,不然用戶會在拖拽時無意選中很多內(nèi)容,從而造成困惑。
  • 組件代碼非常簡單,并且內(nèi)部已經(jīng)封裝好了拖拽能力,以及彈出的提示框,只是拋出了幾個簡單的API給業(yè)務(wù)方使用即可,我們還可以根據(jù)實際需求進一部分封裝,比如線條的寬度、提示的內(nèi)容和位置等等。

4. DragContainer

有了 DragLine 這個基礎(chǔ)組件后,我們就可以很容易的去擴展任何需要拖拽的上層組件了,比如我們來實現(xiàn)一個可拖拽的多列布局容器組件,直接上DragContainer組件的源碼:

// DragContainer.js
import React, { useRef } from 'react';
import DragLine from '../DragLine';
import classnames from 'classnames';
import './index.scss';


const DragContainer = (props) => {
  const {
    className,
    sceneKey,
    minChildWidth = 150,
    contentList = [],
    gap = 16,
  } = props;

  const cls = classnames('drag-container', className);
  const ref = useRef(null);

  // 拖拽結(jié)束時,保存寬度信息
  const onMouseUp = () => {
    const widthList = contentList.map((_, i) => {
      const child = ref.current.querySelector(`.item${i}`);
      return `${child?.offsetWidth}px`;
    });
    localStorage.setItem(sceneKey, widthList.join('#'));
  };

  const onMouseMove = (event, node) => {
    const index = parseInt(node.getAttribute('data-index'));
    const leftElement = ref.current.querySelector(`.item${index}`);
    const rightElement = ref.current.querySelector(`.item${index + 1}`);

    // 拖動距離 = 分割線的位置 - 鼠標(biāo)的位置
    const dragOffset = node.getBoundingClientRect().left - event.clientX;
    const newLeftChildWidth = leftElement.offsetWidth - dragOffset;
    const newRightChildWidth = rightElement.offsetWidth + dragOffset;

    if (newLeftChildWidth >= minChildWidth && newRightChildWidth >= minChildWidth) {
      ref.current.style.setProperty(`--drag-childWidth-${sceneKey}-${index}`, `${newLeftChildWidth}px`);
      ref.current.style.setProperty(`--drag-childWidth-${sceneKey}-${index + 1}`, `${newRightChildWidth}px`);
    }
  };


  const contentData = [];
  const localWidthList = localStorage.getItem(sceneKey)?.split('#') || []; // 獲取本地已經(jīng)保存的寬度信息
  contentList.forEach((d, i) => {
    contentData.push(
      <div
        key={`${sceneKey}_${i}`}
        className={`container-item item${i}`}
        style={{ flexBasis: `var(--drag-childWidth-${sceneKey}-${i}, ${localWidthList[i]})` }}
      >ublnpf9mb
      </div>,
    );
    if (i < contentList.length - 1) {
      contentData.push(
        <DragLine
          key={`${sceneKey}_dragline_${i}`}
          onMouseMove={onMouseMove}
          onMouseUp={onMouseUp}
          tipKey="draggableContainerFlag"
          data-index={i}
          defaultShowTip={i === 0}
          gap={gap}
        />,
      );
    }
  });

  return (
    <div ref={ref} className={cls}>
      {contentData}
    </div>
  );
};


export default DragContainer;

對應(yīng)樣式文件如下:

/* index.scss */
.drag-container {
  display: flex;
  align-items: stretch;
  width: 100%;

  .container-item {
    height: 100%;
    overflow: hidden;
    flex: 1; // 同比例放大縮小
  }
}

DragContainer組件的實現(xiàn)邏輯也比較簡單,基本思路如下:

  • 根據(jù)傳入的contentList進行一個循環(huán),如果不是最后一個child,則多渲染一個DragLine,用以拖拽。
  • 在拖拽線條的回調(diào)函數(shù)里,進行一個拖拽偏移和左右子元素新寬度的計算,再設(shè)置到css變量中,從而實現(xiàn)拖拽寬度實時變化的效果。并且代碼中沒有用到任何React State,不需要重復(fù)渲染整個組件,改變寬度直接使用css實現(xiàn),性能也比較好。
  • css文件第10行,對flex布局的子元素設(shè)置flex: 1,意思是當(dāng)我們拖動瀏覽器窗口大小時,子元素的寬度會同比例放大縮小,就能實現(xiàn)寬度自適應(yīng)了,但這里有個前提是,子元素寬度不要寫死,而是配合js文件第53行的flexBasis屬性一起使用。
  • 上述代碼拷貝后也是可以直接運行的,需要的同學(xué)可以直接試試。

5. 使用效果

我們在業(yè)務(wù)代碼中使用DragContainer組件寫個例子,使用簡單,效果完美:

<DragContainer
  sceneKey="overview-page"
  contentList={[
    <Card>111</Card>,
    <Card>222</Card>,
    <Card>333</Card>,
  ]}
/>

6. 總結(jié)

其實本文我最想要表達(dá)的是,當(dāng)我們接到一個需求之后,先學(xué)會分析和過濾,如果是特定的業(yè)務(wù)需求,實現(xiàn)即可,如果是通用類需求,就要慢慢學(xué)會從組件開發(fā)的角度去思考,是否能夠舉一反三,通過組件開發(fā)去覆蓋解決更多的場景和問題。另外是在功能的實現(xiàn)方面,主要總結(jié)以下幾點:

  • 要能夠通過對比選擇最合適自己的技術(shù),比如簡單的拖拽功能完全可以使用原生js來做,而不是引入一個超大的三方包。
  • 容器寬度的改變可以直接修改css屬性,而不是使用React狀態(tài),減少不必要的重復(fù)渲染。
  • css variable技術(shù),是打通js和css的一種手段。
  • flex布局相關(guān)屬性的熟練使用,可以以更優(yōu)的方案來解決一些布局問題。

到此這篇關(guān)于基于JS實現(xiàn)一個可拖拽的容器布局組件的文章就介紹到這了,更多相關(guān)JS可拖拽容器布局組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論