vue-photo-zoom-pro.vue 9.19 KB
<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,
      },
      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;
      },
      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`,
          borderRadius: '50%'
        };
      },
      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: {
      /**
       * @description: imgLoaded
       * @author: renchao
       */
      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);
        }
      },
      /**
       * @description: mouseMove
       * @author: renchao
       */
      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;
        }
      },
      /**
       * @description: initSelectorProperty
       * @param {*} selectorWidth
       * @author: renchao
       */
      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;
      },
      /**
       * @description: mouseLeave
       * @author: renchao
       */
      mouseLeave () {
        this.hideSelector = true;
        if (this.outShow) {
          this.hideOutShow = true;
        }
      },
      /**
       * @description: reset
       * @author: renchao
       */
      reset () {
        Object.assign(this.selector, {
          top: 0,
          left: 0,
          bgLeft: 0,
          bgTop: 0
        });
        this.resetOutShowInitPosition();
      },
      /**
       * @description: resetOutShowInitPosition
       * @author: renchao
       */
      resetOutShowInitPosition () {
        this.outShowInitTop = 0;
      },
      /**
       * @description: resetOutShowInitPosition
       * @param {*} e
       * @author: renchao
       */
      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;
    max-width: 82%;
    margin: 0 auto;
  }
  .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;
    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>