【JS】原生js实现矩形框的绘制/拖动/缩放

news2025/1/6 19:07:05

 1、要点及功能描述

通过js监听mouse事件来实现矩形框的绘制,再通过区分点击的是边角还是其他位置来实现矩形框的缩放和拖动,并且在拖动和缩放时,都做了边界限制,当缩放或拖动 到边界时,就不能继续拉缩放拖动了。当然在相关开发时,还是需要你对一些常规的offsetLeft,offsetX等的dom属性了解哟~

现在这种主要用来做canvas截图部分的矩形框展示,想要了解canvas截图的可以看我另外两篇博客,分别是pdf的截图绘制和图片的截图绘制。当然本篇博客主要侧重于对矩形框的实现喔~

【PDF】Canvas绘制PDF及截图

canvas图像绘制(图像放大、缩小、拖动和截图)

2、效果图展示

3、原理讲解

3.1、变量详解

    const dom = document.getElementById('out-box')
    const rect  = document.getElementById('rect')
    const origin = dom.getBoundingClientRect()
    const parentBorder = Number(getComputedStyle(dom, null).borderWidth.split('px')[0]) // 父元素边框 如果你明确知道边框宽度,就不需要这行,直接赋值就行
    const childBorder = Number(getComputedStyle(rect, null).borderWidth.split('px')[0]) // 子元素边框 如果你明确知道边框宽度,就不需要这行,直接赋值就行

dom:外层盒子,鼠标下手开始绘制的地方

rect: 矩形框,其实矩形框并不是凭空绘制出来的,只是先将其宽高置为0,并且定位到负无限处,所以在页面上看不到,在绘制时,通过控制矩形框的定位及宽高来进行展示,这也是我进行绘制/拖动/缩放的核心思想

    .rect{
      position: absolute;
      box-shadow: 0 0 0 1999px rgba(0, 0, 0, .4);
      left: -9999px;
      top: 0;
      width: 0;
      height: 0;
      border: 2px solid orange;
      cursor: move;
    }

origin: 其实就是外层盒子的相对于页面的属性,有用处

八个小圆点: 矩形外部八个可缩放的圆点(点击矩形框和点击圆点通过下面方式来区分)

if (e.target !== rect && e.target.className.indexOf('dot') === -1) return // 类名中包含被放行的dot除外



    .rect>span{
      position: absolute;
      width: 6px;
      height: 6px;
      border-radius: 50%;
      border: 2px solid orange;
      background-color: #fff;
    }

parentBorder和childBorder就是你外层盒子和矩形盒子的边框,其实可要可不要,但是主要为了好看(你可以直接赋值就行,这跟影响不大)

3.2、绘制方法讲解 

startMouse:这里left和top,就是在onmousedown开始下笔时,就确定了当前矩形框的定位,用本身下笔时的clientX,clientY去减去外层盒子的偏移,就能得到矩形在外层盒子的定位(类似于offsetLeft,offsetTop,但是这里不能用offsetLeft,offsetTop,不过你也可以自己去试试)

        const left = e.clientX - origin.x
        const top = e.clientY - origin.y
        rect.style.left = left + 'px'
        rect.style.top = top + 'px'

3.3、判断当前是拖动还是缩放讲解

 mousedownHandle:当点击矩形框边角时,你肯定是想要缩放矩形框,当点击其他位置时,你肯定想要拖动矩形框。

在矩形框内部点击,就可以使用offset相关属性,你所点击的位置,就是offsetX,offsetY,这俩属性是相对于当前dom内部的点击位置的,而offsetWidth,offsetHeight是当前dom的宽高,differenec是模糊距离,有时候你得设置一个点击的范围,只要点击点在那个范围内,就是缩放,反之亦然。

