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>

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

相关推荐
程序媛-徐师姐32 分钟前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
Myli_ing1 小时前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风1 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave1 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟1 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾2 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧2 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
chusheng18402 小时前
Java项目-基于SpringBoot+vue的租房网站设计与实现
java·vue.js·spring boot·租房·租房网站
游走于计算机中摆烂的2 小时前
启动前后端分离项目笔记
java·vue.js·笔记
幼儿园的小霸王3 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue