跳转到内容

图片可拖拽对比轮子

刀刀

12/31/2024

0 字

0 分钟

效果

多张效果图中可以两两对比,效果图可拖拽移动位置,可通过鼠标滚轮放大缩小。

拖拽

思路

拖拽事件分为以下三步:

  1. 鼠标移入图片标签内,捕获所有鼠标按下的事件 setCapture
  2. 获取鼠标移动事件 onmousemove ,获取其移动的偏移量 clientXclientY
  3. 获取鼠标松开事件,取消所有事件捕获 releaseCapture

需要注意的要点:

  • 当前移动的是哪个图片(通过传参来判断)
  • 图片 dom 元素是动态创建的,不是固定写死的,因此要动态为元素绑定移动事件

代码

js
<img class='bigger_img_left' :src="$SN_DESIGN_BASE_IMG + imgs[0].imgUrl" ref="leftImg" :style="leftStyle" :width="width+'px'" :height="height+'px'">

initImgMoveFn(){
  const that = this

  this.$nextTick(() => {
    // 左侧图片元素获取
    let imgs_left = document.querySelector('.bigger_img_left')
    imgs_left && that.onMove(imgs_left, 'leftStyle')

    // 右侧图片元素获取
    let imgs_right = document.querySelector('.bigger_img_right')
    imgs_right && that.onMove(imgs_right, 'rightStyle')
  })
},

// 绑定移动事件,obj为触发事件的dom元素,data为该元素的style样式对象
onMove(obj, data) {
  const that = this
    obj.onmousedown = function(event){

      /* 设置box1捕获所有鼠标按下的事件
       * setCapture()
       *  - 只有IE支持,但是在火狐中调用时不会报错,而如果使用chrome调用,会报错
       */
      obj.setCapture && obj.setCapture();

      event = event ||window.event
      //div的偏移量,鼠标.clientX-元素.offsetLeft;鼠标.clientY-元素.offsetTop
      var ol = event.clientX -obj.offsetLeft;
      var ot = event.clientY - obj.offsetTop;
      //为document绑定一个onmousemove事件
      document.onmousemove = function(event){
        event = event ||window.event
        //获取鼠标的坐标
        var left = event.clientX-ol;
        var top = event.clientY-ot;

        //修改box1的位置
        that[data].left = left+"px";
        that[data].top = top+"px";
      };

      //为元素绑定一个鼠标松开事件
      document.onmouseup = function(){
        //当鼠标松开时,被拖拽元素固定在当前位置 onmouseup
        //取消document的onmousemove事件
        document.onmousemove = null;
        document.onmouseup = null;
        //当鼠标松开时,取消对事件的捕获
        obj.releaseCapture && obj.releaseCapture();
      };
      /*
       * 当我们拖拽一个网页的内容时,浏览器会默认去搜索引擎中搜索内容
       *   此时会导致拖拽功能的异常,这是浏览器提供的默认行为
       * 	 如果不希望发生这个行为,则可以通过return false来取消默认行为
       */
      return false;
    };
},

知识点

  • document.releaseCapture

    非标准方法,使用前请注意跨浏览器支持。

    如果该 document 中的一个元素之上当前启用了鼠标捕获,则释放鼠标捕获。通过调用 element.setCapture() 实现在一个元素上启用鼠标捕获。

  • element.setCapture

    非标准方法,使用前请注意跨浏览器支持。

    在处理一个 mousedown 事件过程中调用这个方法来把全部的鼠标事件重新定向到这个元素,直到鼠标按钮被释放或者 document.releaseCapture() 被调用。

    括号内如果被设置为 true, 所有事件被直接定向到这个元素; 如果是 false, 事件也可以在这个元素的子元素上触发。

    注意

    该方法已弃用,请不要在新网站中使用。

  • $nextTick

    Vue 是异步执行 dom 更新的,一旦观察到数据变化,Vue 就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个 watcher 被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和 Dom 操作。而在下一个事件循环时,Vue 会清空队列,并进行必要的 DOM 更新。 当设置 vm.someData = 'new value'DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的 DOM 更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

滚轮缩放

思路

事件 wheel 在滚动鼠标滚轮或操作其他类似输入设备时触发。因此可以通过它监听整个 window 的鼠标滚轮事件。触发事件后:

  1. 判断当前触发事件的鼠标 x 轴坐标,如果小于50%,则左边图片缩放,反之右边图片缩放(因为两张图片平分所有空间)
  2. 判断当前图片的宽高,如果小于等于0则停止缩小
  3. 判断当前执行的操作是放大还是缩小,触发事件后可通过 e.deltaY 参数获取到当前滚轮的方向

代码

js
mounted() {
  // 监听页面滚动事件
  window.addEventListener("wheel", this.onWhell);
},

