最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

1. 前言

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

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

2. 組件分析

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

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

3. DragLine

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

  • 內(nèi)置拖拽能力,可配置拖拽開始和結(jié)束的回調(diào)函數(shù)。
  • 內(nèi)置提示信息,可配置是否在第一次渲染時(shí)默認(rèn)進(jì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 }}>拖動(dòng)這根線試試~</div>
          <Button size="small" onClick={closeNavTips}>關(guān)閉</Button>
        </div>
      )}
    >
      {line}
    </Tooltip>
  ) : line;
});

export default DragLine;

對(duì)應(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)容不可選擇
}

上述代碼,拷貝后可以直接運(yùn)行,我簡(jiǎn)單說明其中幾點(diǎn):

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

4. DragContainer

有了 DragLine 這個(gè)基礎(chǔ)組件后,我們就可以很容易的去擴(kuò)展任何需要拖拽的上層組件了,比如我們來實(shí)現(xiàn)一個(gè)可拖拽的多列布局容器組件,直接上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é)束時(shí),保存寬度信息
  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}`);

    // 拖動(dòng)距離 = 分割線的位置 - 鼠標(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]})` }}
      >wppm3vysvbp
      </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;

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

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

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

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

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

5. 使用效果

我們?cè)跇I(yè)務(wù)代碼中使用DragContainer組件寫個(gè)例子,使用簡(jiǎn)單,效果完美:

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

6. 總結(jié)

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

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

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

相關(guān)文章

  • JS實(shí)現(xiàn)屏蔽shift,Ctrl,alt等功能鍵的方法

    JS實(shí)現(xiàn)屏蔽shift,Ctrl,alt等功能鍵的方法

    這篇文章主要介紹了JS實(shí)現(xiàn)屏蔽shift,Ctrl,alt等功能鍵的方法,涉及javascript針對(duì)鍵盤按鍵的獲取與操作技巧,需要的朋友可以參考下
    2015-06-06
  • JavaScript中DOM和BOM的區(qū)別與用處詳解

    JavaScript中DOM和BOM的區(qū)別與用處詳解

    這篇文章主要介紹了JavaScript中DOM和BOM的區(qū)別與用處,DOM和BOM是前端開發(fā)中常用的兩個(gè)概念,它們分別表示不同的對(duì)象模型,用于操作瀏覽器的不同部分,了解它們的區(qū)別和用處,有助于在前端開發(fā)中靈活使用?JavaScript?操作頁面和瀏覽器,需要的朋友可以參考下
    2024-09-09
  • JavaScript實(shí)現(xiàn)選項(xiàng)卡效果的分析及步驟

    JavaScript實(shí)現(xiàn)選項(xiàng)卡效果的分析及步驟

    這篇文章主要給大家介紹了關(guān)于JavaScript實(shí)現(xiàn)選項(xiàng)卡效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用JavaScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • js動(dòng)態(tài)刪除div元素基本思路及實(shí)現(xiàn)代碼

    js動(dòng)態(tài)刪除div元素基本思路及實(shí)現(xiàn)代碼

    這篇文章主要介紹了js動(dòng)態(tài)刪除div元素基本思路及實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2014-05-05
  • layui實(shí)現(xiàn)三級(jí)導(dǎo)航菜單

    layui實(shí)現(xiàn)三級(jí)導(dǎo)航菜單

    這篇文章主要為大家詳細(xì)介紹了layui實(shí)現(xiàn)三級(jí)導(dǎo)航菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • 微信小程序?qū)崿F(xiàn)手勢(shì)圖案鎖屏功能

    微信小程序?qū)崿F(xiàn)手勢(shì)圖案鎖屏功能

    這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)手勢(shì)圖案鎖屏功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • 百度空間的popup效果分析

    百度空間的popup效果分析

    自從用firefox瀏覽器以來,就幾乎養(yǎng)成了一個(gè)習(xí)慣,就想用WebDeveloper把一些漂亮網(wǎng)站的js包括css給down下來分析一下,用來學(xué)習(xí)。
    2008-10-10
  • JavaScript模擬實(shí)現(xiàn)繼承的方法

    JavaScript模擬實(shí)現(xiàn)繼承的方法

    這篇文章主要介紹了JavaScript模擬實(shí)現(xiàn)繼承的方法,實(shí)例分析了javascript類的操作與模擬實(shí)現(xiàn)繼承的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • javascript改變this指向的方法匯總

    javascript改變this指向的方法匯總

    這篇文章主要介紹了javascript改變this指向的方法匯總的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • JavaScript使用多線程實(shí)現(xiàn)一個(gè)大文件上傳

    JavaScript使用多線程實(shí)現(xiàn)一個(gè)大文件上傳

    這篇文章主要為大家詳細(xì)介紹了JavaScript使用多線程實(shí)現(xiàn)一個(gè)大文件上傳的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2024-11-11

最新評(píng)論

新郑市| 屏南县| 遵义市| 临沂市| 特克斯县| 德昌县| 乳山市| 柯坪县| 舒城县| 成都市| 台南市| 曲阳县| 体育| 富裕县| 新宁县| 沙坪坝区| 新泰市| 鲁甸县| 延庆县| 博客| 平遥县| 开平市| 白朗县| 临漳县| 综艺| 利川市| 乐昌市| 河源市| 沁水县| 塔城市| 刚察县| 金沙县| 仁化县| 呼图壁县| 莒南县| 平度市| 梁河县| 林口县| 涿鹿县| 福州市| 开原市|