VueUse+一个常见的图片列表渲染放大镜Demo

前言

当我们浏览商品时,常常想要仔细观察产品的细节,这时候放大镜功能就显得非常有用,当用户在小图上悬停时,大图会随之改变,并在大图上出现放大镜效果。

效果:

1. 引入VueUse库和定义变量:

VueUse官网地址:VueUse引入及useMouseInElement的使用

css 复制代码
npm i @vueuse/core
js 复制代码
<script setup>
import { ref } from 'vue'
import { useMouseInElement } from '@vueuse/core'

const target = ref(null)

// 获取鼠标在容器内的相对位置
const { elementX, elementY, isOutside } = useMouseInElement(target)
</script>

<template>
  <div ref="target">
    <h1>Hello world</h1>
  </div>
</template>

2. 列表图片渲染

js 复制代码
<template>
    <img :src="imageList[activeIndex]" alt="" />

    <div class="small">
      <!-- 可以使用 @mouseenter 事件监听器来监听鼠标移入(hover)元素的事件。 -->
      <div v-for="(img, i) in imageList" :key="i" @mouseenter="enterhandler(i)" :class="{active: activeIndex === i}">
        <!-- 小图展示 -->
        <img :src="img" alt="" />
      </div>
    </div>
</template>

<script>
const imageList = [
  "https://yanxuan-item.nosdn.127.net/cac68a7880bec1c72dcfce112d10e955.png",
  "https://yanxuan-item.nosdn.127.net/06a158d2888b20383a466227e39bbbc7.jpg",
  "https://yanxuan.nosdn.127.net/8f8092d5bf6a133a8cb59ab7b9f790e9.png",
  "https://yanxuan-item.nosdn.127.net/eac6c40fdb0f977fdf80048d7b181ffa.png",
  "https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg"
]

// 1.小图切换大图显示
const activeIndex = ref(0)

const enterhandler = (i) => {
  activeIndex.value = i
}
</script>

<style scoped lang="scss">
  .small {
    width: 100px;
    height: 400px;
    display: flex;
    flex-direction: column; /* 设置主轴为垂直方向 */
    justify-content: space-between; /* 在主轴上均匀分布 */
    align-items: center; /* 在交叉轴上居中 */
    margin-left: 12px;
    div {
      width: 68px;
      height: 68px;
      cursor: pointer; // 鼠标悬停时显示为手型指示符的样式
      &:hover,
      &.active {
        border: 2px solid greenyellow;
      }
      // 列表小图
      img{
        width: 68px;
        height: 68px;
      }
    }
  }
</style>

用响应式变量activeIndex来得到鼠标移入对应的小图标列表时的下标i,再用类名绑定:class="{active: activeIndex === i}来显示鼠标移入对应的小图标时所响应的样式。最后用响应式变量activeIndex得到的下标放入大图中并绑定:src="imageList[activeIndex]",这样就可以初步显示一个点击切换大图的效果。

3. 小滑块显示

由于大图片的宽高为400px,所以我们可以给大图内的小滑块的宽高设置200px,当我们在大图中移动时可以通过来useMouseInElementelementXelementYisOutside参数设置小滑块的topleft

js 复制代码
<div class="middle" ref="target">
  <!-- 大图展示 -->
  <img :src="imageList[activeIndex]" alt="" />
  <!-- 大图中的蒙层小滑块 -->
  <div class="layer" v-show="!isOutside" :style="{ left: `${left}px`, top: `${top}px` }"></div>
</div>

<script>
const target = ref(null)
const { elementX, elementY, isOutside } = useMouseInElement(target)
</script>

<style scoped lang="scss">
.middle {
    width: 400px;
    height: 400px;
    img{
      width: 400px;
      height: 400px;
}
</style>

4. 计算坐标

监听鼠标位置和切换大图,使用 useMouseInElement 函数来监听鼠标在元素内的位置。通过监听 elementXelementYisOutside 这几个响应式变量的变化,可以获取到鼠标相对于元素的水平和垂直位置,并判断鼠标是否在元素外部。然后根据鼠标位置计算滑块的位置,并处理边界情况。最后,根据滑块的位置计算大图的显示位置。

js 复制代码
<template>
<!-- 放大镜大图 -->
<div class="large" :style="[
    {
      backgroundImage: `url(${imageList[activeIndex]})`,
      backgroundPositionX: `${positionX}px`,
      backgroundPositionY: `${positionY}px`,
    },
  ]" v-show="!isOutside">