当点击的对象不是矩形框时,则代表当前的要对矩形框进行缩放,否则则是要对矩形框进行拖拽

      const startX = e.offsetX
      const startY = e.offsetY
      const width = e.target.offsetWidth
      const height = e.target.offsetHeight
      if (e.target !== rect) {
        flag = 1 // 点击的是边角 缩放
        const parent = e.target.offsetParent.getBoundingClientRect()
        const child = e.target.getBoundingClientRect()
        startX = child.x - parent.x
        startY = child.y - parent.y
        width = e.target.offsetParent.offsetWidth
        height = e.target.offsetParent.offsetHeight
      }
      const difference = 12 // 点击四边角12 px范围为拉伸,其他为拖动,这个值可以根据你需要的来调整

 当然,最好的方式就是有八个缩放点,除了这八个缩放点,其余位置都是被拖拽的地方,下面方法就是判断是哪个拉伸点的(这个也蛮好理解,不信你打开你的截图软件,是不是有8个能被缩放的小方块),除去这八个点以外,其他都是返回[-1, -1],以来标识拖动

      let left = 0 // 0 => left, 1 => middle, 2 => right, -1 => 点击的位置不能被拖动
      let top = 0 // 0 => top, 1 => middle, 2 => bottom, -1 => 点击的位置不能被拖动
      if (startX < difference && startX > 0) { // 点击的位置为矩形左侧0 ~ 6px
        left = 0
      } else if (startX > width / 2 - difference && startX < width / 2 + difference) { // 点击的位置为矩形中间 width/2 - 6px ~ width/2 + 6px
        left = 1
      } else if (startX < width && startX > width - difference){ // 点击的位置为矩形右侧 width - 6px ~ width
        left = 2
      } else {
        left = -1
      }

      if (startY < difference && startY > 0) { // 点击的位置为矩形上侧0 ~ 6px
        top = 0
      } else if (startY > height / 2 - difference && startY < height / 2 + difference) { // 点击的位置为矩形中间 height/2 - 6px ~ height/2 + 6px
        top = 1
      } else if (startY < height && startY > height - difference){ // 点击的位置为矩形下侧 height - 6px ~ height
        top = 2
      } else {
        top = -1
      }

3.4、实现拖动和缩放的方法详解 

 拖动还是缩放?

当mousedownHandle返回的是[-1, -1]时,那就说明是拖动,拖动的话,只需要改变矩形框的left,top定位即可。

if (e.target !== rect && e.target.className.indexOf('dot') === -1) return // 类名中包含被放行的dot除外
        const flag = mousedownHandle(e)
        let left = e.clientX
        let top = e.clientY
        const width = rect.offsetWidth
        const height = rect.offsetHeight
        const [dragX, dragY] = flag
        // 拖动
        if (dragX === -1 && dragY === -1) {
          left -= rect.offsetLeft // 要保持之前矩形框的坐标值
          top -= rect.offsetTop
        }
        const child = e.target.getBoundingClientRect()
        document.onmousemove = e => {
          // 取消浏览器因回流导致的默认事件及冒泡事件
          e.preventDefault()
          if (e.stopPropagation) {
            e.stopPropagation()
          } else {
            e.cancelable = true
          }
          const finallPoint = {
            left: 0,
            top: 0,
            width: 0,
            height: 0
          }
          if (dragX === -1 && dragY === -1) {
            rect.style.cursor = 'move'
            const rightArea = dom.offsetWidth - rect.offsetWidth - (childBorder * 2) // 右边界
            const bottomArea = dom.offsetHeight - rect.offsetHeight - (childBorder * 2) // 下边界
            const leftArea = 0 // 左边界
            const topArea = 0 // 上边界
            finallPoint.left = e.clientX - left > rightArea ? rightArea : (e.clientX - left< leftArea ? leftArea : e.clientX - left)
            finallPoint.top = e.clientY - top > bottomArea ? bottomArea : (e.clientY - top < topArea ? topArea : e.clientY - top)
            rect.style.left = finallPoint.left + 'px'
            rect.style.top = finallPoint.top + 'px'
          }
        }

 当mousedownHandle返回的不是[-1, -1]时,例如[2, 2],那就说明是向右下角缩放(目前实现了向右下角缩放,其余缩放童鞋们可以自己思考喔,同理可参考我的来写哟~)

    document.onmousemove = e => {
          // 取消浏览器因回流导致的默认事件及冒泡事件
          e.preventDefault()
          if (e.stopPropagation) {
            e.stopPropagation()
          } else {
            e.cancelable = true
          }
          if (dragX === 0 && dragY === 0) { // 左上角拉伸
            finallPoint.left = e.clientX > origin.x ? ((e.clientX > (left + width)) ? left + width - origin.x : e.clientX - origin.x) : 0
            finallPoint.top = e.clientY > origin.y ? ((e.clientY > (top + height)) ? top + height - origin.y : e.clientY - origin.y) : 0
            finallPoint.width = e.clientX > origin.x ? ((e.clientX > (left + width)) ? 0 : width + (left - e.clientX)) : width + (left - origin.x)
            finallPoint.height = e.clientY > origin.y ? ((e.clientY > (top + height)) ? 0 : height + (top - e.clientY)) : height + (top - origin.y)
            rect.style.left = finallPoint.left + 'px'
            rect.style.top = finallPoint.top + 'px'
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 1 && dragY === 0) { // 中上拉伸
            finallPoint.top = e.clientY > origin.y ? ((e.clientY > (top + height)) ? top + height - origin.y : e.clientY - origin.y) : 0
            finallPoint.height = e.clientY > origin.y ? ((e.clientY > (top + height)) ? 0 : height + (top - e.clientY)) : height + (top - origin.y)
            rect.style.top = finallPoint.top + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 2 && dragY === 0) { // 右上角拉伸
            finallPoint.top = e.clientY > origin.y ? ((e.clientY > (top + height)) ? top + height - origin.y : e.clientY - origin.y) : 0
            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            finallPoint.height = e.clientY > origin.y ? ((e.clientY > (top + height)) ? 0 : height + (top - e.clientY)) : height + (top - origin.y)
            rect.style.top = finallPoint.top + 'px'
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 2 && dragY === 1) { // 右中拉伸
            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            rect.style.width = finallPoint.width + 'px'
          }else if (dragX === 2 && dragY === 2) { // 右下角拉伸
            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 1 && dragY === 2) { // 中下拉伸
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 0 && dragY === 2) { // 左下角拉伸
            finallPoint.left = e.clientX > origin.x ? ((e.clientX > (left + width)) ? left + width - origin.x : e.clientX - origin.x) : 0
            finallPoint.width = e.clientX > origin.x ? ((e.clientX > (left + width)) ? 0 : width + (left - e.clientX)) : width + (left - origin.x)
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.left = finallPoint.left + 'px'
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 0 && dragY === 1) { // 左中拉伸
            finallPoint.left = e.clientX > origin.x ? ((e.clientX > (left + width)) ? left + width - origin.x : e.clientX - origin.x) : 0
            finallPoint.width = e.clientX > origin.x ? ((e.clientX > (left + width)) ? 0 : width + (left - e.clientX)) : width + (left - origin.x)
            rect.style.left = finallPoint.left + 'px'
            rect.style.width = finallPoint.width + 'px'
          }
    }

