最近遇到一个需求,页面展示两块内容,需要通过拖拽可以动态改变大小,如下图:
-
实现思路:其实就是改变
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; // 左右两边区域的总宽度 = 外层容器宽度 - 中间区域拖拉框的宽度
// 限制左边区域的最小宽度为 leftMinWidth
if (moveLen < leftMinWidth) {
moveLen = leftMinWidth;
}
// 右边区域最小宽度为 rightMinWidth
if (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; // 左右两边区域的总宽度 = 外层容器宽度 - 中间区域拖拉框的宽度
// 限制左边区域的最小宽度为 leftMinWidth
if (moveLen < leftMinWidth) {
moveLen = leftMinWidth;
}
// 右边区域最小宽度为 rightMinWidth
if (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 坐标。