</div>
</template>

<script>
// 控制滑块跟随鼠标移动(监听elementX/Y变化,一旦变化 重新设置left/top)
// 滑块俩坐标
const left = ref(0)
const top = ref(0)

// 大图俩坐标
const positionX = ref(0)
const positionY = ref(0)

// 回调函数会一直执行,可以优化不执行
watch([elementX, elementY, isOutside], () => {
  // 如果鼠标没有移入到盒子里面 直接不执行后面的逻辑
  if (isOutside.value) return
  // 有效范围内控制滑块距离
  // 横向
  if (elementX.value > 100 && elementX.value < 300) {
    left.value = elementX.value - 100
  }
  // 纵向
  if (elementY.value > 100 && elementY.value < 300) {
    top.value = elementY.value - 100
  }
  // 处理边界
  if (elementX.value > 300) { left.value = 200 }
  if (elementX.value < 100) { left.value = 0 }

  if (elementY.value > 300) { top.value = 200 }
  if (elementY.value < 100) { top.value = 0 }

  // 控制大图的显示
  positionX.value = -left.value * 2
  positionY.value = -top.value * 2
})
</script>

<style scoped lang="scss">
// 放大镜大图
.large {
    position: absolute;
    top: 0;
    left: -410px;
    width: 400px;
    height: 400px;
    z-index: 999;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
</style>
  1. 首先定义了四个响应性变量:left, top, positionX, positionY,分别用于控制滑块的水平和垂直位置,以及放大图的水平和垂直显示位置。

  2. 使用watch监听elementX, elementY, isOutside三个变量的变化。当其中任何一个变化时,会执行回调函数。

  3. 回调函数中,首先通过判断isOutside的值,如果鼠标没有移入到盒子内,则直接返回,不执行后续逻辑。

  4. 接着根据elementXelementY的值来控制滑块的位置。如果鼠标在水平方向上的位置在100到300之间,会设置left的值为elementX.value - 100;如果在垂直方向上的位置在100到300之间,会设置top的值为elementY.value - 100

  5. 然后处理边界情况,如果鼠标在水平方向上超过300或小于100,会将left的值分别设置为200或0;如果在垂直方向上超过300或小于100,会将top的值分别设置为200或0。

  6. 最后根据lefttop的值,计算出放大图的positionXpositionY,用于控制大图的显示位置。positionX的值为-left.value * 2positionY的值为-top.value * 2,实现了根据滑块位置控制大图的移动并放大俩倍的效果。

注: 之所以处理边界情况是为了美观,当小滑块在大图中的100-300距离内,滑块会随着鼠标而移动,即left的值为elementX.value - 100,例如,当鼠标位于中间(200,200)时,小滑块的左右角坐标(100,100),如果鼠标在水平方向上超过300或小于100,会将left的值分别设置为200或0,因为在这个范围内小滑块已经到达边界,鼠标在这个范围内移动,对应的坐标则不会再改变,如下:

通过以上操作,就可以实现了一个简单的放大镜效果。用户在小图上悬停时,大图会随之改变,并在大图上出现放大镜效果,方便用户观看商品的细节。通过监听鼠标位置并计算滑块和大图的位置,实现了放大镜的跟随效果。

源码:

js 复制代码
<script setup>
import { ref, watch } from 'vue'
import { useMouseInElement } from '@vueuse/core'

// 图片列表
const imageList = [
  "https://yanxuan-item.nosdn.127.net/cac68a7880bec1c72dcfce112d10e955.png",
  "https://yanxuan-item.nosdn.127.net/06a158d2888b20383a466227e39bbbc7.jpg",
  "https://yanxuan.nosdn.127.net/8f8092d5bf6a133a8cb59ab7b9f790e9.png",
  "https://yanxuan-item.nosdn.127.net/eac6c40fdb0f977fdf80048d7b181ffa.png",
  "https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg"
]

// 1.小图切换大图显示
const activeIndex = ref(0)

const enterhandler = (i) => {
  activeIndex.value = i
}

// 2. 获取鼠标相对位置
const target = ref(null)
const { elementX, elementY, isOutside } = useMouseInElement(target)

// 3. 控制滑块跟随鼠标移动(监听elementX/Y变化,一旦变化 重新设置left/top)
// 滑块俩坐标
const left = ref(0)
const top = ref(0)

// 大图俩坐标
const positionX = ref(0)
const positionY = ref(0)

// 回调函数会一直执行,可以优化不执行
watch([elementX, elementY, isOutside], () => {
  // 如果鼠标没有移入到盒子里面 直接不执行后面的逻辑
  if (isOutside.value) return
  // 有效范围内控制滑块距离
  // 横向
  if (elementX.value > 100 && elementX.value < 300) {
    left.value = elementX.value - 100
  }
  // 纵向
  if (elementY.value > 100 && elementY.value < 300) {
    top.value = elementY.value - 100
  }
  // 处理边界
  if (elementX.value > 300) { left.value = 200 }
  if (elementX.value < 100) { left.value = 0 }

  if (elementY.value > 300) { top.value = 200 }
  if (elementY.value < 100) { top.value = 0 }

  // 控制大图的显示
  positionX.value = -left.value * 2
  positionY.value = -top.value * 2
})

</script>


<template>
  <div class="goods-image">
    <!-- 左侧大图-->
    <div class="middle" ref="target">
      <!-- 大图展示 -->
      <img :src="imageList[activeIndex]" alt="" />
      <!-- 大图中的蒙层小滑块 -->
      <div class="layer" v-show="!isOutside" :style="{ left: `${left}px`, top: `${top}px` }"></div>
    </div>
    <!-- 小图列表 -->
    <div class="small">
      <!-- 可以使用 @mouseenter 事件监听器来监听鼠标移入(hover)元素的事件。 -->
      <div v-for="(img, i) in imageList" :key="i" @mouseenter="enterhandler(i)" :class="{active: activeIndex === i}">
        <!-- 小图展示 -->
        <img :src="img" alt="" />
      </div>
    </div>
    <!-- 放大镜大图 -->
    <div class="large" :style="[
        {
          backgroundImage: `url(${imageList[activeIndex]})`,
          backgroundPositionX: `${positionX}px`,
          backgroundPositionY: `${positionY}px`,
        },
      ]" v-show="!isOutside">
    </div>
  </div>
