Vue 实现图片放大镜的效果

插件名称:vue-photo-zoom-pro

https://github.com/Mater1996/vue-photo-zoom-pro
demo:https://codepen.io/xbup/project/editor/AjnEgE

原插件有一些小bug,我修改了一下,代码如下:


image.png

index.vue

<template>
 <div style="width: 100%;height: 100%">
   <vue-photo-zoom-pro
     :width="bigWidth"
     :url="url"
     :type="type"
     :scale="scale"
     :out-show="showType"
     :overlayStyle="overlayStyle"
   >
   </vue-photo-zoom-pro>
 </div>

</template>

<script>
 import vuePhotoZoomPro from '@/components/PicZoom/vue-photo-zoom-pro'
 export default {
   name: 'PicZoom',
   components: {  vuePhotoZoomPro },
   data() {
     return {
       type: "square",
       showType: true,
     }
   },
   props: {
     url: {
       type: String,
       required: true,
       default: require('@/assets/vehicle_img/blank_vehicle.jpg')
     },
     bigWidth:{
       type: Number,
       required: true,
       default:168
     },
     scale:{
       type: Number,
       required: true,
       default:3
     },
     overlayStyle:{
       type: String,
       default: 'width:100%;height:100%'
     }
   },
 }
</script>

<style rel="stylesheet/scss" lang="scss" scoped>

</style>

vue-photo-zoom-pro.vue

<template>
  <div class="pic-img">
    <div
      class="img-container"
    >
      <img
        ref="img"
        @load="imgLoaded"
        :src="url"
        :style="overlayStyle"
        @error="imgerrorfun"
      />
      <div class="overlay"  @mousemove.stop="!moveEvent && mouseMove($event)"
           @mouseout.stop="!leaveEvent && mouseLeave($event)"  :style="overlayStyle">
      </div>
      <div
        v-if="!hideZoom && imgLoadedFlag &&!hideSelector"
        :class="['img-selector', {'circle': type === 'circle'}]"
        :style="[imgSelectorStyle, imgSelectorSize, imgSelectorPosition, !outShow && imgBg, !outShow && imgBgSize, !outShow && imgBgPosition]"
      >
        <slot></slot>
      </div>
      <div
        v-if="outShow"
        v-show="!hideOutShow"
        :class="['img-out-show', {'base-line': baseline}]"
        :style="[imgOutShowSize, imgOutShowPosition, imgBg, imgBgSize, imgBgPosition]"
      >
        <div
          v-if="pointer"
          class="img-selector-point"
        ></div>
      </div>
    </div>
  </div>

</template>

