通过拖拽动态调整div的大小

最近遇到一个需求,页面展示两块内容,需要通过拖拽可以动态改变大小,如下图:
请添加图片描述

  • 实现思路:其实就是改变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>
  • 扩展:同时支持上下/左右拖拽
    请添加图片描述

  • 实现原理和左右拖拽是一样的,只不过改变的样式是divheight

  • 完整代码如下:

<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 = topDom.offsetHeight; // 经指正,原:resizeDom.offsetHeight会导致每次拖动时都会先回到minTopHeight
        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,一般使用onmousemoveonmouseup两个事件。

当不再需要继续获得鼠标消息就要应该调用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 坐标。

4、鼠标样式

在这里插入图片描述