</template>

<style scoped lang="scss">
.goods-image {
  width: 600px;
  height: 400px;
  position: relative;
  display: flex;
  margin: 200px 600px;
  // 左侧大图
  .middle {
    width: 400px;
    height: 400px;
    img{
      width: 400px;
      height: 400px;
    }
    // 大图中的蒙层小滑块
    .layer {
      width: 200px;
      height: 200px;
      background: rgba(0, 0, 0, 0.2);
      // 绝对定位 然后跟随鼠标控制left和top属性就可以让滑块移动起来
      position: absolute;
      left: 0;
      top: 0;
   }
  }
  // 列表小图夫容器
  .small {
    width: 100px;
    height: 400px;
    display: flex;
    flex-direction: column; /* 设置主轴为垂直方向 */
    justify-content: space-between; /* 在主轴上均匀分布 */
    align-items: center; /* 在交叉轴上居中 */
    margin-left: 12px;
    div {
      width: 68px;
      height: 68px;
      cursor: pointer; // 鼠标悬停时显示为手型指示符的样式
      &:hover,
      &.active {
        border: 2px solid greenyellow;
      }
      // 列表小图
      img{
        width: 68px;
        height: 68px;
      }
    }
  }
  // 放大镜大图
  .large {
    position: absolute;
    top: 0;
    left: -410px;
    width: 400px;
    height: 400px;
    z-index: 999;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  }
}
</style>
相关推荐
工业互联网专业5 分钟前
毕业设计选题:基于springboot+vue+uniapp的驾校报名小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计
J不A秃V头A34 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂1 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹2 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄4 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d