onWhell(e) {
  if (window.innerWidth / 2 > e.clientX) {
     // 左侧图片的宽高
    if (e.deltaY > 0) {
      this.width += 20
      this.height += 10
    } else {
      if(this.width<=30 || this.height<=30) return
      this.width -= 20
      this.height -= 10
    }
  } else {
     // 右图片的宽高
    if (e.deltaY > 0) {
      this.w += 20
      this.h += 10
    } else {
      if(this.w<=30 || this.h<=30) return
      this.w -= 20
      this.h -= 10
    }
  }
},

总体代码

vue
<template>
  <div>
    <div class='box' v-if='showPreview'>
      <div class='close' @click='closeFn'>×</div>
      <div class='top'>
        <div class='left'>
          <template v-if='imgs[0] && imgs[0].id'>
            <div class='name' @click='changeImgFn(0)'>{{imgs[0].name}}(点我切换)效果图</div>
            <div class='btns' @click='handleDownLoadFn(0,0)'>下载完整图</div>
            <div class='btns' @click='handleDownLoadFn(0,1)'>下载裁剪图</div>
          </template>
        </div>
        <div class='right'>
          <template v-if='imgs[1] && imgs[1].id'>
            <div class='name' @click='changeImgFn(1)'>{{imgs[1].name}}(点我切换)效果图</div>
            <div class='btns' @click='handleDownLoadFn(1,0)'>下载完整图</div>
            <div class='btns' @click='handleDownLoadFn(1,1)'>下载裁剪图</div>
          </template>
        </div>
      </div>

      <div class='bottom'>
        <div ref="left_box">
          <div class='list_box' v-if='!imgs[0] || !imgs[0].id'>
            <div class='list' v-for='item in resultList' :key='item.id' @click='handlePreviewFn(item, 0)'>{{item.name}}</div>
          </div>
          <template  v-else>
            <img class='bigger_img_left' :src="$SN_DESIGN_BASE_IMG + imgs[0].imgUrl" ref="leftImg" :style="leftStyle"
                 :width="width+'px'" :height="height+'px'">
            <div class='thumbnail'>
              <img :src="$SN_DESIGN_BASE_IMG + imgs[0].thumbnail" width="100px" height="100px">
              <div>裁剪效果</div>
            </div>
          </template>
        </div>
        <div>
          <div class='list_box' v-if='!imgs[1] || !imgs[1].id'>
            <div class='list' v-for='item in resultList' :key='item.id' @click='handlePreviewFn(item, 1)'>{{item.name}}</div>
          </div>
          <template v-else>
            <img class='bigger_img_right' :src="$SN_DESIGN_BASE_IMG + imgs[1].imgUrl" alt="" :width="w+'px'" :height="h+'px'" :style="rightStyle">
            <div class='thumbnail'>
              <img :src="$SN_DESIGN_BASE_IMG + imgs[1].thumbnail" width="100px" height="100px">
              <div>裁剪效果</div>
            </div>
          </template>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    props: {
      resultList: {
        type: Array,
        default: () => []
      }
    },
    data() {
      return {
        showPreview:false,
        width: 320,
        height: 160,
        w: 320,
        h: 160,
        leftStyle: {
          position: 'absolute',
          // transition: 'all 0.3s',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
        },
        rightStyle: {
          position: 'absolute',
          // transition: 'all 0.3s',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
        },
        imgs: [],
      }
    },
    mounted() {
      // 监听页面滚动事件
      const that = this
      window.addEventListener("wheel", that.onWhell);
    },
    methods: {
      initImgMoveFn(){
        const that = this

        this.$nextTick(() => {
          let imgs_left = document.querySelector('.bigger_img_left')
          imgs_left && that.onMove(imgs_left, 'leftStyle')

          let imgs_right = document.querySelector('.bigger_img_right')
          imgs_right && that.onMove(imgs_right, 'rightStyle')
        })
      },
      onMove(obj, data) {
        console.log(obj, data)
        const that = this
          obj.onmousedown = function(event){

            //设置box1捕获所有鼠标按下的事件
            /*
             * setCapture()
             *  - 只有IE支持,但是在火狐中调用时不会报错
             * 		而如果使用chrome调用,会报错
             */
            obj.setCapture && obj.setCapture();


            event = event ||window.event
            //div的偏移量,鼠标.clientX-元素.offsetLeft

            //div的偏移量,鼠标.clientY-元素.offsetTop

            var ol = event.clientX -obj.offsetLeft;

            var ot = event.clientY - obj.offsetTop;
            //为document绑定一个onmousemove事件

            document.onmousemove = function(event){

              event = event ||window.event
              //当鼠标移动时被拖拽的元素跟随鼠标移动 onmousemove

              //获取鼠标的坐标
              var left = event.clientX-ol;
              var top = event.clientY-ot;

              console.log(left)
              console.log(top)

              //修改box1的位置
              that[data].left = left+"px";
              that[data].top = top+"px";

            };


            //为元素绑定一个鼠标松开事件
            document.onmouseup = function(){
              //当鼠标松开时,被拖拽元素固定在当前位置 onmouseup
              //取消document的onmousemove事件

              document.onmousemove = null;
              document.onmouseup = null;
              //当鼠标松开时,取消对事件的捕获
              obj.releaseCapture && obj.releaseCapture();
            };
            /*
             * 当我们拖拽一个网页的内容时,浏览器会默认去搜索引擎中搜索内容
             *   此时会导致拖拽功能的异常,这是浏览器提供的默认行为
             * 	 如果不希望发生这个行为,则可以通过return false来取消默认行为
             */
            return false;
          };
      },
      changeImgFn(e) {
        this.$set(this.imgs, e, {})
      },
      // 关闭效果图预览
      closeFn() {
        this.showPreview = false
        this.imgs = []
        this.width = 320
          this.height = 160
          this.w = 320
          this.h = 160
          this.leftStyle = {
            position: 'absolute',
            // transition: 'all 0.3s',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
        }
        this.rightStyle = {
          position: 'absolute',
          // transition: 'all 0.3s',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
        }
        this.$emit('closeFn')
      },
      // 点击下载按钮:type左0还是右1;which完整图0还缩略图1
      handleDownLoadFn(type, which) {
        const that = this
        var image = new Image();
        // 解决跨域 Canvas 污染问题
        image.setAttribute("crossOrigin", "anonymous");
        image.onload = function() {
          var canvas = document.createElement("canvas");
          canvas.width = image.width;
          canvas.height = image.height;
          var context = canvas.getContext("2d");
          context.drawImage(image, 0, 0, image.width, image.height);
          var url = canvas.toDataURL("image/png",0.7); //得到图片的base64编码数据
          var a = document.createElement("a"); // 生成一个a元素
          var event = new MouseEvent("click"); // 创建一个单击事件
          a.download = which ? that.imgs[type].name+'裁剪图.png' : that.imgs[type].name+'.png'; // 设置图片名称
          a.href = url; // 将生成的URL设置为a.href属性
          a.dispatchEvent(event); // 触发a的单击事件
        };
        let imgsrc = which ? this.imgs[type].thumbnail :this.imgs[type].imgUrl
        image.src = this.$SN_DESIGN_BASE_IMG + imgsrc
        console.log(image)
      },
      // 点击选择图片
      handlePreviewFn(e, i) {
        this.$set(this.imgs, i, e)
        this.initImgMoveFn()
      },
      onWhell(e) {
        if (window.innerWidth / 2 > e.clientX) {
          if (e.deltaY > 0) {
            this.width += 20
            this.height += 10
          } else {
            if(this.width<=30 || this.height<=30) return
            this.width -= 20
            this.height -= 10
          }
        } else {
          if (e.deltaY > 0) {
            this.w += 20
            this.h += 10
          } else {
            if(this.w<=30 || this.h<=30) return
            this.w -= 20
            this.h -= 10
          }
        }
      },
    }
  }
