VUE篇之可拖动裁剪框

涉及知识点: offsetLeft, offsetTop, offsetWidth, offsetHeight;offsetX, offsetY;clientX,clientY

css:clip-path

学习直通车:HTMLElement.offsetLeft - Web API 接口参考 | MDN

MouseEvent.offsetX - Web API 接口参考 | MDN

MouseEvent.clientX - Web API 接口参考 | MDN

clip-path - CSS:层叠样式表 | MDN

1.布局搭建

html 复制代码
<template>
  <div class="wrap">
    <div ref="box1" class="box1" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseUp">
      <img class="img1" src="./images/7.jpg" alt="" />
      <img ref="clipImg" class="img2" src="./images/7.jpg" alt="" />
      <div ref="cropBox" class="cropBox" @mousedown="onMouseCropBoxDown">
        <div class="leftUp" @mousedown="onMouseDownDot($event, 'leftUp')"></div>
        <div class="up" @mousedown="onMouseDownDot($event, 'up')"></div>
        <div class="rightUp" @mousedown="onMouseDownDot($event, 'rightUp')"></div>
        <div class="right" @mousedown="onMouseDownDot($event, 'right')"></div>
        <div class="rightDown" @mousedown="onMouseDownDot($event, 'rightDown')"></div>
        <div class="down" @mousedown="onMouseDownDot($event, 'down')"></div>
        <div class="leftDown" @mousedown="onMouseDownDot($event, 'leftDown')"></div>
        <div class="left" @mousedown="onMouseDownDot($event, 'left')"></div>
      </div>
    </div>
    <div class="box2">
      <img ref="previewImg" src="./images/7.jpg" alt="" />
    </div>
  </div>
</template>

2.css样式

@mixin css混入

$boxWidth 变量定义

html 复制代码
<style lang="scss">
.wrap {
  display: flex;
  padding: 30px;

  $boxWidth: 400px; //盒子大小
  $boxHeight: 400px;
  $defaultCropBoxWidth: 150px; //裁切框初始值
  $defaultCropBoxHeight: 150px;
  $dotWidth: 6px; //点

  .box1 {
    width: $boxWidth;
    height: $boxHeight;
    border: 1px solid #000;
    position: relative;
    box-sizing: border-box;

    @mixin img {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      -webkit-user-drag: none !important;
      user-select: none;
    }

    .img1 {
      @include img;
      opacity: 0.5;
    }

    .img2 {
      @include img;
      clip-path: polygon(
        0 0,
        $defaultCropBoxWidth 0,
        $defaultCropBoxWidth $defaultCropBoxHeight,
        0 $defaultCropBoxHeight
      );
    }

    .cropBox {
      position: absolute;
      left: 0;
      top: 0;
      width: $defaultCropBoxWidth;
      height: $defaultCropBoxHeight;
      border: 1px solid #fff;
      cursor: all-scroll;

      $halfDotWidth: calc(6px / -2);

      @mixin dot {
        position: absolute;
        width: $dotWidth;
        height: $dotWidth;
        background-color: #fff;
      }

      .leftUp {
        @include dot;
        top: $halfDotWidth;
        left: $halfDotWidth;
        cursor: nw-resize;
      }
      .up {
        @include dot;
        top: $halfDotWidth;
        left: 50%;
        margin-left: $halfDotWidth;
        cursor: n-resize;
      }
      .rightUp {
        @include dot;
        top: $halfDotWidth;
        right: $halfDotWidth;
        cursor: ne-resize;
      }
      .right {
        @include dot;
        top: 50%;
        margin-top: $halfDotWidth;
        right: $halfDotWidth;
        cursor: e-resize;
      }
      .rightDown {
        @include dot;
        bottom: $halfDotWidth;
        right: $halfDotWidth;
        cursor: se-resize;
      }
      .down {
        @include dot;
        bottom: $halfDotWidth;
        left: 50%;
        margin-left: $halfDotWidth;
        cursor: s-resize;
      }
      .leftDown {
        @include dot;
        bottom: $halfDotWidth;
        left: $halfDotWidth;
        cursor: sw-resize;
      }
      .left {
        @include dot;
        top: 50%;
        margin-top: $halfDotWidth;
        left: $halfDotWidth;
        cursor: w-resize;
      }
    }
  }

  .box2 {
    width: $boxWidth;
    height: $boxHeight;
    margin-left: 50px;
    position: relative;
    img {
      position: absolute;
      left: 0;
      top: 0;
      width: 400px;
      height: 400px;
      display: block;
      clip-path: polygon(
        0 0,
        $defaultCropBoxWidth 0,
        $defaultCropBoxWidth $defaultCropBoxHeight,
        0 $defaultCropBoxHeight
      );
      -webkit-user-drag: none !important;
      user-select: none;
    }
  }
}
</style>