4、源码展示

可直接运行展示

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>drawRectangle</title>
  <style>
    *{
      margin: 0;
      padding: 0;
    }
    .container{
      width: 1000px;
      height: 600px;
      margin: 10% auto;
    }
    .control{
      margin-bottom: 10px;
    }
    #out-box{
      width: 100%;
      height: 550px;
      border: 2px solid #000;
      position: relative;
      overflow: hidden;
    }
    .rect{
      position: absolute;
      box-shadow: 0 0 0 1999px rgba(0, 0, 0, .4);
      left: -9999px;
      top: 0;
      width: 0;
      height: 0;
      border: 2px solid orange;
      cursor: move;
    }
    .rect>span{
      position: absolute;
      width: 6px;
      height: 6px;
      border-radius: 50%;
      border: 2px solid orange;
      background-color: #fff;
    }
    .rect .left-top{
      left: -6px;
      top: -6px;
      cursor: nwse-resize;
    }
    .rect .middle-top{
      left: 50%;
      top: -6px;
      transform: translateX(-50%);
      cursor: n-resize;
    }
    .rect .right-top{
      right: -6px;
      top: -6px;
      cursor: nesw-resize;
    }
    .rect .right-middle{
      right: -6px;
      top: 50%;
      transform: translateY(-50%);
      cursor: e-resize;
    }
    .rect .right-bottom{
      right: -6px;
      bottom: -6px;
      cursor: nwse-resize;
    }
    .rect .middle-bottom{
      left: 50%;
      bottom: -6px;
      transform: translateX(-50%);
      cursor: s-resize;
    }
    .rect .left-bottom{
      left: -6px;
      bottom: -6px;
      cursor: nesw-resize;
    }
    .rect .left-middle{
      left: -6px;
      top: 50%;
      transform: translateY(-50%);
      cursor: w-resize;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="control">
      <button id="clear">清屏</button>
      <button id="start-paint">开始绘制</button>
    </div>
    <div id="out-box">
      <div id="rect" class="rect">
        <span class="left-top dot"></span>
        <span class="middle-top dot"></span>
        <span class="right-top dot"></span>
        <span class="right-middle dot"></span>
        <span class="right-bottom dot"></span>
        <span class="middle-bottom dot"></span>
        <span class="left-bottom dot"></span>
        <span class="left-middle dot"></span>
      </div>
    </div>
  </div>
  
  <script>
    const dom = document.getElementById('out-box')
    const rect  = document.getElementById('rect')
    const origin = dom.getBoundingClientRect()
    const parentBorder = Number(getComputedStyle(dom, null).borderWidth.split('px')[0]) // 父元素边框 如果你明确知道边框宽度,就不需要这行,直接赋值就行
    const childBorder = Number(getComputedStyle(rect, null).borderWidth.split('px')[0]) // 子元素边框 如果你明确知道边框宽度,就不需要这行,直接赋值就行
    /**
     * 开始绘制
     */
    const startMouse = () => {
      dom.style.cursor = 'crosshair'
      dom.onmousedown = e => {
        if (e.target !== dom) return
        const left = e.offsetX
        const top = e.offsetY
        rect.style.left = left + 'px'
        rect.style.top = top + 'px'
        dom.onmousemove = e => {
          // 宽高边界限制
          const widthArea = e.clientX - origin.x > dom.offsetWidth - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - (parentBorder * 2 + childBorder * 2) : e.clientX - origin.x
          const heightArea = e.clientY - origin.y > dom.offsetHeight - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - (parentBorder * 2 + childBorder * 2) : e.clientY - origin.y
          rect.style.width = widthArea - left + 'px'
          rect.style.height = heightArea - top + 'px'
        }
        dom.onmouseup = e => {
          dom.onmousedown = null
          dom.onmousemove = null
          dom.onmouseup = null
          dom.style.cursor = ''
          editMouse()
        }
      }
    }
    const editMouse = () => {
      rect.onmousedown = e => {
        if (e.target !== rect && e.target.className.indexOf('dot') === -1) return // 类名中包含被放行的dot除外
        const flag = mousedownHandle(e)
        let left = e.clientX
        let top = e.clientY
        const width = rect.offsetWidth
        const height = rect.offsetHeight
        const [dragX, dragY] = flag
        // 拖动
        if (dragX === -1 && dragY === -1) {
          left -= rect.offsetLeft // 要保持之前矩形框的坐标值
          top -= rect.offsetTop
        }
        const child = e.target.getBoundingClientRect()
        document.onmousemove = e => {
          // 取消浏览器因回流导致的默认事件及冒泡事件
          e.preventDefault()
          if (e.stopPropagation) {
            e.stopPropagation()
          } else {
            e.cancelable = true
          }
          const finallPoint = {
            left: 0,
            top: 0,
            width: 0,
            height: 0
          }
          if (dragX === -1 && dragY === -1) {
            rect.style.cursor = 'move'
            const rightArea = dom.offsetWidth - rect.offsetWidth - (childBorder * 2) // 右边界
            const bottomArea = dom.offsetHeight - rect.offsetHeight - (childBorder * 2) // 下边界
            const leftArea = 0 // 左边界
            const topArea = 0 // 上边界
            finallPoint.left = e.clientX - left > rightArea ? rightArea : (e.clientX - left< leftArea ? leftArea : e.clientX - left)
            finallPoint.top = e.clientY - top > bottomArea ? bottomArea : (e.clientY - top < topArea ? topArea : e.clientY - top)
            rect.style.left = finallPoint.left + 'px'
            rect.style.top = finallPoint.top + 'px'
          } else if (dragX === 0 && dragY === 0) { // 左上角拉伸
            finallPoint.left = e.clientX > origin.x ? ((e.clientX > (left + width)) ? left + width - origin.x : e.clientX - origin.x) : 0
            finallPoint.top = e.clientY > origin.y ? ((e.clientY > (top + height)) ? top + height - origin.y : e.clientY - origin.y) : 0
            finallPoint.width = e.clientX > origin.x ? ((e.clientX > (left + width)) ? 0 : width + (left - e.clientX)) : width + (left - origin.x)
            finallPoint.height = e.clientY > origin.y ? ((e.clientY > (top + height)) ? 0 : height + (top - e.clientY)) : height + (top - origin.y)
            rect.style.left = finallPoint.left + 'px'
            rect.style.top = finallPoint.top + 'px'
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 1 && dragY === 0) { // 中上拉伸
            finallPoint.top = e.clientY > origin.y ? ((e.clientY > (top + height)) ? top + height - origin.y : e.clientY - origin.y) : 0
            finallPoint.height = e.clientY > origin.y ? ((e.clientY > (top + height)) ? 0 : height + (top - e.clientY)) : height + (top - origin.y)
            rect.style.top = finallPoint.top + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 2 && dragY === 0) { // 右上角拉伸
            finallPoint.top = e.clientY > origin.y ? ((e.clientY > (top + height)) ? top + height - origin.y : e.clientY - origin.y) : 0
            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            finallPoint.height = e.clientY > origin.y ? ((e.clientY > (top + height)) ? 0 : height + (top - e.clientY)) : height + (top - origin.y)
            rect.style.top = finallPoint.top + 'px'
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 2 && dragY === 1) { // 右中拉伸
            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            rect.style.width = finallPoint.width + 'px'
          }else if (dragX === 2 && dragY === 2) { // 右下角拉伸
            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 1 && dragY === 2) { // 中下拉伸
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 0 && dragY === 2) { // 左下角拉伸
            finallPoint.left = e.clientX > origin.x ? ((e.clientX > (left + width)) ? left + width - origin.x : e.clientX - origin.x) : 0
            finallPoint.width = e.clientX > origin.x ? ((e.clientX > (left + width)) ? 0 : width + (left - e.clientX)) : width + (left - origin.x)
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.left = finallPoint.left + 'px'
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'
          } else if (dragX === 0 && dragY === 1) { // 左中拉伸
            finallPoint.left = e.clientX > origin.x ? ((e.clientX > (left + width)) ? left + width - origin.x : e.clientX - origin.x) : 0
            finallPoint.width = e.clientX > origin.x ? ((e.clientX > (left + width)) ? 0 : width + (left - e.clientX)) : width + (left - origin.x)
            rect.style.left = finallPoint.left + 'px'
            rect.style.width = finallPoint.width + 'px'
          }
        }
        document.onmouseup = e => {
          document.onmousemove = null
          document.onmouseup = null
          rect.style.cursor = 'move'
        }
      }
    }
    /**
     * mousedown逻辑处理
     */
    const mousedownHandle = (e) => {
      let flag = 0 // 点击的是除边角以外的其他部分 拖动
      let startX = e.offsetX
      let startY = e.offsetY
      let width = e.target.offsetWidth
      let height = e.target.offsetHeight
      if (e.target !== rect) {
        flag = 1 // 点击的是边角 缩放
        const parent = e.target.offsetParent.getBoundingClientRect()
        const child = e.target.getBoundingClientRect()
        startX = child.x - parent.x
        startY = child.y - parent.y
        width = e.target.offsetParent.offsetWidth
        height = e.target.offsetParent.offsetHeight
      }
      const difference = 12 // 点击四边角12 px范围为拉伸,其他为拖动,这个值可以根据你需要的来调整
      let left = 0 // 0 => left, 1 => middle, 2 => right, -1 => 点击的位置不能被拖动
      let top = 0 // 0 => top, 1 => middle, 2 => bottom, -1 => 点击的位置不能被拖动
      if (startX < difference) { // 点击的位置为矩形左侧
        left = 0
      } else if (startX > width / 2 - difference && startX < width / 2 + difference) { // 点击的位置为矩形中间 width/2 - 6px ~ width/2 + 6px
        left = 1
      } else if (startX < width && startX > width - difference){ // 点击的位置为矩形右侧 width - 6px ~ width
        left = 2
      } else {
        left = -1
      }

      if (startY < difference) { // 点击的位置为矩形上侧
        top = 0
      } else if (startY > height / 2 - difference && startY < height / 2 + difference) { // 点击的位置为矩形中间 height/2 - 6px ~ height/2 + 6px
        top = 1
      } else if (startY < height && startY > height - difference){ // 点击的位置为矩形下侧 height - 6px ~ height
        top = 2
      } else {
        top = -1
      }
      if (left === -1 || top === -1 || (left === 1 && top === 1)) {
        return [-1, -1]
      }
      return [left, top] // 只会有八个位置能被准确返回,其余都是返回[-1, -1]
    }

    const clear = document.querySelector('#clear') // 清屏
    const startPaint = document.querySelector('#start-paint') // 开始绘制
    clear.onclick = e => {
      rect.style.left = '-9999px'
      rect.style.top = 0
      rect.style.width = 0
      rect.style.height = 0
    }
    startPaint.onclick = e => {
      startMouse()
    }
  </script>
</body>
</html>

5、问题答疑?

1、在onmousedown里为啥要加上 if (e.target !== dom) return 这行代码呢?

dom.onmousedown = e => {
        if (e.target !== dom) return
}

因为我在多次写的时候发现呀,很多时候矩形框的下面就会有类似截图软件下的涂鸦功能,当你点击涂鸦按钮时,其实这个时候你也是点击dom的(可理解为事件穿透),但是我们写逻辑的时候不想要涂鸦按钮和dom一起绑定住想区分开,所以这个时候判断onmousedown的对象和dom是否一致,不一致的话,就不执行后续操作。

2、在onmousemove里为啥要加 e.preventDefault() ... 这段代码呢?

    document.onmousemove = e => {
          e.preventDefault()
          if (e.stopPropagation) {
            e.stopPropagation()
          } else {
            e.cancelable = true
          }
    } 

注意哦,只在绑定对象为document才加这一段代码,其他的不加~

加上这段代码是因为在mousemove来回移动到当前矩形框时,会出现浏览器的黑色拒绝符号并会卡顿,导致操作不流畅,所以加上这段代码可以取消浏览器因回流导致的默认事件及冒泡事件

3、 矩形框周围的黑色半透明蒙层是如何实现的?

box-shadow: 0 0 0 1999px rgba(0, 0, 0, .4);

是通过这行样式实现的喔,其中1999px基本满足大部分屏幕的要求,当然也可以根据你的需求来设置哦,但是一定要切记,外层盒子要设置overflow,不然矩形框的阴影将会溢出去

overflow: hidden;

4、绘制矩形框时的边界范围限制?

其实可以看到,绘制时,矩形框的left和top都是已经确定好的,只是在mousemove时候改变宽高,但是宽高不能超过外层盒子的范围呀,所以就是在你mousemove时,将计算出来的宽高与父级offsetWidth,offsetHeight进行比较(记住为了更好看,我这里是将边框宽度也一起计算进来的喔,如果你不想计算的话,影响也不大~)(内部变量找不到的可以到上面源码里查看全部喔~)

          // 宽高边界限制
          const widthArea = e.clientX - origin.x > dom.offsetWidth - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - (parentBorder * 2 + childBorder * 2) : e.clientX - origin.x
          const heightArea = e.clientY - origin.y > dom.offsetHeight - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - (parentBorder * 2 + childBorder * 2) : e.clientY - origin.y
          rect.style.width = widthArea - left + 'px'
          rect.style.height = heightArea - top + 'px'

5、拖动矩形框时的边界范围限制?

这里给出了上下左右四个边界范围限制,当移动left,top超过这个范围时,都将给予处理,使矩形框不会超过外层盒子的边界范围(内部变量找不到的可以到上面源码里查看全部喔~)

            const rightArea = dom.offsetWidth - rect.offsetWidth - (childBorder * 2) // 右边界
            const bottomArea = dom.offsetHeight - rect.offsetHeight - (childBorder * 2) // 下边界
            const leftArea = 0 // 左边界
            const topArea = 0 // 上边界
            finallPoint.left = e.clientX - left > rightArea ? rightArea : (e.clientX - left< leftArea ? leftArea : e.clientX - left)
            finallPoint.top = e.clientY - top > bottomArea ? bottomArea : (e.clientY - top < topArea ? topArea : e.clientY - top)
            rect.style.left = finallPoint.left + 'px'
            rect.style.top = finallPoint.top + 'px'

6、缩放矩形框时的边界范围限制?

缩放的时候,其实矩形框的left,top,width和height都可能需要改变,不过具体要看你缩放的是哪个边角,下面我代码里是缩放的右下角,所以只需要改变矩形框宽高即可。当宽高的长度等于或超过外层盒子的offsetWidth,offsetHeight减去矩形框的offsetLeft,offsetTop时,就说明已经到范围边界了,这个长度不能再被增加了。(内部变量找不到的可以到上面源码里查看全部喔~)

            finallPoint.width = (e.clientX - left + width > dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) ? dom.offsetWidth - rect.offsetLeft - (parentBorder * 2 + childBorder * 2) : e.clientX - left + width)
            finallPoint.height = (e.clientY- top + height > dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) ? dom.offsetHeight - rect.offsetTop - (parentBorder * 2 + childBorder * 2) : e.clientY- top + height)
            rect.style.width = finallPoint.width + 'px'
            rect.style.height = finallPoint.height + 'px'

