vue3.0中手写商品放大镜效果

Posted 奥特曼 

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3.0中手写商品放大镜效果相关的知识,希望对你有一定的参考价值。

实现效果

实现分析 

(1) 基础组件布局

 goods-info.vue 父组件

<template>
<div class="goods-info">
  <!--左边-->
  <div class="media">
      <GoodsImage />
    <!-- 商品图片组件 -->
  </div>
    </div>
</template>

<script>
import GoodsImage from './components/goods-image.vue'
export default {
  components: {
    GoodsImage
  }
}
</script>

<style lang="less" scoped>
.goods-info {
  min-height: 600px;
  background: #fff;
  display: flex;
  .media {
    width: 580px;
    height: 600px;
    padding: 30px 50px;
  }
  .spec {
    flex: 1;
    padding: 30px 30px 30px 0;
  }
}
</style>

goods-image图片子组件

<template>
  <div class="goods-image">
    <div class="middle">
      <img src="https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg" alt="">
    </div>
    <ul class="small">
      <li v-for="i in 4" :key="i">
        <img src="https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg" alt="">
      </li>
    </ul>
  </div>
</template>

<style scoped lang="less">
.goods-image {
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;
  .middle {
    width: 400px;
    height: 400px;
    background: #f5f5f5;
  }
  .small {
    width: 80px;
    li {
      width: 68px;
      height: 68px;
      margin-left: 12px;
      margin-bottom: 15px;
      cursor: pointer;
      &:hover,&.active {
        border: 2px solid @xtxColor;
      }
    }
  }
}
</style>

目前效果

 (2)父传子 传图片数据

goods-info.vue

<template>
<GoodsImage :picture="list.mainPictures" />  
</template>

<script>
import GoodsImage from './components/goods-image.vue'
import { findGoods } from '@/api/goods' // 引入api
import { ref } from 'vue'
export default {
  components: {
    GoodsImage
  },
  setup () {
    const list = ref([]) // 用来接收数据
    findGoods(4005091).then((data) => { // 实际上是路由 route.params.id  模拟采用指定id
      list.value = data.result
    })
  }
}
</script>

goods-image子组件

<template>
  <div class="goods-image">
    <div class="middle">
      <img :src="pictures[currIdx]" alt="">
    </div>
    <ul class="small">
      <li v-for="(img,idx) in pictures" @mouseenter="currIdx=idx" :key="img">
        <img :src="img" alt="">
      </li>
    </ul>
  </div>
</template>
<script>
import { ref } from 'vue'
export default {
  name: 'goods-image',
  props: {
    pictures: {
      type: Array, //接收父组件传过来的数据
      default: () => [] 
    }
  },
  setup () {
    const currIdx = ref(0)  //定义图片的索引 
    return { currIdx }
  }
}
</script>

@mouseenter="currIdx=idx"  当鼠标进入小图片时  拿到当前的索引 给左图片进行改变 pictures[currIdx]

  (3)遮罩效果

 goods-image子组件 添加html结构和样式

    <div class="middle" ref="target">
      <img :src="images[currIndex]" alt="">
+      <div class="layer"></div>
    </div>

<style scoped lang="less">
 .middle {
    width: 400px;
    height: 400px;
+    position: relative;
+    cursor: move;
+    .layer {
+      width: 200px;
+      height: 200px;
+      background: rgba(0,0,0,.2);
+      left: 0;
+      top: 0;
+      position: absolute;
+    }
  }
</style>

 (4)监听要放大元素的鼠标位置

使用vueuse 工具库中的 useMouseInElement 方法  它可以检测到鼠标距离左上角的距离和当前鼠标是否在元素内

 

1. 使用useMouseInElement 方法   使用vueuse库中都要先安装

npm i @vueuse/core

2. 引入并使用

测试:{{elementX}}, {{elementY}}, {{isOutside}}  //最后不需要删掉 即可

<div class="middle" ref="target">

// 导入 useMouseInElement
import { useMouseInElement } from '@vueuse/core'

setup(){
  // 以哪个元素为基准,计算鼠标的位置
  const target = ref(null)
	const { elementX, elementY, isOutside } = useMouseInElement(target)

 	return { currIndex, target, elementX, elementY, isOutside }
}

目前效果

 (5)使遮罩层跟随鼠标的移动

       <div class="layer" :style="shadeMove"></div>

 setup () {
    // 当前图片的索引
    const currIdx = ref(0)
    // 获取鼠标在哪个当前元素内
    const target = ref(null)
    const { elementX, elementY, isOutside } = useMouseInElement(target)
    // 准备要给遮罩绑定的 left top 值
    const shadeMove = {
      left: '0px',
      top: ' 0px'
    }
    // 监听鼠标移动的变化
    watch([elementX, elementY, isOutside], () => {
    // elementX 要加.value  不加不生效 小坑!
      const left = elementX.value - 100
      const top = elementY.value - 100
      shadeMove.left = left + 'px'
      shadeMove.top = top + 'px'
    })

    return { currIdx, target, elementX, elementY, isOutside, shadeMove }
  }

 注意为什么要 位置-100呢 首先知道 遮罩的大小是200px  如果不-100  遮罩会在鼠标的左上角显示

