欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于JS實現一個可拖拽的容器布局組件

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

1. 前言

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

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

2. 組件分析

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

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

3. DragLine

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

  • 內置拖拽能力,可配置拖拽開始和結束的回調函數。
  • 內置提示信息,可配置是否在第一次渲染時默認進行“可拖拽”的信息提示。

廢話不多說,直接上拖拽線條組件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'); // 設置標記
    setVisible(false); // 關閉彈窗
  };

  // 拖拽結束
  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();// 關閉拖拽提示框
    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}>關閉</Button>
        </div>
      )}
    >
      {line}
    </Tooltip>
  ) : line;
});

export default DragLine;

對應的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; // 內容不可選擇
}

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

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

4. DragContainer

有了 DragLine 這個基礎組件后,我們就可以很容易的去擴展任何需要拖拽的上層組件了,比如我們來實現一個可拖拽的多列布局容器組件,直接上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);

  // 拖拽結束時,保存寬度信息
  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}`);

    // 拖動距離 = 分割線的位置 - 鼠標的位置
    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('#') || []; // 獲取本地已經保存的寬度信息
  contentList.forEach((d, i) => {
    contentData.push(
      <div
        key={`${sceneKey}_${i}`}
        className={`container-item item${i}`}
        style={{ flexBasis: `var(--drag-childWidth-${sceneKey}-${i}, ${localWidthList[i]})` }}
      >vvxyksv9kd
      </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;

對應樣式文件如下:

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

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

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

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

5. 使用效果

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

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

6. 總結

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

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

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

相關文章

最新評論