---不了解的可以评论哟,林大大哟原创---

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/23094.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【个人简介】一枚在上海的AndroidiOSWindow逆向电子工程师

> Hello World!, I am Humenger 「 From Shanghai, China 」 「 Android Reverse engineer, applied electronic technology Shan Dong University, China 」 &#x1f41d;主要涉及平台: Android(70%),iOS(15%),Window(5%),macOS(3%),其他(7%) &#x1f98b;主要涉…

易基因|RNA m7G甲基化测序(m7G-MeRIP-seq)

N7-甲基鸟苷&#xff08;N7-methylguanosine&#xff0c;m7G&#xff09;是真核生物tRNA、rRNA和mRNA 5cap中最丰富的修饰之一。作为一种重要的表观遗传修饰&#xff0c;m7G RNA甲基化在基因表达、加工代谢、蛋白质合成、转录稳定等方面发挥着重要的作用&#xff0c;参与疾病发…

Pinely Round 1 (Div. 1 + Div. 2) E - Make It Connected思维分类讨论

昨晚的problem e 一直wa。因为答案&#xff0c;不唯一&#xff0c;调起来只能肉眼debug。被干emo了qwq。好在赛后看到 ugly2333的 思路和我差不多&#xff0c;最后还是要选取度数较小的最优, 好像从度数的角度出发&#xff0c;不容易wa。 题意&#xff1a; 给你一个图&#xf…

