一个VUE照片放大镜的插件

<template>
  <div class="photo-zoom-pro">
    <div
      class="container"
      @mouseenter="!disabled && !enterEvent && mouseEnter($event)"
      @mousemove="!disabled && !moveEvent && mouseMove($event)"
      @mouseleave="!disabled && !leaveEvent && mouseLeave($event)"
    >
      <img class="origin-img" ref="img" @load="imgLoaded($event)" />
      <div
        v-if="selector"
        v-show="!hideZoomer && imgLoadedFlag && showText.isShow"
        class="div-top-txt"
        :style="[
          zoomerTopTxtPosition,
        ]">
        <span class="span-top-text">{{showText.wfxw}}</span>
      </div>
      <div
        v-if="selector"
        v-show="!hideZoomer && imgLoadedFlag"
        :class="['img-zoomer', { circle: type === 'circle' }]"
        :style="[
          zoomerStyle,
          zoomerSize,
          zoomerPosition,
          !outZoomer && zoomerBgUrl,
          !outZoomer && zoomerBgSize,
          !outZoomer && zoomerBgPosition
        ]">
        <slot name="zoomer"></slot>
      </div>
      <div
        v-if="selector"
        v-show="!hideZoomer && imgLoadedFlag && showText.isShow"
        class="div-bottom-txt"
        :style="[
          zoomerBottomTxtPosition,
        ]">
        <div style="text-align: center;margin-bottom: 5px">
          <span class="span-top-text" style="color: #1E90FF;font-size: 18px;font-weight: bold" v-if="showText.licenseCategory == '02'">{{showText.hpzlName}}</span>
          <span class="span-top-text" style="color: #FFA500;font-size: 18px;font-weight: bold" v-else-if="showText.licenseCategory == '01'">{{showText.hpzlName}}</span>
          <span class="span-top-text" style="color: #13ce66;font-size: 18px;font-weight: bold" v-else-if="showText.licenseCategory == 'F07'
               || showText.licenseCategory == '51'
               || showText.licenseCategory == '52'">
          {{showText.hpzlName}}
        </span>
          <span class="span-top-text" style="font-size: 18px;font-weight: bold" v-else>{{showText.hpzlName}}</span>
          <span class="span-top-text" style="font-size: 18px;font-weight: bold">{{showText.hphm}}</span>
        </div>
        <span class="span-top-text">车辆类型:{{showText.cllx}}</span>
        <br>
        <span class="span-top-text">车牌品牌:{{showText.clpp}}</span>
        <br>
        <span class="span-top-text">车牌颜色:{{showText.cpys}}</span>
      </div>
      <div
        v-if="outZoomer"
        v-show="!hideOutZoomer"
        :class="['img-out-show', { 'base-line': baseline }]"
        :style="[
          outZoomerStyle,
          outZoomerSize,
          outZoomerPosition,
          zoomerBgUrl,
          zoomerBgSize,
          zoomerBgPosition
        ]"
      >
        <div v-if="pointer" class="img-zoomer-point"></div>
        <slot name="outzoomer"></slot>
      </div>
      <slot></slot>
    </div>
  </div>
