前端实现左右拖拽JS /CSS实现拖拽

3,342 阅读4分钟

写这篇文章主要是在项目中多次用到拖拽,当然也耗费了很多精力,尝试了很多种实现方法都存在着大大小小的问题。

最终,实验了两种拖拽:

  • js实现拖拽,由于我的项目是react框架,所以选择了react的组件
  • react-draggable
  • react-split-pane
  • css实现拖拽,比想象中的要好,而且相对加载也比较快

react-draggable

参考网站:

www.npmjs.com/package/rea…

juejin.cn/post/684490…

实现原理:

react-draggable 的拖拽通过CSS中的transform: translate(x, y)来实现目标元素的移动。

1. 安装

$ npm install react-draggable --save

2. 引用

import Draggable from 'react-draggable';

3. 案例

代码示例中是实现一个左右拖拽的例子,所以在属性中 axis="x" 是以x轴方向拖拽,在使用此组件时这个时必须设置的,当以上下拖拽的时候设置为 "y"

import React from 'react';
import ReactDOM from 'react-dom';
import Draggable from 'react-draggable';
 
class App extends React.Component {
 
  eventLogger = (e: MouseEvent, data: Object) => {
    console.log('Event: ', e);
    console.log('Data: ', data);
  };
 
  render() {
    return (
      <Draggable
        axis="x"
        handle=".handle"
        defaultPosition={{x: 0, y: 0}}
        position={null}
        grid={[25, 25]}
        scale={1}
        onStart={this.handleStart}
        onDrag={this.handleDrag}
        onStop={this.handleStop}>
        <div>
          <div className="handle">Drag from here</div>
          <div>This readme is really dragging on...</div>
        </div>
      </Draggable>
    );
  }
}
 
ReactDOM.render(<App/>, document.body);
属性:
allowAnyClick:  boolean // 默认false,设为true非左键可实现点击拖拽
axis: string // 'x':x轴方向拖拽、'y':y轴方向拖拽、'none':禁止拖拽
bounds: { left: number, top: number, right: number, bottom: number } | string 
    // 限定移动的边界,接受值:
    //(1)'parent':在移动元素的offsetParent范围内
    //(2)一个选择器,在指定的Dom节点内
    //(3){ left: number, top: number, right: number, bottom: number }对象,限定每个方向可以移动的距离
cancel:制定给一个选择器组织drag初始化,例如'.body'
defaultClassName:string // 拖拽ui类名,默认'react-draggable'
drfaultClassNameDragging:string // 正在拖拽ui类名,默认'eact-draggable-dragging'
defaultClassNameDragged:string //拖拽后的类名,默认'react-draggable-dragged'
defaultPosition:{ x: number, y: number } // 起始x和y的位置
disabled:boolean // true禁止拖拽任何元素
grid:[number, number] // 正在拖拽的网格范围
handle:string // 初始拖拽的的选择器'.handle'
offsetParent:HTMLElement // 拖拽的offsetParent
onMouseDown: (e: MouseEvent) => void // 鼠标按下的回调
onStart: DraggableEventHandler // 开始拖拽的回调
onDrag:DraggableEventHandler // 拖拽时的回调
onStop:DraggableEventHandler // 拖拽结束的回调
position: {x: number, y: number} // 控制元素的位置
positionOffset: {x: number | string, y: number | string} // 相对于起始位置的偏移
scale:number // 定义拖拽元素的缩放

代码示例

import React, { Component } from 'react';
import Draggable from 'react-draggable';
import styled from 'styled-components';

// 容器
const Container = styled.div`
  display: flex;
  justify-content: flex-start;
`;

// 左边内容部分
const LeftContent = styled.div`
  position: relative;
  width: ${props => props.width}px;
  height: 100vh;
  padding: 20px;
  background-color: #E6E6FA;
  overflow: hidden;
  flex-grow:1;
`;

// 拖拽部分
const DraggableBox = styled.div`
  position: absolute;
  left: ${props => props.left}px;
  top: 0;
  width: 5px;
  height: 100vh;
  background-color: ${props => props.background};
  cursor: col-resize;
  z-index: 1000;
`;

// 右边内容部分
const RightContent = styled.div`
  width: calc(100% - ${props => props.leftBoxWidth}px);
  height: 100vh;
  padding: 20px;
  background-color: #FFF;
  flex-grow:1;
  z-index: 100;
`;

const Li = styled.li`
  white-space: nowrap;
`;

class DraggableExp extends Component {
  state = {
    initialLeftBoxWidth: 150, // 左边区块初始宽度
    leftBoxWidth: 150, // 左边区块初始宽度
    leftBoxMinWidth: 100, // 左边区块最小宽度
    leftBoxMaxWidth: 300, // 左边区块最大宽度
    dragBoxBackground: 'transparent' // 拖拽盒子的背景色
  }

  // 拖动时设置拖动box背景色,同时更新左右box的宽度
  onDrag = (ev, ui) => {
    const { initialLeftBoxWidth } = this.state;
    const newLeftBoxWidth = ui.x + initialLeftBoxWidth;
    this.setState({
      leftBoxWidth: newLeftBoxWidth,
      dragBoxBackground: '#FFB6C1'
    });
  };