什么是组织孤岛?它会带来哪些影响?可以这样去对付它

作为一个在不同地点和时区与不同团队合作的远程工作者&#xff0c;我有过公平的孤岛经历。 是的&#xff0c;它们扼杀了任何组织的成长。那么&#xff0c;在使你&#xff08;和组织中的每个人&#xff09;失去生产力、困惑、自私和不快乐之后。 在这篇文章中&#xff0c;我将…

ADRV9009中armBinary反汇编IDA参数设置

armBinary.bin文件如果不做处理的话就是一堆16进制数,扔到IDA里也只是一堆有颜色的16进制数,需要进行一些参数设置。 1 选择IDA32位打开armBinary.bin文件 2 load a new file设置 Processor type选择ARM Little-endian [ARM],点击Edit ARM architecture options进行相应修…

Linux 中的内部命令和外部命令

Linux 中的内部命令和外部命令 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;Linux 中的内部命令和外部命令 CSDN&#xff1a;Linux 中的内部命令和外部命令 什么是 bash shell ? bash shell&#xff0c;就是一个程序&#xff0c;就是 Linux 系统安装的…

漫谈 Java 平台上的反应式编程

反应式编程&#xff08;Reactive Programming&#xff09;是一套完整的编程体系&#xff0c;既有其指导思想&#xff0c;又有相应的框架和库的支持&#xff0c;并且在生产环境中有大量实际的应用。在支持度方面&#xff0c;既有大公司参与实践&#xff0c;也有强大的开源社区的…

