本例是在React中实现,不过改一改经过原生js也很好实现,另外兼容性也作到了IE9。(IE8讲道理也是能够的)。css
首先看一下须要实现的需求:react
要拖动图中的白色横条调整绿色和蓝色区域的高度,要拖动白色竖条调整左边区域和红色区域的宽度。git
一两年前曾经遇到过这个需求,当时直接在网上搜了个解决方案贴上去了,不过那个解决方案很挫。github
此次的项目又遇到这个需求,并且是三个块的拖动。不只须要左右拖动还须要上下拖动。浏览器
在这里特意记录下解决方案,也但愿能够获得一些反馈与优化。布局
横条拖动和竖条拖动原理差很少,那就先来实现竖条左右拖动调整宽度。优化
水平方向的布局是经过如下方式实现:this
.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),便可实现水平拖动的效果。code
经过鼠标按下白色竖条,开启水平拖动,监控鼠标位置,计算vNum,在鼠标放开或者鼠标移出拖动区域时中止水平拖动。component
计算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地址