跳转到内容

图片可拖拽对比轮子

刀刀
0字
0分钟
2026/8/16

效果

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

拖拽

思路

拖拽事件分为以下三步:

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

需要注意的要点:

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

代码

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

<script>
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;
  };
}
</script>

知识点

  • 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, 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>

贡献者

The avatar of contributor named as duyidao duyidao
The avatar of contributor named as 刀刀 刀刀

页面历史

刀刀博客累计访客 人;文档累计访问量共