</template>
<script>
  export default {
    name: "photoZoom",
    props: {
      url: String,
      highUrl: String,
      width: {
        type: Number,
        default: 168
      },
      height: {
        type: Number,
        default: -1
      },
      type: {
        type: String,
        default: "square",
        validator: function(value) {
          return ["circle", "square"].indexOf(value) !== -1;
        }
      },
      zoomerStyle: {
        type: Object,
        default() {
          return {};
        }
      },
      outZoomerStyle: {
        type: Object,
        default() {
          return {};
        }
      },
      scale: {
        type: Number,
        default: 4
      },
      enterEvent: {
        type: [Object, UIEvent],
        default: null
      },
      moveEvent: {
        type: [Object, UIEvent],
        default: null
      },
      leaveEvent: {
        type: [Object, UIEvent],
        default: null
      },
      selector: {
        type: Boolean,
        default: true
      },
      outZoomer: {
        type: Boolean,
        default: false
      },
      pointer: {
        type: Boolean,
        default: false
      },
      baseline: {
        type: Boolean,
        default: false
      },
      disabledReactive: {
        type: Boolean,
        default: false
      },
      disabled: {
        type: Boolean,
        default: false
      },
      showText:{
        type: Object,
        default() {
          return {
            isShow:false,
            clpp:'',
            cpys:'',
            cllx:'',
            clzt:'',
            hpzlName:'',
            licenseCategory:'',
            hphm:'',
            wfxw:'',
          };
        }
      },
    },
    data() {
      return {
        zoomerRect: {
          top: 0, // 当前缩放器的位置
          left: 0,
          absoluteLeft: 0, // 缩放器初始位置相对于屏幕的位置
          absoluteTop: 0
        },
        zoomerBgRect: {
          top: 0, // 背景位置
          left: 0
        },
        zoomerPoint: {
          leftBound: 0, //缩放器的边界
          topBound: 0,
          rightBound: 0,
          bottomBound: 0
        },
        vZoomerPoint: {
          //虚拟缩放器(相对于背景上的缩放器)
          leftBound: 0, //虚拟缩放器鼠标边界
          topBound: 0,
          rightBound: 0,
          bottomBound: 0
        },
        hideZoomer: true, // 是否隐藏缩放器
        hideOutZoomer: true, // 是否隐藏外部缩放器
        imgLoadedFlag: false, // 图片加载完毕标识
        outZoomerInitTop: 0, // 外部缩放器的初始位置
        outZoomerTop: 0, // 外部缩放器的位置
        imgInfo: {}, // 图片信息
        $img: null // 图片dom
      };
    },
    watch: {
      /**
       * 缩放比例变化时,手动触发move事件来更改大小
       * 主要解决放大器在内部时的比例变化时的响应
       */
      scale() {
        this.initVZoomerPoint();
        !this.outZoomer && this.mouseMove();
      },
      /**
       * 外部事件变化时的响应
       */
      enterEvent: "mouseEnter",
      moveEvent: "mouseMove",
      leaveEvent: "mouseLeave",
      /**
       * 图片地址变化时重置
       */
      url: "handlerUrlChange",
      /**
       * 缩放器宽度/高度变化时重置缩放器属性
       */
      width: "initZoomerPoint",
      height: "initZoomerPoint",
      vZoomerHalfWidth: "initVZoomerPoint",
      vZoomerHalfHeight: "initVZoomerPoint"
    },
    computed: {
      /**
       * 缩放器宽高
       * 有高度用高度没高度用宽度
       */
      zoomerHeight() {
        return this.height > 0 ? this.height : this.width;
      },
      /**
       * 缩放器宽/高半径
       */
      zoomerHalfWidth() {
        return this.width / 2;
      },
      zoomerHalfHeight() {
        return this.zoomerHeight / 2;
      },
      /**
       * 虚拟缩放器宽/高半径
       */
      vZoomerHalfWidth() {
        const zoomerHalfWidth = this.zoomerHalfWidth;
        return this.outZoomer ? zoomerHalfWidth * this.scale : zoomerHalfWidth;
      },
      vZoomerHalfHeight() {
        const zoomerHalfHeight = this.zoomerHalfHeight;
        return this.outZoomer ? zoomerHalfHeight * this.scale : zoomerHalfHeight;
      },
      /**
       * 缩放器位置
       */
      zoomerPosition() {
        const { top, left } = this.zoomerRect;
        return {
          top: `${top}px`,
          left: `${left}px`
        };
      },
      /**
       * 放大图片顶部信息
       */
      zoomerTopTxtPosition() {
        const { left, top } = this.zoomerRect;
        return {
          top: `${top-40}px`,
          left: `${left}px`,
        };
      },
      /**
       * 放大图片底部信息
       */
      zoomerBottomTxtPosition() {
        const { left, top } = this.zoomerRect;
        return {
          top: `${top+350}px`,
          left: `${left}px`,
        };
      },

      /**
       * 大图片顶部信息容器大小
       */
      zoomerTopTxtSize() {
        const { width, zoomerHeight: height } = this;
        return {
          width: `${width}px`,
          height: `${height}px`
        };
      },
      /**
       * 缩放器大小
       */
      zoomerSize() {
        const { width, zoomerHeight: height } = this;
        return {
          width: `${width}px`,
          height: `${height}px`
        };
      },
      /**
       * 外部缩放器大小
       */
      outZoomerSize() {
        const { scale, width, zoomerHeight: height } = this;
        return {
          width: `${width * scale}px`,
          height: `${height * scale}px`
        };
      },
      /**
       * 外部缩放器位置
       */
      outZoomerPosition() {
        return {
          top: `${this.outZoomerTop}px`
        };
      },
      /**
       * 高清图地址地址
       */
      zoomerBgUrl() {
        return {
          backgroundImage: `url(${this.highUrl || this.url})`
        };
      },
      /**
       * 高清图大小
       */
      zoomerBgSize() {
        const {
          scale,
          imgInfo: { height, width }
        } = this;
        return {
          backgroundSize: `${width * scale}px ${height * scale}px`
        };
      },
      /**
       * 高清图位置
       */
      zoomerBgPosition() {
        const { left, top } = this.zoomerBgRect;
        return {
          backgroundPosition: `${left}px ${top}px`
        };
      },
    },
    created() {
      this.url && this.handlerUrlChange();
      this.beforeReactivateMoveFns = [];
    },
    mounted() {
      this.$img = this.$refs["img"];
      this.addResizeListener(this.$img, rect => {
        this.imgInfo = rect;
        this.handlerImgResize();
      });
    },
    methods: {
      /**
       * 为某个dom添加监听dom位置或者大小变化的
       */
      addResizeListener(dom, cb) {
        if (!this.disabledReactive) {
          // if (ResizeObserver) {
          //   const resizeObserver = new ResizeObserver(([entrie]) => {
          //     const {contentRect} = entrie
          //     cb && contentRect && cb(contentRect.toJSON());
          //   });
          //   resizeObserver.observe(dom);
          // } else {
          //   this.beforeReactivateMoveFns.push(() => {
          //     const rect = dom.getBoundingClientRect().toJSON();
          //     if (this.validImgResize(rect)) {
          //       cb && cb(rect);
          //     }
          //   });
          // }
          this.beforeReactivateMoveFns.push(() => {
            // const rect =  dom.getBoundingClientRect().toJSON();
            const rect =  this.getObjXy(dom);
            if (this.validImgResize(rect)) {
              cb && cb(rect);
            }
          });
        }
      },
      getObjXy(obj){
        var xy = obj.getBoundingClientRect();
        var top = xy.top-document.documentElement.clientTop+document.documentElement.scrollTop,//document.documentElement.clientTop 在IE67中始终为2,其他高级点的浏览器为0
          bottom = xy.bottom,
          left = xy.left-document.documentElement.clientLeft+document.documentElement.scrollLeft,//document.documentElement.clientLeft 在IE67中始终为2,其他高级点的浏览器为0
          right = xy.right,
          width = xy.width||right - left, //IE67不存在width 使用right - left获得
          height = xy.height||bottom - top,
          x = xy.x,
          y = xy.y;
        return {
          top:top,
          right:right,
          bottom:bottom,
          left:left,
          width:width,
          height:height,
          x:x,
          y:y,
        }
      },
      /**
       * 图片url改变
       */
      handlerUrlChange() {
        this.imgLoadedFlag = false;
        this.loadImg(this.url).then(this.imgLoaded, console.error);
      },
      /**
       * 加载图片
       * @param {String} 图片地址
       * @return {Promise}
       */
      loadImg(url) {
        return new Promise((resolve, reject) => {
          const img = document.createElement("img");
          img.addEventListener("load", resolve);
          img.addEventListener("error", reject);
          img.src = url;
        });
      },
      /**
       * 图片记载完毕事件
       */
      imgLoaded() {
        const $img = this.$img;
        if (!this.imgLoadedFlag) {
          this.imgLoadedFlag = true;
          $img.src = this.url;
          setTimeout(() => {
            // this.imgInfo = $img.getBoundingClientRect().toJSON();
            this.imgInfo = this.getObjXy($img);
            this.handlerImgResize();
            this.$emit("created", $img, this.imgInfo);
          });
        }
      },
      /**
       * 检测img大小或者位置是否改变
       */
      validImgResize(imgInfo) {
        return JSON.stringify(this.imgInfo) !== JSON.stringify(imgInfo);
      },
      /**
       * 图片大小或者位置改变后的事件
       */
      handlerImgResize() {
        this.initZoomerProperty();
        this.resetOutZoomPosition();
      },
      /**
       * 鼠标移入事件
       */
      mouseEnter(e) {
        if (this.imgLoadedFlag) {
          this.hideZoomer = false;
        }
        this.$emit("mouseenter", e);
      },
      /**
       * 鼠标移动事件, 触摸
       */
      mouseMove(e) {
        if (this.hideZoomer) return;
        e = e || this.pointerInfo;
        if (this.imgLoadedFlag && e) {
          // 缓存event信息
          this.pointerInfo = e;
          // 执行move响应前的方法
          this.beforeReactivateMoveFns.forEach(fn => fn.call(this));
          const { pageX, pageY, clientY } = e;
          const scrollTop = pageY - clientY;
          const {
            scale,
            zoomerRect,
            zoomerBgRect,
            zoomerPoint,
            vZoomerPoint,
            outZoomer,
            zoomerHalfWidth,
            zoomerHalfHeight,
            vZoomerHalfWidth,
            vZoomerHalfHeight
          } = this;
          const { absoluteLeft, absoluteTop } = zoomerRect;
          const { leftBound, topBound, rightBound, bottomBound } = zoomerPoint;
          const {
            leftBound: vZoomerLeftBound,
            topBound: vZoomerTopBound,
            rightBound: vZoomerRightBound,
            bottomBound: vZoomerBottomBound
          } = vZoomerPoint;
          let outZoomerInitTop = this.outZoomerInitTop;
          //鼠标相对于容器的位置
          const x = pageX - absoluteLeft;
          const y = pageY - absoluteTop;
          // 记录/修改外部缩放器的位置
          // 缩放器当前左上角的位置
          const zoomerLeft = x > leftBound ? Math.min(x, rightBound) : leftBound;
          const zoomerTop = y > topBound ? Math.min(y, bottomBound) : topBound;
          // 当前鼠标相对于背景的位置
          const vZoomerX =
            x * scale > vZoomerLeftBound
              ? Math.min(x * scale, vZoomerRightBound)
              : vZoomerLeftBound;
          const vZoomerY =
            y * scale > vZoomerTopBound
              ? Math.min(y * scale, vZoomerBottomBound)
              : vZoomerTopBound;
          // 更新UI位置
          zoomerRect.left = zoomerLeft - zoomerHalfWidth; // 缩放器偏移到中心
          zoomerRect.top = zoomerTop - zoomerHalfHeight;
          zoomerBgRect.left = -vZoomerX + vZoomerHalfWidth; // 背景位置偏移到左上角
          zoomerBgRect.top = -vZoomerY + vZoomerHalfHeight;
          if (outZoomer) {
            if (!outZoomerInitTop) {
              outZoomerInitTop = this.outZoomerInitTop =
                scrollTop + this.imgInfo.top;
            }
            this.hideOutZoomer && (this.hideOutZoomer = false);
            this.outZoomerTop =
              scrollTop > outZoomerInitTop ? scrollTop - outZoomerInitTop : 0;
          }
        }
        this.$emit("mousemove", e);
      },
      /**
       * 鼠标移出事件
       */
      mouseLeave(e) {
        this.hideZoomer = true;
        if (this.outZoomer) {
          this.hideOutZoomer = true;
        }
        this.$emit("mouseleave", e);
      },
      /**
       * 初始化选择器的属性
       */
      initZoomerProperty() {
        const zoomerRect = this.zoomerRect;
        const { left, top } = this.imgInfo;
        const { documentElement, body } = document;
        const scrollTop =
          documentElement.scrollTop || window.pageYOffset || body.scrollTop;
        const scrollLeft =
          documentElement.scrollLeft || window.pageXOffset || body.scrollLeft;
        zoomerRect.absoluteLeft = left + scrollLeft; // 缩放器初始位置相对于屏幕左上角的位置
        zoomerRect.absoluteTop = top + scrollTop;
        this.initZoomerPoint();
        this.initVZoomerPoint();
      },
      initZoomerPoint() {
        const zoomerHalfWidth = this.zoomerHalfWidth;
        const zoomerHalfHeight = this.zoomerHalfHeight;
        const { width, height } = this.imgInfo;
        const zommerPoint = this.zoomerPoint;
        zommerPoint.leftBound = zoomerHalfWidth; //鼠标相对于容器的边界
        zommerPoint.topBound = zoomerHalfHeight;
        zommerPoint.rightBound = width - zoomerHalfWidth;
        zommerPoint.bottomBound = height - zoomerHalfHeight;
      },
      /**
       * 初始化选择器背景属性
       */
      initVZoomerPoint() {
        const vZoomerPoint = this.vZoomerPoint;
        const vZoomerHalfWidth = this.vZoomerHalfWidth;
        const vZoomerHalfHeight = this.vZoomerHalfHeight;
        const { width, height } = this.imgInfo;
        const scale = this.scale;
        vZoomerPoint.leftBound = vZoomerHalfWidth; //虚拟缩放器鼠标相对于容器的边界
        vZoomerPoint.topBound = vZoomerHalfHeight;
        vZoomerPoint.rightBound = width * scale - vZoomerHalfWidth;
        vZoomerPoint.bottomBound = height * scale - vZoomerHalfHeight;
      },
      /**
       * 重置
       */
      reset() {
        this.initZoomerProperty();
      },
      /**
       * 重置外部放大区域属性
       */
      resetOutZoomPosition() {
        this.outZoomerInitTop = 0;
      }
    }
  };
