使用vue实现商品放大镜效果

本文详细介绍了如何使用Vue.js技术实现商品详情页的放大镜效果,通过创建组件、处理鼠标移动事件以及利用CSS样式,让用户体验到便捷的商品预览功能。教程包括关键代码示例和步骤解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// 此代码依赖于vue 通过使用组件的方式引用此代码 需要传入的数据均写有注释
//这次主要优化了放大图片的位移比例 使放大图片的位移更加精确 更符合用户习惯 添加了缩略图片超出处理样式 

// 优化之后的代码
<template>
  <div class="magnifier">
    <div class="outer-box">
      <!-- 放大图片 -->
      <div class="magnifier-box"
           v-show="elemenImgShow"
           ref="thumbnailBox">
        <div class="inner-box"
             ref="maginiferInnerbox">
          <img ref="thumbnailImg"
               :src="magnifierImgArr.magnifier[imgIndex].src"
               alt="">
        </div>
      </div>
      <!-- 展示的图片 -->
      <div class="img-show"
           ref="imgShowBox">
        <div class="magnifier-img"
             v-show="elemenImgShow"
             ref="magnifierImg"></div>
        <img :src="magnifierImgArr.showImg[imgIndex].src"
             alt="">
        <div class="transparent"
             @mousemove="magnifiermove($event)"
             @mouseleave="elemenImgShow = false"></div>

      </div>
      <!-- 缩略图 -->
      <div class="img-thumbnail flexbox between">
        <div class="icon"
             @click="nextPic"
             v-if="iconShow">&lt;</div>
        <div class="rel-box"
             rel="ulOuterBox">
          <ul class=""
              ref="ulList">
            <li v-for="(item, index) in thumbnailForArr"
                :key="index"
                @mouseenter="thumbnailEnter(index)"
                :class="{'active':imgIndex===index}">
              <img :src="item.src"
                   alt=""
                   srcset="">
            </li>
          </ul>
        </div>
        <div class="icon"
             v-if="iconShow"
             @click="previous">&gt;</div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: ['magnifierImgArr', 'goodsBoxWidth', 'reload'],
  /*
    magnifierImgArr
    传入的图片对象集合 数据格式 :
      magnifierImgArr: {
          //缩略图图片数组
         thumbnail: [{src: ''}],
          //要放大的图片数组
         showImg: [{src:''}]
          //放大的图片数组
         magnifier: [{src:''}]
       },
      // 其中缩略图图片数组可以不传 如果不传将自动使用 要放大的图片数组 的数据
      // goodsBoxWidth
      // 设置放大的图片盒子的宽度 尽量沾满商品图片的右边部分
       // reload 如果传入的数据发生变化自动将图片索引下标变为0
  */
  watch: {
    reload () {
      if (this.reload === 1) {
        this.imgIndex = 0
      }
    }
  },
  data () {
    return {
      // magnifierImgArr: {
      //   // 缩略图图片数组
      //   thumbnail: [
      //     { src: require('../assets/img/letter-img/22628333-1_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-2_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-3_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-4_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-5_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-1_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-2_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-3_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-4_x_2.jpg') },
      //     { src: require('../assets/img/letter-img/22628333-5_x_2.jpg') }
      //   ],
      //   // 要放大的图片数组
      //   showImg: [{ src: require('../assets/img/center/22628333-1_w_2.jpg') },
      //     { src: require('../assets/img/center/22628333-2_w_2.jpg') },
      //     { src: require('../assets/img/center/22628333-3_w_2.jpg') },
      //     { src: require('../assets/img/center/22628333-4_w_2.jpg') },
      //     { src: require('../assets/img/center/22628333-5_w_2.jpg') }],
      //   // 放大的图片数组
      //   magnifier: [
      //     { src: require('../assets/img/big/22628333-1_u_2.jpg') },
      //     { src: require('../assets/img/big/22628333-2_u_2.jpg') },
      //     { src: require('../assets/img/big/22628333-3_u_2.jpg') },
      //     { src: require('../assets/img/big/22628333-4_u_2.jpg') },
      //     { src: require('../assets/img/big/22628333-5_u_2.jpg') }
      //   ]
      // },
      imgIndex: 0,
      elemenImgShow: false,
      leftData: 0
    }
  },
  mounted () {

  },
  computed: {
    thumbnailForArr () {
      if (this.magnifierImgArr.thumbnail && this.magnifierImgArr.thumbnail.length) {
        return this.magnifierImgArr.thumbnail
      } else {
        return this.magnifierImgArr.showImg
      }
    },
    // 是否显示左右操作按钮
    iconShow () {
      if (this.thumbnailForArr.length > 5) {
        return true
      } else {
        return false
      }
    }
  },
  methods: {
    // 缩略图鼠标移入事件
    thumbnailEnter (i) {
      this.imgIndex = i
    },
    initData () {
      // 将放大盒子向右移动
      let outerBoxWidth = parseInt(this.$refs.imgShowBox.offsetWidth)
      // 放大镜盒子
      let thumbnailBox = this.$refs.thumbnailBox
      thumbnailBox.style.left = (outerBoxWidth + 15) + 'px'
      // 设置放大盒子的宽度
      if (this.goodsBoxWidth && this.goodsBoxWidth > 500) {
        thumbnailBox.style.width = this.goodsBoxWidth - outerBoxWidth - 75 + 'px'
      } else {
        thumbnailBox.width = 500 + 'px'
      }
    },
    magnifiermove (e) {
      // 有鼠标事件才加载大图片的位移 以解决在数据未加载之前就赋值了错误的位移
      this.initData()
      // 鼠标移入展示图片
      this.elemenImgShow = true
      // 鼠标下移动的半透明方块
      let magnifierEle = this.$refs.magnifierImg
      // 待放大的图片盒子宽高
      let outerBoxHeight = parseInt(this.$refs.imgShowBox.offsetHeight)
      let outerBoxWidth = parseInt(this.$refs.imgShowBox.offsetWidth)
      //  半透明方块的宽高
      let magnifierHeight = parseInt(magnifierEle.offsetHeight)
      let magnifierWidth = parseInt(magnifierEle.offsetWidth)
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      // 刚开始赋值left的初始值时就要减去放大镜图片宽度的一般 保证移动距离大于宽度一半之后才开始移动图片 top同理
      let left = e.offsetX - (magnifierWidth / 2)
      let top = e.offsetY - (magnifierHeight / 2)
      // 左边临界点
      left = left < 0 ? 0 : left
      //  右边临界点 透明方块可以移动的距离只有盛放商品展示图片盒子的宽度减去半透明盒子的宽度 如果移动的距离大于这个宽度 就将可移动距离设置为最大宽度 否则为鼠标移动的距离 top值同理
      left = left > outerBoxWidth - magnifierWidth ? outerBoxWidth - magnifierWidth : left
      magnifierEle.style.left = left + 'px'
      // 上面临界点
      top = top < 0 ? 0 : top
      // 下面临界点
      top = top > outerBoxHeight - magnifierHeight ? outerBoxHeight - magnifierHeight : top
      magnifierEle.style.top = top + 'px'
      // 放大的图片位移通过使用margin改变图片的位置 使用比例换算出移动的距离
      // 获取到需要放大的图片元素
      let thumbnailImgEle = this.$refs.thumbnailImg
      let maginiferInnerboxEle = this.$refs.maginiferInnerbox
      // 用大图片的宽高减去盛放大图片盒子的宽高就是可以移动的最大距离
      let maxLeft = thumbnailImgEle.offsetWidth - maginiferInnerboxEle.offsetWidth
      let maxTop = thumbnailImgEle.offsetHeight - maginiferInnerboxEle.offsetHeight
      // 换算出小图片对大图片的比例
      // 使用大图片可移动的距离除以放大镜可以移动的距离 即小图对大图的移动比例
      // 放大镜可移动的距离就是小图片盒子宽高减去放大镜盒子的宽高
      let leftScale = parseFloat((maxLeft / (outerBoxWidth - magnifierWidth)).toFixed(2))
      let topScale = parseFloat((maxTop / (outerBoxHeight - magnifierHeight)).toFixed(2))
      // 通过动态改变大图片的margin值实现图片移动
      thumbnailImgEle.style.marginLeft = '-' + (leftScale * left) + 'px'
      thumbnailImgEle.style.marginTop = '-' + (topScale * top) + 'px'
    },
    // 缩略图列表箭头的点击事件
    // 下一个
    nextPic () {
      this.positionDistance('next')
    },
    // 上一个
    previous () {
      this.positionDistance('prv')
    },
    positionDistance (nextOrPre) {
      let ulListEle = this.$refs.ulList
      let ulListEleWidth = ulListEle.offsetWidth
      // 设置单次移动的距离
      let num = ulListEleWidth / 4
      let distance = 0
      if (nextOrPre === 'next') {
        // 设置可以移动的最大距离
        let maxData = (this.thumbnailForArr.length * num) - ulListEleWidth
        distance = this.leftData >= maxData ? maxData : this.leftData + num
      } else {
        distance = this.leftData > num ? this.leftData - num : 0
      }
      ulListEle.style.left = '-' + distance + 'px'
      this.leftData = distance
    }
  }
}
</script>
<style lang="less" scoped>
.magnifier {
  min-width: 270px;
  padding: 10px;
  box-sizing: border-box;
  .outer-box {
    position: relative;
    width: 100%;
    height: 100%;
    .img-show {
      border: 1px solid #ccc;
      width: 500px;
      height: calc(100% - 80px);
      position: relative;
      .magnifier-img {
        width: 200px;
        height: 200px;
        background: rgba(160, 203, 236, 0.5);
        position: absolute;
        top: 0;
        left: 0;
        z-index: 10;
      }
      .transparent {
        cursor: move;
        width: 100%;
        height: 100%;
        background: none;
        position: absolute;
        top: 0;
        left: 0;
        z-index: 20;
      }
      img {
        max-height: 100%;
        max-width: 100%;
      }
    }
    .img-thumbnail {
      width: 100%;
      margin-top: 10px;
      height: 54px;
      .icon {
        width: 35px;
        height: 35px;
        line-height: 25px;
        font-size: 14px;
        text-align: center;
        cursor: pointer;
        padding: 5px;
        box-sizing: border-box;
        background-color: #f5f5f5;
        // 禁止浏览器选中文字
        moz-user-select: -moz-none;
        -moz-user-select: none;
        -o-user-select: none;
        -khtml-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
        user-select: none;
        &:hover {
          background-color: #007acc;
          color: #fff;
        }
      }
      .rel-box {
        width: calc(100% - 60px);
        height: 100%;
        position: relative;
        overflow: auto;
        // 隐藏滚动条
        &::-webkit-scrollbar {
          display: none;
        }
        ul {
          position: absolute;
          left: 0;
          z-index: 100;
          top: 0;
          width: 100%;
          white-space: nowrap;
          li {
            width: calc(100% / 4);
            display: inline-block;
            height: 50px;
            border: 2px solid transparent;
            vertical-align: middle;
            box-sizing: border-box;
            &.active {
              border-color: #f00;
            }
            text-align: center;
            img {
              width: calc(100%);
              height: calc(100%);
            }
          }
        }
      }
    }
    .magnifier-box {
      position: absolute;
      left: 50px;
      top: -5px;
      width: 500px;
      height: 330px;
      padding: 20px;
      z-index: 10;
      background-color: #fff;
      .inner-box {
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-color: #fff;
      }
    }
  }
}
</style>