以上可以完成如下布局

3.实现拖拽预览

javascript 复制代码
<script>
const WIDTH = 400;
const HEIGHT = 400;
const MIN = 50;
export default {
  data() {
    return {
      startCropMouseX: 0,
      stertCropMouseY: 0,
      inSelectBox: false, //判断是否在选框中拖动
    };
  },

  mounted() {},
  methods: {
    // 选框移动
    onMouseMove(e) {
      // offsetLeft返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值。
      const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = this.$refs.cropBox;
      const { offsetX, offsetY } = e;
      if (this.inSelectBox) {
        const xChange = offsetX - this.startCropMouseX; //获取移动距离
        const yChange = offsetY - this.stertCropMouseY;
        let left = offsetLeft + xChange; //每次移动时,获取选框位置
        let top = offsetTop + yChange;
        if (left <= 0) {
          left = 0;
          this.$refs.cropBox.style.left = `${left}px`;
        } else if (left + offsetWidth >= WIDTH) {
          left = WIDTH - offsetWidth; //总宽度-自身宽度
          this.$refs.cropBox.style.left = `${left}px`;
        } else {
          this.$refs.cropBox.style.left = `${left}px`;
        }
        if (top <= 0) {
          top = 0;
          this.$refs.cropBox.style.top = `${top}px`;
        } else if (top + offsetHeight >= HEIGHT) {
          top = HEIGHT - offsetHeight; //总高度-自身高度
          this.$refs.cropBox.style.top = `${top}px`;
        } else {
          this.$refs.cropBox.style.top = `${top}px`;
        }
        this.setHeightView();
        this.setPreviewImg();
      }
    },
    // 鼠标抬起
    onMouseUp(e) {
      this.inSelectBox = false;
    },
    // 选框拖动
    onMouseCropBoxDown(e) {
      const { offsetX, offsetY } = e;
      this.startCropMouseX = offsetX;
      this.stertCropMouseY = offsetY;
      this.inSelectBox = true;
    },
    // 设置选区可见位置
    setHeightView() {
      const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
      this.$refs.clipImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
    },
    // 设置预览图片
    setPreviewImg() {
      const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
      this.$refs.previewImg.style.left = `-${offsetLeft}px`;
      this.$refs.previewImg.style.top = `-${offsetTop}px`;
      this.$refs.previewImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
    },
    // 获取裁切path
    getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth) {
      return `polygon(${offsetLeft}px ${offsetTop}px,
      ${offsetLeft + offsetWidth}px ${offsetTop}px,
      ${offsetLeft + offsetWidth}px ${offsetTop + offsetHeight}px,
      ${offsetLeft}px ${offsetTop + offsetHeight}px)`;
    }
  }
};
</script>

4.实现裁剪

实现裁剪主要是进行点的拖拽,主要方法是上下左右;对于上左可以采用上的方法+左的方法即可

定义变量:

currentDot: '',

isDotDown: false //是否点位拉伸

上 下 左 右 方法

