实现可调整宽高的DIV(左右拖动和上下拖动)

时间:2024-03-12 10:51:39

前言

本例是在React中实现,不过改一改通过原生js也很好实现,另外兼容性也做到了IE9。(IE8讲道理也是可以的)。

首先看一下需要实现的需求:

要拖动图中的白色横条调整绿色和蓝色区域的高度,要拖动白色竖条调整左边区域和红色区域的宽度。

一两年前曾经遇到过这个需求,当时直接在网上搜了个解决方案贴上去了,不过那个解决方案很挫。

这次的项目又遇到这个需求,而且是三个块的拖动。不仅需要左右拖动还需要上下拖动。

在这里特地记录下解决方案,也希望可以得到一些反馈与优化。

方案的思路

横条拖动和竖条拖动原理差不多,那就先来实现竖条左右拖动调整宽度。

水平方向的布局是通过以下方式实现:

.left{
  width: 500px;
  height: 100%;
  float: left;
  position: relative;
}

.v-resize{
  height: 100%;
  width: 4px;
  position: absolute;
  background: #fff;
  left: 500px;
  z-index: 2;
  cursor: col-resize;
  user-select: none;
}

.right{
  margin-left: 504px;
  background-color: lightsalmon;
  height: 100%;
}

通过样式我们可以了解到,只要同时改变left块的宽度,白色竖条v-resize的left定位和right块的定位(这个数相差不大,我们将这个数定义为vNum),即可实现水平拖动的效果。

通过鼠标按下白色竖条,开启水平拖动,监控鼠标位置,计算vNum,在鼠标放开或者鼠标移出拖动区域时停止水平拖动。

计算vNum的本质实际上就是通过鼠标位置减去拖动区域离浏览器左侧位置,从而得到vNum。

因为每块区域都有最大和最小宽度的限制,这里仅仅加了个vNumLimit来进行限制,即竖条最少要离最左侧和最右侧的距离。

这里还要考虑到每次窗口resize时,各个参数可能都会有所调整,所以加了窗口resize的处理,当然也加了防抖。

本来想要分步讲解,但是冬寒人乏,懒病发作。

方案的实现

jsx部分

import React, { Component } from \'react\'
import _ from \'underscore\'
import styles from \'./index.css\'

// 可调整宽高的Div
export default class ResizeDiv extends Component {
  state = {
    isHResize: false,
    isVResize: false,
    hNum: 100,
    vNum: 500,
    hNumLimit: 30,
    vNumLimit: 30
  }

  resizeOffsetInfo = {
    clientTop: 0,
    clientLeft: 0
  }

  leftHeight = 0

  containerWidth = 0

  componentDidMount() {
    this.initResizeInfo()
    const throttled = _.throttle(() => {
      this.initResizeInfo()
    }, 200)

    window.onresize = throttled
  }
  componentWillUnmount() {
    window.onresize = null
  }