【Linux】-- 开发工具yum、vim、gcc、g++、gdb、make、makefile使用介绍

目录 一、yum 1.了解yum &#xff08;1&#xff09;RPM &#xff08;2&#xff09;yum 2.yum使用 &#xff08;1&#xff09;查看软件包 &#xff08;2&#xff09;安装软件 &#xff08;3&#xff09;卸载软件 二.Linux编辑器-vim 1. vim概念 &#xff08;1&am…

flink集群搭建

1、安装包flink-1.10.0-bin-scala_2.11.tgz 2、tar -zxf flink-1.10.0-bin-scala_2.11.tgz 解压到指定目录 解压之后的文件名称是flink-1.10.0 3、flink-1.10.0的目录结构如下&#xff1a; bin/&#xff1a;flink的相关命令 conf/&#xff1a;flink的配置文件 examples/&a…

业务数据分析-Excel公式与函数(三)

目录 概念 运算符 地址的引用 逻辑函数 文本函数 统计函数 查找与引用函数 日期函数 常见出错信息 概念 公式&#xff1a;Excel的核心功能&#xff0c;功能强大 如果要定义的话&#xff0c;可以说是 以开头的&#xff0c;对地址进行引用的计算形式 说的高大上一点的…

方法2—并行数据流转换为一种特殊串行数据流模块的设计