javascript 复制代码
 // 右移 获取拉伸宽度+cropBox自身宽度
    rightMove(e) {
      let x = e.clientX; //鼠标X坐标(用offsetX有bug)
      const { offsetLeft } = this.$refs.box1;
      const maxX = offsetLeft + WIDTH - 2;
      if (x > maxX) {
        x = maxX;
      }
      const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
      // 上一次点到浏览器距离
      const preX = offsetLeft + left + offsetWidth;
      const addWidth = x - preX;
      this.$refs.cropBox.style.width = `${offsetWidth + addWidth}px`;
    },
    // 上移
    upMove(e) {
      let y = e.clientY; //鼠标Y坐标
      const { offsetTop } = this.$refs.box1;
      if (y < offsetTop) {
        y = offsetTop;
      }
      const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
      // 右侧线距离浏览器的距离-y
      this.$refs.cropBox.style.height = `${offsetHeight + offsetTop + top - y}px`;
      // y-parent距离浏览器的距离
      this.$refs.cropBox.style.top = `${y - offsetTop}px`;
    },
    // 下移
    downMove(e) {
      let y = e.clientY;
      const { offsetTop } = this.$refs.box1;
      const maxY = offsetTop + HEIGHT - 2;
      if (y > maxY) {
        y = maxY;
      }
      const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
      // 上一次点到浏览器距离
      const preY = offsetHeight + top + offsetTop;
      const addWidth = y - preY;
      this.$refs.cropBox.style.height = `${offsetHeight + addWidth}px`;
    },
    // 左移
    leftMove(e) {
      let x = e.clientX; //鼠标X坐标
      const { offsetLeft } = this.$refs.box1;
      if (x < offsetLeft) {
        x = offsetLeft;
      }
      const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
      // 右侧线距离浏览器的距离-x
      this.$refs.cropBox.style.width = `${offsetWidth + offsetLeft + left - x}px`;
      // x-parent距离浏览器的距离
      this.$refs.cropBox.style.left = `${x - offsetLeft}px`;
    }

给每个点增加方法调用

javascript 复制代码
 // 选框移动
    onMouseMove(e) {
 .....之前代码
      if (this.isDotDown) {
        switch (this.currentDot) {
          case 'right':
            this.rightMove(e);
            break;
          case 'up':
            this.upMove(e);
            break;
          case 'left':
            this.leftMove(e);
            break;
          case 'down':
            this.downMove(e);
            break;
          case 'rightUp':
            this.rightMove(e);
            this.upMove(e);
            break;
          case 'leftUp':
            this.leftMove(e);
            this.upMove(e);
            break;
          case 'leftDown':
            this.leftMove(e);
            this.downMove(e);
            break;
          case 'rightDown':
            this.rightMove(e);
            this.downMove(e);
            break;
          default:
            break;
        }
        this.setHeightView();
        this.setPreviewImg();
      }
    },

完整的代码文件查看:

html 复制代码
<template>
  <div class="wrap">
    <div ref="box1" class="box1" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseUp">
      <img class="img1" src="./images/7.jpg" alt="" />
      <img ref="clipImg" class="img2" src="./images/7.jpg" alt="" />
      <div ref="cropBox" class="cropBox" @mousedown="onMouseCropBoxDown">
        <div class="leftUp" @mousedown="onMouseDownDot($event, 'leftUp')"></div>
        <div class="up" @mousedown="onMouseDownDot($event, 'up')"></div>
        <div class="rightUp" @mousedown="onMouseDownDot($event, 'rightUp')"></div>
        <div class="right" @mousedown="onMouseDownDot($event, 'right')"></div>
        <div class="rightDown" @mousedown="onMouseDownDot($event, 'rightDown')"></div>
        <div class="down" @mousedown="onMouseDownDot($event, 'down')"></div>
        <div class="leftDown" @mousedown="onMouseDownDot($event, 'leftDown')"></div>
        <div class="left" @mousedown="onMouseDownDot($event, 'left')"></div>
      </div>
    </div>
    <div class="box2">
      <img ref="previewImg" src="./images/7.jpg" alt="" />
    </div>
  </div>
