最近遇到一个需求,页面展示两块内容,需要通过拖拽可以动态改变大小,如下图:
-
实现思路:其实就是改变
div
样式的width
,本质上就是Dom操作。 -
完整代码:(基于
vue2
项目实践)
<template><div class="box" ref="boxRef"><div class="left" ref="leftRef">左侧内容,默认展示30%,默认最小宽度200px</div><div class="resize" ref="resizeRef"></div><div class="right" ref="rightRef">右侧内容,默认展示70%,默认最小宽度200px</div></div>
</template><script>
export default {data() {return {};},mounted() {this.handleResize();},methods: {handleResize(leftMinWidth = 200, rightMinWidth = 200) {// 获取Dom节点const boxDom = this.$refs.boxRef,leftDom = this.$refs.leftRef,resizeDom = this.$refs.resizeRef,rightDom = this.$refs.rightRef;resizeDom.onmousedown = e => {const startX = e.clientX; // 记录坐标起始位置leftDom.left = leftDom.offsetWidth; // 左边元素起始宽度document.onmousemove = e => {const endX = e.clientX; // 鼠标拖动的终止位置let moveLen = leftDom.left + (endX - startX); // 移动的距离 = endX - startX,左边区域最后的宽度 = resizeDom.left + 移动的距离const maxWidth = boxDom.clientWidth - resizeDom.offsetWidth; // 左右两边区域的总宽度 = 外层容器宽度 - 中间区域拖拉框的宽度// 限制左边区域的最小宽度为 leftMinWidthif (moveLen < leftMinWidth) {moveLen = leftMinWidth;}// 右边区域最小宽度为 rightMinWidthif (moveLen > maxWidth - rightMinWidth) {moveLen = maxWidth - rightMinWidth;}leftDom.style.width = (moveLen / maxWidth) * 100 + "%"; // 设置左边区域的宽度,通过换算为百分比的形式,实现窗体放大缩小自适应rightDom.style.width = ((maxWidth - moveLen) / maxWidth) * 100 + "%"; // 右边区域 = 总大小 - 左边宽度 - 拖动条宽度};document.onmouseup = () => {document.onmousemove = null;document.onmouseup = null;resizeDom.releaseCapture && resizeDom.releaseCapture(); // 鼠标捕获释放};resizeDom.setCapture && resizeDom.setCapture(); // 启用鼠标捕获return false;};}}
};
</script><style lang="less" scoped>
.box {width: 100%;height: 300px;display: flex;
}
.left {width: 30%;background-color: #f1eab3;border: 1px solid #dcdfe6;
}
.resize {position: relative;width: 5px;cursor: col-resize;background-size: cover;background-position: center;&:hover {background-color: #45a3ff;}
}
.right {width: 70%;background-color: #b5ef8f;border: 1px solid #dcdfe6;
}
</style>
-
扩展:同时支持上下/左右拖拽
-
实现原理和左右拖拽是一样的,只不过改变的样式是
div
的height
-
完整代码如下:
<template><div class="box" ref="boxRef" id="box"><div class="left" ref="leftRef">左侧,默认width=30%,minWidth=200px</div><div class="resize" ref="resizeRef"></div><div class="right" ref="rightRef"><div class="top" ref="topRef">右侧,默认width=70%,minWidth=200px,头部区域,默认minHeight=50px</div><div class="resizeY" ref="resizeYRef"></div><div class="content" ref="contentRef">右侧,默认width=70%,minWidth=200px,内容区域,默认minHeight=50px</div></div></div>
</template><script>
export default {data() {return {};},mounted() {this.handleResize();this.handleYResize();},methods: {handleResize(leftMinWidth = 200, rightMinWidth = 200) {// 获取Dom节点const boxDom = this.$refs.boxRef,leftDom = this.$refs.leftRef,resizeDom = this.$refs.resizeRef,rightDom = this.$refs.rightRef;resizeDom.onmousedown = e => {const startX = e.clientX; // 记录坐标起始位置leftDom.left = leftDom.offsetWidth; // 左边元素起始宽度document.onmousemove = e => {const endX = e.clientX; // 鼠标拖动的终止位置let moveLen = leftDom.left + (endX - startX); // 移动的距离 = endX - startX,左边区域最后的宽度 = resizeDom.left + 移动的距离const maxWidth = boxDom.clientWidth - resizeDom.offsetWidth; // 左右两边区域的总宽度 = 外层容器宽度 - 中间区域拖拉框的宽度// 限制左边区域的最小宽度为 leftMinWidthif (moveLen < leftMinWidth) {moveLen = leftMinWidth;}// 右边区域最小宽度为 rightMinWidthif (moveLen > maxWidth - rightMinWidth) {moveLen = maxWidth - rightMinWidth;}leftDom.style.width = (moveLen / maxWidth) * 100 + "%"; // 设置左边区域的宽度,通过换算为百分比的形式,实现窗体放大缩小自适应rightDom.style.width = ((maxWidth - moveLen) / maxWidth) * 100 + "%"; // 右边区域 = 总大小 - 左边宽度 - 拖动条宽度};document.onmouseup = () => {document.onmousemove = null;document.onmouseup = null;resizeDom.releaseCapture && resizeDom.releaseCapture(); // 鼠标捕获释放};resizeDom.setCapture && resizeDom.setCapture(); // 启用鼠标捕获return false;};},handleYResize(minTopHeight = 50, minContentHeight = 50) {const boxDom = this.$refs.boxRef,topDom = this.$refs.topRef,resizeDom = this.$refs.resizeYRef,contentDom = this.$refs.contentRef;resizeDom.onmousedown = e => {const startY = e.clientY;resizeDom.top = resizeDom.offsetHeight;document.onmousemove = e => {const endY = e.clientY;let moveLen = resizeDom.top + (endY - startY);const maxHeight = boxDom.clientHeight - resizeDom.offsetHeight;if (moveLen < minTopHeight) {moveLen = minTopHeight;}if (moveLen > maxHeight - minContentHeight) {moveLen = maxHeight - minContentHeight;}topDom.style.height = (moveLen / maxHeight) * 100 + "%";contentDom.style.height =((maxHeight - moveLen) / maxHeight) * 100 + "%";};document.onmouseup = () => {document.onmousemove = null;document.onmouseup = null;resizeDom.releaseCapture && resize.releaseCapture();};resizeDom.setCapture && resizeDom.setCapture();return false;};}}
};
</script><style lang="less" scoped>
.box {display: flex;width: 100%;height: 300px;
}
.left {width: 30%;background-color: #f1eab3;border: 1px solid #dcdfe6;
}
.resize {position: relative;width: 3px;height: 100%;cursor: col-resize;background-size: cover;background-position: center;&:hover {background-color: #45a3ff;}
}
.right {width: 70%;// background-color: #b5ef8f;// border: 1px solid #dcdfe6;overflow: hidden;.top {height: 20%;background-color: #b5ef8f;border: 1px solid #dcdfe6;}.resizeY {position: relative;height: 3px;cursor: row-resize;&:hover {background-color: #45a3ff;}}.content {height: 80%;background: #abc8ea;border: 1px solid #dcdfe6;}
}
</style>
知识点
1、属性:clientWidth / offsetWidth
clientWidth只读属性,返回元素的内部宽度,该属性包括内边距,但不包括垂直滚动条(如果有)、边框和外边距。
offsetWidth 只读属性,返回元素的布局宽度。该属性包含元素的边框、水平线上的内边距、竖直方向滚动条(如果有的话)、以及CSS设置的宽度(width)值。
例如:一个有滚动条的div
(clientWidth) 205 = 185(实际width) + 10 (padding) + 10 (padding)
(offsetWidth) 240 = 185(实际width) + 10 (padding) + 10 (padding) + 10 (border) + 10 (border) + 15 (scrollWidth)
2、方法:setCapture() / releaseCapture()
调用SetCapture()函数后,能够捕获鼠标相关事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onmouseout
,一般使用onmousemove
和onmouseup
两个事件。
当不再需要继续获得鼠标消息就要应该调用releaseCapture()释放掉,否则别的线程想调用就会失败。所以:setCapture()
和releaseCapture()
必须成对呈现!
3、 鼠标事件
事件名称 | 含义 |
---|---|
mousedown | 鼠标按下 |
mouseup | 鼠标释放 |
click | 左键单击 |
dbclick | 左键双击 |
mousemove | 鼠标移动 |
mouseover | 鼠标经过 |
mouseout | 鼠标滑出 |
mouseenter | 鼠标进入 |
mouseleave | 鼠标离开 |
contextmenu | 右键菜单 |
1)执行顺序:mousedown => mouseup => click
2)mouseover 和 mouseout子元素也会触发,可以冒泡触发。mouseenter 和mouseleave是针对侦听的对象触发,阻止了冒泡。
3)阻止鼠标的默认事件
e.preventDefault()
e.returnValue = false; // IE8 及以下兼容写法
return false; // IE兼容写法,只用作on事件阻止默认事件
- 事件对象属性:
clientX / clientY
点击位置距离当前body可视区域的x,y 坐标。