并行数据流转换为一种特殊串行数据流模块的设计&#xff0c;设计两个可综合的电路模块1&#xff0c;第一个可综合模块&#xff0c;M1。2&#xff0c;描述M2模块3&#xff0c;描述M0模块的Verilog代码4&#xff0c;描述顶层模块5&#xff0c;电路生成的门级网表&#xff0c;netl…

Camtasia2023简单易用的电脑录屏视频剪辑软件

教学、演示、培训视频轻松制作!Camtasia非常容易学习 你不需要一个大的预算或花哨的视频编辑技能。只需录制屏幕并添加一些特效即可。无论您是有经验还是这是第一次制作视频 Camtasia都会为您提供制作高质量视频所需的一切。创建观看者实际观看的内容。视频将为您提供更多的互动…

军队文职丨2022年武警部队面向社会公开招聘351名文职人员公告!高中学历可报,11月25日前报名!

2022年武警部队面向社会公开招聘 专业技能岗位文职人员公告 根据《军队专业技能岗位文职人员聘用管理暂行规定》及有关政策规定&#xff0c;现就2022年武警部队面向社会公开招聘专业技能岗位文职人员有关事项公告如下&#xff1a; 一、招聘岗位 武警部队所属用人单位运输投送…

前后端分类 (增加,查询)