</template>
<script>
const WIDTH = 400;
const HEIGHT = 400;
const MIN = 50;
export default {
  data() {
    return {
      currentDot: '',
      startCropMouseX: 0,
      stertCropMouseY: 0,
      inSelectBox: false, //判断是否在选框中拖动
      isDotDown: false //是否是点位拉伸
    };
  },

  mounted() {},
  methods: {
    // 选框移动
    onMouseMove(e) {
      // offsetLeft返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值。
      const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = this.$refs.cropBox;
      const { offsetX, offsetY } = e;
      if (this.inSelectBox) {
        const xChange = offsetX - this.startCropMouseX; //获取移动距离
        const yChange = offsetY - this.stertCropMouseY;
        let left = offsetLeft + xChange; //每次移动时,获取选框位置
        let top = offsetTop + yChange;
        if (left <= 0) {
          left = 0;
          this.$refs.cropBox.style.left = `${left}px`;
        } else if (left + offsetWidth >= WIDTH) {
          left = WIDTH - offsetWidth; //总宽度-自身宽度
          this.$refs.cropBox.style.left = `${left}px`;
        } else {
          this.$refs.cropBox.style.left = `${left}px`;
        }
        if (top <= 0) {
          top = 0;
          this.$refs.cropBox.style.top = `${top}px`;
        } else if (top + offsetHeight >= HEIGHT) {
          top = HEIGHT - offsetHeight; //总高度-自身高度
          this.$refs.cropBox.style.top = `${top}px`;
        } else {
          this.$refs.cropBox.style.top = `${top}px`;
        }
        this.setHeightView();
        this.setPreviewImg();
      }
      if (this.isDotDown) {
        switch (this.currentDot) {
          case 'right':
            this.rightMove(e);
            break;
          case 'up':
            this.upMove(e);
            break;
          case 'left':
            this.leftMove(e);
            break;
          case 'down':
            this.downMove(e);
            break;
          case 'rightUp':
            this.rightMove(e);
            this.upMove(e);
            break;
          case 'leftUp':
            this.leftMove(e);
            this.upMove(e);
            break;
          case 'leftDown':
            this.leftMove(e);
            this.downMove(e);
            break;
          case 'rightDown':
            this.rightMove(e);
            this.downMove(e);
            break;
          default:
            break;
        }
        this.setHeightView();
        this.setPreviewImg();
      }
    },
    // 鼠标抬起
    onMouseUp(e) {
      this.inSelectBox = false;
      this.isDotDown = false;
    },
    // 选框拖动
    onMouseCropBoxDown(e) {
      const { offsetX, offsetY } = e;
      this.startCropMouseX = offsetX;
      this.stertCropMouseY = offsetY;
      this.inSelectBox = true;
    },
    // 点位移动
    onMouseDownDot(e, localtion) {
      e.stopPropagation();
      this.currentDot = localtion;
      this.isDotDown = true;
    },
    // 设置选区可见位置
    setHeightView() {
      const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
      this.$refs.clipImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
    },
    // 设置预览图片
    setPreviewImg() {
      const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
      this.$refs.previewImg.style.left = `-${offsetLeft}px`;
      this.$refs.previewImg.style.top = `-${offsetTop}px`;
      this.$refs.previewImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
    },
    // 获取裁切path
    getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth) {
      return `polygon(${offsetLeft}px ${offsetTop}px,
      ${offsetLeft + offsetWidth}px ${offsetTop}px,
      ${offsetLeft + offsetWidth}px ${offsetTop + offsetHeight}px,
      ${offsetLeft}px ${offsetTop + offsetHeight}px)`;
    },
    // 右移 获取拉伸宽度+cropBox自身宽度
    rightMove(e) {
      let x = e.clientX; //鼠标X坐标(用offsetX有bug)
      const { offsetLeft } = this.$refs.box1;
      const maxX = offsetLeft + WIDTH - 2;
      if (x > maxX) {
        x = maxX;
      }
      const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
      // 上一次点到浏览器距离
      const preX = offsetLeft + left + offsetWidth;
      const addWidth = x - preX;
      this.$refs.cropBox.style.width = `${offsetWidth + addWidth}px`;
    },
    // 上移
    upMove(e) {
      let y = e.clientY; //鼠标Y坐标
      const { offsetTop } = this.$refs.box1;
      if (y < offsetTop) {
        y = offsetTop;
      }
      const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
      // 右侧线距离浏览器的距离-y
      this.$refs.cropBox.style.height = `${offsetHeight + offsetTop + top - y}px`;
      // y-parent距离浏览器的距离
      this.$refs.cropBox.style.top = `${y - offsetTop}px`;
    },
    // 下移
    downMove(e) {
      let y = e.clientY;
      const { offsetTop } = this.$refs.box1;
      const maxY = offsetTop + HEIGHT - 2;
      if (y > maxY) {
        y = maxY;
      }
      const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
      // 上一次点到浏览器距离
      const preY = offsetHeight + top + offsetTop;
      const addWidth = y - preY;
      this.$refs.cropBox.style.height = `${offsetHeight + addWidth}px`;
    },
    // 左移
    leftMove(e) {
      let x = e.clientX; //鼠标X坐标
      const { offsetLeft } = this.$refs.box1;
      if (x < offsetLeft) {
        x = offsetLeft;
      }
      const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
      // 右侧线距离浏览器的距离-x
      this.$refs.cropBox.style.width = `${offsetWidth + offsetLeft + left - x}px`;
      // x-parent距离浏览器的距离
      this.$refs.cropBox.style.left = `${x - offsetLeft}px`;
    }
  }
};
</script>

