欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-05 11:28:29
前言 本例是在React中实现,不过改一改通过原生js也很好实现,另外兼容性也做到了IE9。(IE8讲道理也是可以的)。 首先看一下需要实现的需求: 要拖动图中的白色横条调整绿色和蓝色区域的高度,要拖动白色竖条调整左边区域和红色区域的宽度。 一两年前曾经遇到过这个需求,当时直接在网上搜了个解决方案贴 ......

前言

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

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

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

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

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

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

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

方案的思路

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

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

.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在github上的地址:demo地址