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>

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

相关推荐
aesthetician26 分钟前
Node.js v25 重磅发布!革新与飞跃:深入探索 JavaScript 运行时的未来
javascript·node.js·vim
知识分享小能手3 小时前
uni-app 入门学习教程,从入门到精通,uni-app基础扩展 —— 详细知识点与案例(3)
vue.js·学习·ui·微信小程序·小程序·uni-app·编程
demi_meng4 小时前
reactNative 遇到的问题记录
javascript·react native·react.js
MC丶科4 小时前
【SpringBoot 快速上手实战系列】5 分钟用 Spring Boot 搭建一个用户管理系统(含前后端分离)!新手也能一次跑通!
java·vue.js·spring boot·后端
千码君20165 小时前
React Native:从react的解构看编程众多语言中的解构
java·javascript·python·react native·react.js·解包·解构
90后的晨仔7 小时前
Pinia 状态管理原理与实战全解析
前端·vue.js
EndingCoder7 小时前
WebSocket实时通信:Socket.io
服务器·javascript·网络·websocket·网络协议·node.js
90后的晨仔7 小时前
Vue3 状态管理完全指南:从响应式 API 到 Pinia
前端·vue.js
90后的晨仔7 小时前
Vue 内置组件全解析:提升开发效率的五大神器
前端·vue.js
我胡为喜呀7 小时前
Vue3 中的 watch 和 watchEffect:如何优雅地监听数据变化
前端·javascript·vue.js