  /**
  * 初始化resize信息
  */
  initResizeInfo = () => {
    const hEle = document.getElementById(\'h_resize_container\')
    this.resizeOffsetInfo = this.getEleOffset(hEle)
    this.leftHeight = hEle.offsetHeight
    this.containerWidth = document.getElementById(\'v_resize_container\').offsetWidth
  }

  /**
  * 获取元素的偏移信息
  */
  getEleOffset(ele) {
    var clientTop = ele.offsetTop
    var clientLeft = ele.offsetLeft
    let current = ele.offsetParent
    while (current !== null) {
      clientTop += current.offsetTop
      clientLeft += current.offsetLeft
      current = current.offsetParent
    }
    return {
      clientTop,
      clientLeft,
      height: ele.offsetHeight,
      width: ele.offsetWidth
    }
  }

  /**
  * 开始拖动水平调整块
  */
  hResizeDown = () => {
    this.setState({
      isHResize: true
    })
  }

  /**
  * 拖动水平调整块
  */
  hResizeOver = (e) => {
    const { isHResize, hNum, hNumLimit } = this.state
    if (isHResize && hNum >= hNumLimit && (this.resizeOffsetInfo.height - hNum >= hNumLimit)) {
      let newValue = this.resizeOffsetInfo.clientTop + this.resizeOffsetInfo.height - e.clientY
      if (newValue < hNumLimit) {
        newValue = hNumLimit
      }
      if (newValue > this.resizeOffsetInfo.height - hNumLimit) {
        newValue = this.resizeOffsetInfo.height - hNumLimit
      }
      this.setState({
        hNum: newValue
      })
    }
  }

  /**
  * 开始拖动垂直调整块
  */
  vResizeDown = () => {
    this.setState({
      isVResize: true
    })
  }

  /**
  * 拖动垂直调整块
  */
  vResizeOver = (e) => {
    const { isVResize, vNum, vNumLimit } = this.state
    if (isVResize && vNum >= vNumLimit && (this.containerWidth - vNum >= vNumLimit)) {
      let newValue = e.clientX - this.resizeOffsetInfo.clientLeft
      if (newValue < vNumLimit) {
        newValue = vNumLimit
      }
      if (newValue > this.containerWidth - vNumLimit) {
        newValue = this.containerWidth - vNumLimit
      }
      this.setState({
        vNum: newValue
      })
    }
  }

  /**
  * 只要鼠标松开或者离开区域,那么就停止resize
  */
  stopResize = () => {
    this.setState({
      isHResize: false,
      isVResize: false
    })
  }

  render() {
    const hCursor = this.state.isHResize ? \'row-resize\' : \'default\'
    const hColor = this.state.isHResize ? \'#ddd\' : \'#fff\'
    const vCursor = this.state.isVResize ? \'col-resize\' : \'default\'
    const vColor = this.state.isVResize ? \'#ddd\' : \'#fff\'

    return (
      <div className={styles[\'container\']} onMouseUp={this.stopResize} onMouseLeave={this.stopResize}>
        <div id=\'v_resize_container\' className={styles[\'content\']} onMouseMove={this.vResizeOver}>
          <div id=\'h_resize_container\' style={{ width: this.state.vNum, cursor: vCursor }} className={styles[\'left\']}
            onMouseMove={this.hResizeOver}>
            <div style={{ bottom: this.state.hNum, cursor: hCursor }} className={styles[\'left-top\']}>aasd</div>
            <div style={{ bottom: this.state.hNum, backgroundColor: hColor }} draggable={false} onMouseDown={this.hResizeDown} className={styles[\'h-resize\']} />
            <div style={{ height: this.state.hNum + 4, cursor: hCursor }} className={styles[\'left-bottom\']}>asd</div>
          </div>
          <div style={{ left: this.state.vNum, backgroundColor: vColor }} draggable={false} onMouseDown={this.vResizeDown} className={styles[\'v-resize\']} />
          <div style={{ marginLeft: this.state.vNum + 4, cursor: vCursor }} className={styles[\'right\']}>
            asdas
          </div>
        </div>
      </div>
    )
  }
}

css部分

.container{
  margin: 30px;
  overflow: hidden;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

.content{
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  min-height: 300px;
}

.left{
  width: 500px;
  height: 100%;
  float: left;
  position: relative;
}

.left-top{
  position: absolute;
  top: 0;
  bottom: 104px;
  width: 100%;
  background-color: lightblue;
}

.h-resize{
  height: 4px;
  width: 100%;
  background: #fff;
  position: absolute;
  bottom: 100px;
  z-index: 1;
  cursor: row-resize;
  user-select: none;
}

.left-bottom{
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 100px;
  background-color: lightgreen;
}

.v-resize{
  height: 100%;
  width: 4px;
  position: absolute;
  background: #fff;
  left: 500px;
  z-index: 2;
  cursor: col-resize;
  user-select: none;
}

.right{
  margin-left: 504px;
  background-color: lightsalmon;
  height: 100%;
}

总结

技术上其实还是比较简单的,不过丝般润滑的左右移动还是挺有成就感的。

如果有更好的玩法还望不吝赐教。

这是这个demo的地址:demo地址