zoukankan      html  css  js  c++  java
  • 实现可调整宽高的DIV(左右拖动和上下拖动)

    前言

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

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

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

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

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

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

    方案的思路

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

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

    .left{
       500px;
      height: 100%;
      float: left;
      position: relative;
    }
    
    .v-resize{
      height: 100%;
       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,
           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={{  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{
       500px;
      height: 100%;
      float: left;
      position: relative;
    }
    
    .left-top{
      position: absolute;
      top: 0;
      bottom: 104px;
       100%;
      background-color: lightblue;
    }
    
    .h-resize{
      height: 4px;
       100%;
      background: #fff;
      position: absolute;
      bottom: 100px;
      z-index: 1;
      cursor: row-resize;
      user-select: none;
    }
    
    .left-bottom{
      position: absolute;
      bottom: 0;
       100%;
      height: 100px;
      background-color: lightgreen;
    }
    
    .v-resize{
      height: 100%;
       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地址

  • 相关阅读:
    前端页面实现报警器提示音效果
    Bootstrap相关优质项目学习清单
    Bootstrap相关优质项目学习清单
    [慕课笔记] node+mongodb建站攻略
    基础知识(11)- 异常、断言、日志和调试
    洛谷 P2580 于是他错误的点名开始了
    codevs 4189 字典
    HDU 1251 统计难题
    HDU 1827 Summer Holiday
    HDU 3836 Equivalent Sets
  • 原文地址:https://www.cnblogs.com/vvjiang/p/10117994.html
Copyright © 2011-2022 走看看