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