<style lang="scss">
.wrap {
  display: flex;
  padding: 30px;

  $boxWidth: 400px; //盒子大小
  $boxHeight: 400px;
  $defaultCropBoxWidth: 150px; //裁切框初始值
  $defaultCropBoxHeight: 150px;
  $dotWidth: 6px; //点

  .box1 {
    width: $boxWidth;
    height: $boxHeight;
    border: 1px solid #000;
    position: relative;
    box-sizing: border-box;

    @mixin img {
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      -webkit-user-drag: none !important;
      user-select: none;
    }

    .img1 {
      @include img;
      opacity: 0.5;
    }

    .img2 {
      @include img;
      clip-path: polygon(
        0 0,
        $defaultCropBoxWidth 0,
        $defaultCropBoxWidth $defaultCropBoxHeight,
        0 $defaultCropBoxHeight
      );
    }

    .cropBox {
      position: absolute;
      left: 0;
      top: 0;
      width: $defaultCropBoxWidth;
      height: $defaultCropBoxHeight;
      border: 1px solid #fff;
      cursor: all-scroll;

      $halfDotWidth: calc(6px / -2);

      @mixin dot {
        position: absolute;
        width: $dotWidth;
        height: $dotWidth;
        background-color: #fff;
      }

      .leftUp {
        @include dot;
        top: $halfDotWidth;
        left: $halfDotWidth;
        cursor: nw-resize;
      }
      .up {
        @include dot;
        top: $halfDotWidth;
        left: 50%;
        margin-left: $halfDotWidth;
        cursor: n-resize;
      }
      .rightUp {
        @include dot;
        top: $halfDotWidth;
        right: $halfDotWidth;
        cursor: ne-resize;
      }
      .right {
        @include dot;
        top: 50%;
        margin-top: $halfDotWidth;
        right: $halfDotWidth;
        cursor: e-resize;
      }
      .rightDown {
        @include dot;
        bottom: $halfDotWidth;
        right: $halfDotWidth;
        cursor: se-resize;
      }
      .down {
        @include dot;
        bottom: $halfDotWidth;
        left: 50%;
        margin-left: $halfDotWidth;
        cursor: s-resize;
      }
      .leftDown {
        @include dot;
        bottom: $halfDotWidth;
        left: $halfDotWidth;
        cursor: sw-resize;
      }
      .left {
        @include dot;
        top: 50%;
        margin-top: $halfDotWidth;
        left: $halfDotWidth;
        cursor: w-resize;
      }
    }
  }

  .box2 {
    width: $boxWidth;
    height: $boxHeight;
    margin-left: 50px;
    position: relative;
    img {
      position: absolute;
      left: 0;
      top: 0;
      width: 400px;
      height: 400px;
      display: block;
      clip-path: polygon(
        0 0,
        $defaultCropBoxWidth 0,
        $defaultCropBoxWidth $defaultCropBoxHeight,
        0 $defaultCropBoxHeight
      );
      -webkit-user-drag: none !important;
      user-select: none;
    }
  }
}
</style>

未完待续。。。。后续会增加一个获取的裁剪图片

相关推荐
●VON2 分钟前
使用 Electron 构建天气桌面小工具:调用公开 API 实现跨平台实时天气查询V1.0.0
前端·javascript·electron·openharmony
穷人小水滴1 小时前
使用 epub 在手机快乐阅读
javascript·deno·科幻
爱学习的程序媛2 小时前
《深入浅出Node.js》核心知识点梳理
javascript·node.js
华仔啊3 小时前
Vue3 如何实现图片懒加载?其实一个 Intersection Observer 就搞定了
前端·vue.js
Robet3 小时前
TS和JS成员变量修饰符
javascript·typescript
方法重载3 小时前
前端性能优化之“代码分割与懒加载”)
javascript
我叫张小白。3 小时前
Vue3 响应式数据:让数据拥有“生命力“
前端·javascript·vue.js·vue3
laocooon5238578863 小时前
vue3 本文实现了一个Vue3折叠面板组件
开发语言·前端·javascript
科普瑞传感仪器4 小时前
从轴孔装配到屏幕贴合:六维力感知的机器人柔性对位应用详解
前端·javascript·数据库·人工智能·机器人·自动化·无人机
n***F8754 小时前
SpringMVC 请求参数接收
前端·javascript·算法