// 未优化代码
<template>
  <div class="magnifier">
    <div class="outer-box">
      <!-- 放大图片 -->
      <div class="magnifier-box"
           v-show="elemenImgShow"
           ref="thumbnailBox">
        <div class="inner-box">
          <img ref="thumbnailImg"
               :src="magnifierImgArr.magnifier[imgIndex].src"
               alt="">
        </div>
      </div>
      <!-- 展示的图片 -->
      <div class="img-show"
           ref="imgShowBox">
        <div class="magnifier-img"
             v-show="elemenImgShow"
             ref="magnifierImg"></div>
        <img :src="magnifierImgArr.showImg[imgIndex].src"
             alt="">
        <div class="transparent"
             @mousemove="magnifiermove($event)"
             @mouseleave="elemenImgShow = false"></div>
      </div>
      <!-- 缩略图 -->
      <div class="img-thumbnail flexbox j-start">
        <li v-for="(item, index) in thumbnailForArr"
            :key="index"
            @mouseenter="thumbnailEnter(index)"
            :class="{'active':imgIndex===index}">
          <img :src="item.src"
               alt=""
               srcset="">
        </li>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: ['magnifierImgArr', 'goodsBoxWidth'],
  /*
    magnifierImgArr
    传入的图片对象集合 数据格式 :
      magnifierImgArr: {
          //缩略图图片数组
         thumbnail: [{src: ''}],
          //要放大的图片数组
         showImg: [{src:''}]
          //放大的图片数组
         magnifier: [{src:''}]
       },
      // 其中缩略图图片数组可以不传 如果不传将自动使用 要放大的图片数组 的数据
      // goodsBoxWidth
      // 设置放大的图片盒子的宽度 尽量沾满商品图片的右边部分
  */
  data () {
    return {
      // magnifierImgArr: {
      //   // 缩略图图片数组
      //   thumbnail: [{src: require('../assets/img/letter-img/22628333-1_x_2.jpg')},
      //     {src: require('../assets/img/letter-img/22628333-2_x_2.jpg')},
      //     {src: require('../assets/img/letter-img/22628333-3_x_2.jpg')},
      //     {src: require('../assets/img/letter-img/22628333-4_x_2.jpg')},
      //     {src: require('../assets/img/letter-img/22628333-5_x_2.jpg')}],
      //   // 要放大的图片数组
      //   showImg: [{src: require('../assets/img/center/22628333-1_w_2.jpg')},
      //     {src: require('../assets/img/center/22628333-2_w_2.jpg')},
      //     {src: require('../assets/img/center/22628333-3_w_2.jpg')},
      //     {src: require('../assets/img/center/22628333-4_w_2.jpg')},
      //     {src: require('../assets/img/center/22628333-5_w_2.jpg')}],
      //   // 放大的图片数组
      //   magnifier: [{src: require('../assets/img/big/22628333-1_u_2.jpg')},
      //     {src: require('../assets/img/big/22628333-2_u_2.jpg')},
      //     {src: require('../assets/img/big/22628333-3_u_2.jpg')},
      //     {src: require('../assets/img/big/22628333-4_u_2.jpg')},
      //     {src: require('../assets/img/big/22628333-5_u_2.jpg')} ]
      // },
      imgIndex: 0,
      elemenImgShow: false
    }
  },
  mounted () {

  },
  computed: {
    thumbnailForArr () {
      if (this.magnifierImgArr.thumbnail && this.magnifierImgArr.thumbnail.length) {
        return this.magnifierImgArr.thumbnail
      } else {
        return this.magnifierImgArr.showImg
      }
    }
  },
  methods: {
    // 缩略图鼠标移入事件
    thumbnailEnter (i) {
      this.imgIndex = i
    },
    initData () {
      // 将放大盒子向右移动
      let outerBoxWidth = parseInt(this.$refs.imgShowBox.offsetWidth)
      // 放大镜盒子
      let thumbnailBox = this.$refs.thumbnailBox
      thumbnailBox.style.left = (outerBoxWidth + 15) + 'px'
      // 设置放大盒子的宽度
      if (this.goodsBoxWidth && this.goodsBoxWidth > 500) {
        thumbnailBox.style.width = this.goodsBoxWidth - outerBoxWidth - 75 + 'px'
      } else {
        thumbnailBox.width = 500
      }
    },
    magnifiermove (e) {
      // 有鼠标事件才加载大图片的位移 以解决在数据未加载之前就赋值了错误的位移
      this.initData()
      // 鼠标移入展示图片
      this.elemenImgShow = true
      // 鼠标下移动的半透明方块
      let magnifierEle = this.$refs.magnifierImg
      // 待放大的图片盒子宽高
      let outerBoxHeight = parseInt(this.$refs.imgShowBox.offsetHeight)
      let outerBoxWidth = parseInt(this.$refs.imgShowBox.offsetWidth)
      //  半透明方块的宽高
      let magnifierHeight = parseInt(magnifierEle.offsetHeight)
      let magnifierWidth = parseInt(magnifierEle.offsetWidth)
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      // 刚开始赋值left的初始值时就要减去放大镜图片宽度的一般 保证移动距离大于宽度一半之后才开始移动图片 top同理
      let left = e.offsetX - (magnifierWidth / 2)
      let top = e.offsetY - (magnifierHeight / 2)
      // 左边临界点
      left = left < 0 ? 0 : left
      //  右边临界点 透明方块可以移动的距离只有盛放商品展示图片盒子的宽度减去半透明盒子的宽度 如果移动的距离大于这个宽度 就将可移动距离设置为最大宽度 否则为鼠标移动的距离 top值同理
      left = left > outerBoxWidth - magnifierWidth ? outerBoxWidth - magnifierWidth : left
      magnifierEle.style.left = left + 'px'
      // 上面临界点
      top = top < 0 ? 0 : top
      // 下面临界点
      top = top > outerBoxHeight - magnifierHeight ? outerBoxHeight - magnifierHeight : top
      magnifierEle.style.top = top + 'px'
      // 放大的图片位移通过使用margin改变图片的位置 使用比例换算出移动的距离
      // 获取到需要放大的图片元素
      let thumbnailImgEle = this.$refs.thumbnailImg
      // 换算出小图片对大图片的比例
      let leftScale = parseFloat((thumbnailImgEle.offsetWidth / (magnifierWidth / 2.5)).toFixed(2))
      let topScale = parseFloat((thumbnailImgEle.offsetHeight / ((magnifierHeight / 2.5))).toFixed(2))
      thumbnailImgEle.style.marginLeft = '-' + (leftScale * left) + 'px'
      thumbnailImgEle.style.marginTop = '-' + (topScale * top) + 'px'
    }
  }
}
</script>
<style lang="less" scoped>
.magnifier {
  width: 100%;
  height: 100%;
  padding: 10px;
  box-sizing: border-box;
  .outer-box {
    position: relative;
    width: 100%;
    height: 100%;
    .img-show {
      border: 1px solid #ccc;
      width: 100%;
      height: calc(100% - 80px);
      position: relative;
      .magnifier-img {
        width: 200px;
        height: 200px;
        background: rgba(160, 203, 236, 0.5);
        position: absolute;
        top: 0;
        left: 0;
        z-index: 10;
      }
      .transparent {
        cursor: move;
        width: 100%;
        height: 100%;
        background: none;
        position: absolute;
        top: 0;
        left: 0;
        z-index: 20;
      }
      img {
        max-height: 100%;
        max-width: 100%;
      }
    }
    .img-thumbnail {
      margin-top: 10px;
      height: 54px;
      overflow: auto;
      li {
        width: 50px;
        height: 50px;
        border: 2px solid transparent;
        vertical-align: middle;
        &.active {
          border-color: #f00;
        }
        text-align: center;
        img {
          max-height: 100%;
          max-width: 100%;
        }
      }
    }
    .magnifier-box {
      position: absolute;
      left: 50px;
      top: -5px;
      width: 500px;
      height: 330px;
      padding: 20px;
      z-index: 10;
      background-color: #fff;
      .inner-box {
        overflow: hidden;
        width: 100%;
        height: 100%;
        background-color: #fff;
      }
    }
  }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值