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>

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

相关推荐
若川1 小时前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
@解忧杂货铺6 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
苹果酱05677 小时前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
真的很上进11 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
web1309332039811 小时前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
supermapsupport12 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
m0_7482548813 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
噢,我明白了14 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__14 小时前
APIs-day2
javascript·css·css3