</script>

<style scoped>
  .container {
    position: relative;
    width: 100%;
    height: 100%;
  }

  .container .origin-img {
    width: 100%;
    height: 100%;
    display: block;
  }

  .img-zoomer {
    position: absolute;
    cursor: crosshair;
    border: 1px solid rgba(0, 0, 0, 0.1);
    background-repeat: no-repeat;
    background-color: rgba(0, 0, 0, 0.6);
    box-sizing: border-box;
  }

  .div-top-txt{
    position: absolute;
    z-index: 99;
    width: 350px;
    height: 40px;
    padding: 5px;
    background: white;
  }

  .div-bottom-txt{
    position: absolute;
    z-index: 99;
    width: 350px;
    height: 100px;
    padding: 5px;
    background: white;
  }
  .span-top-text{
    font-size: 15px;
  }

  .img-zoomer.circle {
    border-radius: 50%;
  }

  .img-out-show {
    position: absolute;
    right: -8px;
    background-repeat: no-repeat;
    transform: translate(100%, 0);
    border: 1px solid rgba(0, 0, 0, 0.1);
    box-sizing: border-box;
  }

  .img-zoomer-point {
    position: absolute;
    width: 4px;
    height: 4px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: black;
  }

  .img-out-show.base-line::after,
  .img-out-show.base-line::before {
    position: absolute;
    box-sizing: border-box;
    content: "";
    border: 1px dashed rgba(0, 0, 0, 0.36);
  }

  .img-out-show.base-line::after {
    width: 1px;
    top: 0;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
  }

  .img-out-show.base-line::before {
    height: 1px;
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
  }
</style>

1.新建photoZoom.vue放到 components中
2.用法如下:
<photo-zoom
ref="zoom"
:width="350"
:url="Image_ELAPSE_URL + form.localElapsedImgUrl"
:type="type"
:out-zoomer="outZoomer"
:zoomer-style="{'background-color':'rgba(0,0,0,0.6)'}"
:showText="showText"
style="width: 100%;height: 100%">
</photo-zoom>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351