<script>
  export default {
    name: "vue-photo-zoom-pro",
    props: {
      url: {
        type: String,
        default: require('@/assets/vehicle_img/blank_vehicle.jpg')
      },
      highUrl: String,
      width: {
        type: Number,
        default: 168
      },
      type: {
        type: String,
        default: "square",
        validator: function(value) {
          return ["circle", "square"].indexOf(value) !== -1;
        }
      },
      selectorStyle: {
        type: Object,
        default() {
          return {};
        }
      },
      outShowStyle: {},
      scale: {
        type: Number,
        default: 3
      },
      moveEvent: {
        type: [Object, MouseEvent],
        default: null
      },
      leaveEvent: {
        type: [Object, MouseEvent],
        default: null
      },
      hideZoom: {
        type: Boolean,
        default: false
      },
      outShow: {
        type: Boolean,
        default: false
      },
      pointer: {
        type: Boolean,
        default: false
      },
      baseline: {
        type: Boolean,
        default: false
      },
      overlayStyle:{
        type: String,
        default: 'width:100%;height:100%'
      },
    },
    data() {
      return {
        selector: {
          width: this.width,
          top: 0,
          left: 0,
          bgTop: 0,
          bgLeft: 0,
          rightBound: 0,
          bottomBound: 0,
          absoluteLeft: 0,
          absoluteTop: 0
        },
        imgInfo: {},
        $img: null,
        screenWidth: document.body.clientWidth,
        outShowInitTop: 0,
        outShowTop: 0,
        hideOutShow: true,
        imgLoadedFlag: false,
        hideSelector: false,
        timer: null
      };
    },
    watch: {
      moveEvent(e) {
        this.mouseMove(e);
      },
      leaveEvent(e) {
        this.mouseLeave(e);
      },
      url(n) {
        this.imgLoadedFlag = false;
        // let img = require('@/assets/vehicle_img/blank_vehicle.jpg')
        // if(n == img){
        //  this.outShow = false
        // }
      },
      width(n) {
        this.initSelectorProperty(n);
      },
      screenWidth(val) {
        if (!this.timer) {
          this.screenWidth = val;
          this.timer = setTimeout(() => {
            this.imgLoaded();
            clearTimeout(this.timer);
            this.timer = null;
          }, 400);
        }
      }
    },
    computed: {
      addWidth() {
        return !this.outShow ? (this.width / 2) * (1 - this.scale) : 0;
      },
      imgSelectorPosition() {
        let { top, left } = this.selector;
        return {
          top: `${top}px`,
          left: `${left}px`
        };
      },
      imgSelectorSize() {
        let width = this.selector.width;
        return {
          width: `${width}px`,
          height: `${width}px`
        };
      },
      imgSelectorStyle() {
        return this.selectorStyle;
      },
      imgOutShowSize() {
        let {
          scale,
          selector: { width }
        } = this;
        return {
          width: `${width * scale}px`,
          height: `${width * scale}px`
        };
      },
      imgOutShowPosition() {
        return {
          top: `${this.outShowTop}px`,
          right: `${-8}px`
        };
      },
      imgBg() {
        return {
          backgroundImage: `url(${this.highUrl || this.url})`
        };
      },
      imgBgSize() {
        let {
          scale,
          imgInfo: { height, width }
        } = this;
        return {
          backgroundSize: `${width * scale}px ${height * scale}px`
        };
      },
      imgBgPosition() {
        let { bgLeft, bgTop } = this.selector;
        return {
          backgroundPosition: `${bgLeft}px ${bgTop}px`
        };
      },
    },
    mounted() {
      this.$img = this.$refs["img"];
    },
    methods: {
      imgLoaded() {
        let imgInfo = this.$img.getBoundingClientRect();
        if (JSON.stringify(this.imgInfo) != JSON.stringify(imgInfo)) {
          this.imgInfo = imgInfo;
          this.initSelectorProperty(this.width);
          this.resetOutShowInitPosition();
        }
        if (!this.imgLoadedFlag) {
          this.imgLoadedFlag = true;
          this.$emit("created", imgInfo);
        }
      },
      mouseMove(e) {
        if (!this.hideZoom && this.imgLoadedFlag) {
          this.imgLoaded();
          const { pageX, pageY, clientY } = e;
          const { scale, selector, outShow, addWidth, outShowAutoScroll } = this;
          let { outShowInitTop } = this;
          const scrollTop = pageY - clientY;
          const { absoluteLeft, absoluteTop, rightBound, bottomBound } = selector;
          const x = pageX - absoluteLeft; // 选择器的x坐标 相对于图片
          const y = pageY - absoluteTop; // 选择器的y坐标
          if (outShow) {
            if (!outShowInitTop) {
              outShowInitTop = this.outShowInitTop = scrollTop + this.imgInfo.top;
            }
            this.hideOutShow && (this.hideOutShow = false);
            this.outShowTop =
              scrollTop > outShowInitTop ? scrollTop - outShowInitTop : 0;
          }
          this.hideSelector && (this.hideSelector = false);
          selector.top = y > 0 ? (y < bottomBound ? y : bottomBound) : 0;
          selector.left = x > 0 ? (x < rightBound ? x : rightBound) : 0;
          selector.bgLeft = addWidth - x * scale; // 选择器图片的坐标位置
          selector.bgTop = addWidth - y * scale;
        }
      },
      initSelectorProperty(selectorWidth) {
        const selectorHalfWidth = selectorWidth / 2;
        const selector = this.selector;
        const { width, height, left, top } = this.imgInfo;
        const { scrollLeft, scrollTop } = document.documentElement;
        selector.width = selectorWidth;
        selector.rightBound = width - selectorWidth;
        selector.bottomBound = height - selectorWidth;
        selector.absoluteLeft = left + selectorHalfWidth + scrollLeft;
        selector.absoluteTop = top + selectorHalfWidth + scrollTop;
      },
      mouseLeave() {
        this.hideSelector = true;
        if (this.outShow) {
          this.hideOutShow = true;
        }
      },
      reset() {
        Object.assign(this.selector, {
          top: 0,
          left: 0,
          bgLeft: 0,
          bgTop: 0
        });
        this.resetOutShowInitPosition();
      },
      resetOutShowInitPosition() {
        this.outShowInitTop = 0;
      },
      imgerrorfun(e){
        // let img = require('@/assets/vehicle_img/blank_vehicle.jpg')
        // this.url = img
        // e.target.src = img
        // e.target.onerror= null
      }
    }
  };
</script>

<style scoped>
  .img-container {
    position: relative;
  }
  .overlay{
    cursor: crosshair;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0.5;
    z-index: 3;
  }

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

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

  .img-out-show {
    z-index: 5000;
    position: absolute;
    background-repeat: no-repeat;
    -webkit-background-size: cover;
    background-color: white;
    transform: translate(100%, 0);
  }

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

  .img-out-show.base-line::after {
    position: absolute;
    box-sizing: border-box;
    content: "";
    width: 1px;
    border: 1px dashed rgba(0, 0, 0, 0.36);
    top: 0;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
  }

  .img-out-show.base-line::before {
    position: absolute;
    box-sizing: border-box;
    content: "";
    height: 1px;
    border: 1px dashed rgba(0, 0, 0, 0.36);
    left: 0;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
  }

</style>

引用

<pic-zoom :url="storageUrl" :bigWidth="150" :scale="3"  overlayStyle=" width: 100%;height: 430px;border-radius: 3px;" ></pic-zoom>

import PicZoom from '@/components/PicZoom'

components: { VehicleInfo ,PicZoom},

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