  // 拖拽结束,重置drag-box的背景色
  onDragStop = () => {
    this.setState({
      dragBoxBackground: 'transparent'
    });
  };

  render() {
    const { initialLeftBoxWidth, 
            leftBoxWidth, 
            leftBoxMinWidth, 
            leftBoxMaxWidth, 
            dragBoxBackground } = this.state;
    return (
      <Container>
        <LeftContent width={leftBoxWidth}>
          <h3 style={{paddingLeft: 20}}>目录</h3>
          <ul>
            <Li>目录1</Li>
            <Li>目录2</Li>
            <Li>目录3</Li>
            <Li>这是个非常长非常长非常长的目录</Li>
          </ul>
          <Draggable 
            axis="x"
            defaultPosition={{ x: 0, y: 0 }}
            bounds={{ left: leftBoxMinWidth - initialLeftBoxWidth, right: leftBoxMaxWidth - initialLeftBoxWidth }}
            onDrag={this.onDrag}
            onStop={this.onDragStop}>
            <DraggableBox
              left={initialLeftBoxWidth - 5} 
              background={dragBoxBackground} />
          </Draggable>
        </LeftContent>
        <RightContent leftBoxWidth={leftBoxWidth}>
          <h3>这里是内容块</h3>
        </RightContent>
      </Container>
    )
  }
}

export default DraggableExp;
  • 存在问题
  • 卡顿
  • 上下拖拽实现存在问题

放弃使用主要时因为我的项目中使用了iframe渲染,

axios调取数据后获取回来的数据利用iframe渲染,当渲染后的页面拖拽时会引起卡顿,而且在实现上下拖拽时存在一些问题,再加上拖拽利用多个state控制,传来传去的state影响了页面渲染

后期发现css实现的方式就果断放弃了

CSS实现拖拽

实现原理:

(1)理解textarea标签的原理

image.png

(2)利用overflow:hidden

(3)CSS的resize属性

我们知道,textarea时个文本域,可以进行对右下角拖拽使文本框改变大小,利用这个原理我们可以根据css的resize属性进行拖拽实现。

对于这个拖拽的区域比较小,所以我们要把这个区域变大,可以设置它的高度和盒子高度一样,并且使其opacity: 0;再加入一个可拖拽的线控制线的位置,让让我们认为拖拽的是那根线。

代码示例:

  • HTML:
<div class="wrapper">
  <div class="left">
    <div class="resizeBox"></div>     //拖拽的盒子,其实就类似于textarea的那个
    <div class="resizeLine"></div>   //拖拽线
    <div class="continer">        //拖拽盒子的内容
      左侧的内容,左侧的内容,左侧的内容,左侧的内容
    </div>

  </div>
  <div class="right">
    右侧的内容,右侧的内容,右侧的内容,右侧的内容
  </div>
</div>
  • css
 .wrapper {
      overflow: hidden;
    }
    .left {
      height: 400px;
      background-color: #fff;
      position: relative;
      float: left;
    }
    .right {
      height: 400px;
      padding: 16px;
      background-color: #eee;
      box-sizing: border-box;
      overflow: hidden;
    }
    .continer {
      position: absolute;
      top: 0; right: 5px; bottom: 0; left: 0;
      padding: 16px;
      overflow-x: hidden;
    }
    /*拖拽盒子,被隐藏的盒子*/
    .resizeBox {
      width: 300px; height: inherit;
      resize: horizontal;
      cursor: ew-resize;
      cursor: col-resize;
      opacity:0;
      overflow: scroll;
    }
    /* 拖拽线 */
    .resizeLine {
      position: absolute;
      right: 0; top: 0; bottom: 0;
      border-right: 2px solid #eee;
      border-left: 1px solid #bbb;
      pointer-events: none;
    }
    .resizeBox:hover ~ .resizeLine,
    .resizeBox:active ~ .resizeLine {
      border-left: 5px solid rgba(240,128,128, .4);
    }
    .resizeBox::-webkit-scrollbar {
      width: 300px; height: inherit;
    }

     //下面内容可不写
     /*Firefox只有下面一小块区域可以拉伸 */
    @supports (-moz-user-select: none) {
      .resizeBox:hover ~ .resizeLine,
      .resizeBox:active ~ .resizeLine {
        border-left: 1px solid #bbb;
      }
      .resizeBox:hover ~ .resizeLine::after,
      .resizeBox:active ~ .resizeLine::after {
        content: '';
        position: absolute;
        width: 16px; height: 16px;
        bottom: 0; right: -8px; 
      }
    }

实现效果

test.gif

react-split-pane

上面两种方法都是比较笨重,需要自己设置和写布局的,推荐一个不需要写布局,直接引用即可的组件。😶

这个比较简单,直接安装就可以用,而且不需要写这么多代码,后来才发现,我也是对自己无语了。

不过最终还是没采用这种方法,有些布局还是不太适应。

参考网站:

www.npmjs.com/package/rea…

github.com/angularsen/…

git上有案例,各种拖拽模板,可直接引用。

如有问题,还请多多指点。

希望可以帮到码友们! 

最后发现拖拽只要遇到iframe都会出现点问题,请参考:juejin.cn/post/684490…