图解1

 图解2

 ​​​​

 现在的遮罩是 absolute 绝对定位 也就是可以在页面中 自由飞翔~~~  所以要对遮罩进行控制  只能在当前div块下自由飞翔~~

 加一下判断 

    watch([elementX, elementY, isOutside], () => {
      let left = elementX.value
      let top = elementY.value
      if (left < 0) left = 0
      if (left > 200) left = 200
      if (top < 0) top = 0
      if (top > 200) top = 200
      shadeMove.left = left + 'px'
      shadeMove.top = top + 'px'
    })

  (6)准备大图容器 用来放大显示

 <div class='goods-image'>
+  <div class="large" :style="{backgroundImage: `url(${pictures[currIdx]})`}"></div>
   <!-- 中图 -->
   <div class="middle" ref="target"> 省略其他...</div>
   <!-- 小图 -->
   <ul class="small">省略其他...</ul>
</div>

.goods-image {
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;
+  z-index: 500;
+  .large {
+    position: absolute;
+    top: 0;
+    left: 412px;
+    width: 400px;
+    height: 400px;
+    box-shadow: 0 0 10px rgba(0,0,0,0.1);
+    background-repeat: no-repeat;
+    background-size: 800px 800px;
+    background-color: #f8f8f8;
+  }

 通过background-size进行放大一倍  目前效果图  使用绝对定位 移动到最右边

 (7)实现遮罩移动大图跟随移动

<div class="large" :style="{backgroundImage: `url(${pictures[currIdx]})`,...bigImg}"></div>   
 // 定义用来控制大图的显示位置    
    const bigImg = {
      'background-position-X': '0px',
      'background-position-Y': '0px'
    }
    watch([elementX, elementY, isOutside], () => {
      let left = elementX.value - 100
      let top = elementY.value - 100
      if (left < 0) left = 0
      if (left > 200) left = 200
      if (top < 0) top = 0
      if (top > 200) top = 200
      shadeMove.left = left + 'px'
      shadeMove.top = top + 'px'
+   // 因为是小图的二倍 所以 * 2  
+      bigImg['background-position-X'] = -left * 2 + 'px'
+      bigImg['background-position-Y'] = -top * 2 + 'px'
    })

(8)鼠标移出隐藏遮罩和大图

加上两个v-show isOutside代表在盒子外就为true 所以做一个取反

     <!-- 大图 -->
      <div class="large" v-show="!isOutside" :style="{backgroundImage: `url(${pictures[currIdx]})`,...bigImg}"></div>
      <!-- 遮罩 -->
       <div class="layer" v-show="!isOutside"  :style="shadeMove"></div>

 完整代码

 goods-info.vue 父组件

<template>
X:{{elementX}}Y:{{elementY}} 是否在元素外 {{isOutside}}
  <div class="goods-image">
      <!-- 大图 -->
      <div class="large" :style="{backgroundImage: `url(${pictures[currIdx]})`,...bigImg}"></div>
      <!-- 中图 -->
    <div class="middle">
      <!-- 左侧图片 -->
      <img :src="pictures[currIdx]" alt="" ref="target" >
      <!-- 遮罩 -->
       <div class="layer" :style="shadeMove"></div>
    </div>
    <ul class="small">
      <!-- 右侧小图片 -->
      <li v-for="(img,idx) in pictures" @mouseenter="currIdx=idx" :key="img">
        <img :src="img" alt="">
      </li>
    </ul>
  </div>
</template>
<script>

import { ref, watch } from 'vue'
import { useMouseInElement } from '@vueuse/core'
export default {
  name: 'goods-image',
  props: {
    pictures: {
      type: Array,
      default: () => []
    }
  },
  setup () {
    // 当前图片的索引
    const currIdx = ref(0)
    // 获取鼠标在当前元素内的距离
    const target = ref(null)
    const { elementX, elementY, isOutside } = useMouseInElement(target)
    const shadeMove = {
      left: '0px',
      top: '0px'
    }
    const bigImg = {
      'background-position-X': '0px',
      'background-position-Y': '0px'
    }
    watch([elementX, elementY, isOutside], () => {
      let left = elementX.value - 100
      let top = elementY.value - 100
      if (left < 0) left = 0
      if (left > 200) left = 200
      if (top < 0) top = 0
      if (top > 200) top = 200
      shadeMove.left = left + 'px'
      shadeMove.top = top + 'px'
      bigImg['background-position-X'] = -left * 2 + 'px'
      bigImg['background-position-Y'] = -top * 2 + 'px'
    })

    return { currIdx, target, elementX, elementY, isOutside, shadeMove, bigImg }
  }
}
</script>
<style scoped lang="less">
.goods-image {
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;
   z-index: 500;
 .large {
   position: absolute;
   top: 0;
   left: 412px;
   width: 400px;
   height: 400px;
   box-shadow: 0 0 10px rgba(0,0,0,0.1);
   background-repeat: no-repeat;
   background-size: 800px 800px;
   background-color: #f8f8f8;
   }
  .middle {
    width: 400px;
    height: 400px;
    background: #f5f5f5;
       position: relative;
   cursor: move;
   .layer {
     width: 200px;
     height: 200px;
     background: rgba(0,0,0,.2);
     left: 0;
     top: 0;
     position: absolute;
   }
  }
  .small {
    width: 80px;
    li {
      width: 68px;
      height: 68px;
      margin-left: 12px;
      margin-bottom: 15px;
      cursor: pointer;
      &:hover,&.active {
        border: 2px solid @xtxColor;
      }
    }
  }
}
</style>

 goods-image图片子组件

<template>
X:{{elementX}}Y:{{elementY}} 是否在元素外 {{isOutside}}
  <div class="goods-image">
      <!-- 大图 -->
      <div class="large" v-show="!isOutside" :style="{backgroundImage: `url(${pictures[currIdx]})`,...bigImg}"></div>
      <!-- 中图 -->
    <div class="middle">
      <!-- 左侧图片 -->
      <img :src="pictures[currIdx]" alt="" ref="target" >
      <!-- 遮罩 -->
       <div class="layer" v-show="!isOutside"  :style="shadeMove"></div>
    </div>
    <ul class="small">
      <!-- 右侧小图片 -->
      <li v-for="(img,idx) in pictures" @mouseenter="currIdx=idx" :key="img">
        <img :src="img" alt="">
      </li>
    </ul>
  </div>
</template>
<script>

import { ref, watch } from 'vue'
import { useMouseInElement } from '@vueuse/core'
export default {
  name: 'goods-image',
  props: {
    pictures: {
      type: Array,
      default: () => []
    }
  },
  setup () {
    // 当前图片的索引
    const currIdx = ref(0)
    // 获取鼠标在当前元素内的距离
    const target = ref(null)
    const { elementX, elementY, isOutside } = useMouseInElement(target)
    const shadeMove = {
      left: '0px',
      top: '0px'
    }
    const bigImg = {
      'background-position-X': '0px',
      'background-position-Y': '0px'
    }
    watch([elementX, elementY, isOutside], () => {
      let left = elementX.value - 100
      let top = elementY.value - 100
      if (left < 0) left = 0
      if (left > 200) left = 200
      if (top < 0) top = 0
      if (top > 200) top = 200
      shadeMove.left = left + 'px'
      shadeMove.top = top + 'px'
      bigImg['background-position-X'] = -left * 2 + 'px'
      bigImg['background-position-Y'] = -top * 2 + 'px'
    })

    return { currIdx, target, elementX, elementY, isOutside, shadeMove, bigImg }
  }
}
</script>
<style scoped lang="less">
.goods-image {
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;
   z-index: 500;
 .large {
   position: absolute;
   top: 0;
   left: 412px;
   width: 400px;
   height: 400px;
   box-shadow: 0 0 10px rgba(0,0,0,0.1);
   background-repeat: no-repeat;
   background-size: 800px 800px;
   background-color: #f8f8f8;
   }
  .middle {
    width: 400px;
    height: 400px;
    background: #f5f5f5;
       position: relative;
   cursor: move;
   .layer {
     width: 200px;
     height: 200px;
     background: rgba(0,0,0,.2);
     left: 0;
     top: 0;
     position: absolute;
   }
  }
  .small {
    width: 80px;
    li {
      width: 68px;
      height: 68px;
      margin-left: 12px;
      margin-bottom: 15px;
      cursor: pointer;
      &:hover,&.active {
        border: 2px solid @xtxColor;
      }
    }
  }
}
</style>

   

以上是关于vue3.0中手写商品放大镜效果的主要内容,如果未能解决你的问题,请参考以下文章

vue3.0手写省市区地区联动 详细

vue3.0手写dialog提示框

商品图片局部放大效果

JS实现淘宝商品图片放大效果(放大镜)

JS实现淘宝商品图片放大效果(放大镜)

JS实现淘宝商品图片放大效果(放大镜)