目录 一&#xff0c;后台代码 二&#xff0c;前台代码 一&#xff0c;后台代码 一&#xff0c;配置文件 application.yml server:port: 8080servlet:context-path: /spboot spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.j…

隧道HTTP API使用教程

华科隧道HTTP格式为&#xff1a;ip:port username password 隧道代理分钟2种模式&#xff1a; 固定时间更改新IP&#xff08;比如5分钟&#xff0c;10分钟&#xff0c;初次开通的时候可设定&#xff09;请求一次更换一个新IP&#xff08;可通过浏览器或者curl&#xff09; 1、…

图_图的存储_添加边_图的遍历_DFS_树的重心_BFS_图中点的层次

文章目录图有向图的存储添加遍历1.DFS例题&#xff1a;树的重心题目分析使用DFS遍历2.BFS例题&#xff1a;图中点的层次图 树是特殊的图&#xff08;无环连通图&#xff09; 有向图&#xff08;a -> b&#xff09; 无向图&#xff08;a -> b, b -> a&#xff09; …

ffmpeg源码阅读之avformat_alloc_output_context2

整体结构流程 核心逻辑 通过读源码发现核心的处理逻辑是av_guess_format函数&#xff0c;这里就根据核心逻辑来阅读&#xff0c;其余的基本是是在做判断和赋值 av_guess_format阅读分析 步骤1(先看头文件) /*** Return the output format in the list of registered output…

wy的leetcode刷题记录_Day46

wy的leetcode刷题记录_Day46 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2022-11-19 前言 补 目录wy的leetcode刷题记录_Day46声明前言1732. 找到最高海拔题目介绍思路代码收获106. 从中序与后序遍历序列构造二叉树题目介绍思路代码…

【Java毕设】基于SpringBoot实现新冠疫情统计系统(Idea+Navicat)

推荐学习专栏&#xff1a; Java基础学习专栏&#xff1a;java基础知识学习Java进阶学习专栏&#xff1a;java编程进阶学习 前言 疫情在我们的生活中反反复复&#xff0c;为了方便我们更直观的清楚新冠疫情数据&#xff0c;通过Java编程可以统计疫情信息&#xff0c;更好管控。…

CSC公派|在读博士赴新加坡南洋理工大学联合培养

我们先助O同学取得了英国牛津大学的邀请函&#xff0c;并成功获批CSC项目。由于该校办理T5签证所需的COS担保证书迟迟未果&#xff0c;考虑到其毕业及CSC延期时限&#xff0c;我们又为O同学申请到新加坡南洋理工大学&#xff0c;顺利改派并签证出国。 O同学背景&#xff1a; 申…