</script>

<style lang='less' scoped>
.box {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;height: 100vh;
  background-color: rgba(0,0,0,.5);
  z-index:9999;
  
  .bigger_img_left,
  .bigger_img_right {
    cursor: move;
  }

  .close {
    position: absolute;
    top: 10px;
    right: 20px;
    font-size: 25px;
    cursor: pointer;
  }

  .top {
    height: 60px;
    line-height: 60px;
    background-color: #fff;

    > div {
      display: inline-block;
      height: 100%;
      width: 50%;
      overflow: hidden;
      padding-left: 25px;

      > div {
        display: inline-block;
      }

      .name {
        cursor: pointer;

        &:hover {
          color: #1890ff;
        }
      }
      
      .btns {
        height: 50px;
        line-height: 50px;
        margin-top: 5px;
        padding: 0 20px;
        border: 1px solid #ccc;
        border-radius: 10px;
        cursor: pointer;
        margin-left: 20px;
        box-sizing: border-box;

        &:hover {
          background-color: #1890ff;
          color: #fff;
          border: 1px solid #1890ff;
        }
      }

      .name {
        font-size: 20px;
        font-weight: 600;
      }
    }
  }

  .bottom {
    height: calc(100vh - 60px);
    > div {
      display: inline-block;
      position: relative;
      height: 100%;
      width: 50%;border: 1px solid red;
      overflow: hidden;

      .list_box {
        height: 500px;
        margin-top: 200px;
        overflow-y: auto;
      }
      .list {
        background-color: #fff;
        width: 50%;
        height: 60px;
        line-height: 60px;
        text-align: center;
        margin: 10px auto;
        cursor: pointer;
        &:hover {
          color: #fff;
          background-color: #1890ff;
        }
        &:first-child {
          margin-top: 200px;
        }
      }

      .thumbnail {
        position: absolute;
        top: 0;
        right: 0;
        width: 120px;
        text-align: center;
        padding-top: 10px;
        height: 140px;
        background-color: #fff;
        box-sizing: border-box;
      }
    }
  }
}
</style>