VueUse+一个常见的图片列表渲染放大镜Demo
前言
当我们浏览商品时,常常想要仔细观察产品的细节,这时候放大镜功能就显得非常有用,当用户在小图上悬停时,大图会随之改变,并在大图上出现放大镜效果。
效果:
1. 引入VueUse库和定义变量:
VueUse官网地址:VueUse引入及useMouseInElement的使用
npm i @vueuse/core
<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. 列表图片渲染
<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,当我们在大图中移动时可以通过来useMouseInElement
的elementX
、elementY
和isOutside
参数设置小滑块的top
和left
。
<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
函数来监听鼠标在元素内的位置。通过监听 elementX
、elementY
和 isOutside
这几个响应式变量的变化,可以获取到鼠标相对于元素的水平和垂直位置,并判断鼠标是否在元素外部。然后根据鼠标位置计算滑块的位置,并处理边界情况。最后,根据滑块的位置计算大图的显示位置。
<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>
-
首先定义了四个响应性变量:
left
,top
,positionX
,positionY
,分别用于控制滑块的水平和垂直位置,以及放大图的水平和垂直显示位置。 -
使用
watch
监听elementX
,elementY
,isOutside
三个变量的变化。当其中任何一个变化时,会执行回调函数。 -
回调函数中,首先通过判断
isOutside
的值,如果鼠标没有移入到盒子内,则直接返回,不执行后续逻辑。 -
接着根据
elementX
和elementY
的值来控制滑块的位置。如果鼠标在水平方向上的位置在100到300之间,会设置left
的值为elementX.value - 100
;如果在垂直方向上的位置在100到300之间,会设置top
的值为elementY.value - 100
。 -
然后处理边界情况,如果鼠标在水平方向上超过300或小于100,会将
left
的值分别设置为200或0;如果在垂直方向上超过300或小于100,会将top
的值分别设置为200或0。 -
最后根据
left
和top
的值,计算出放大图的positionX
和positionY
,用于控制大图的显示位置。positionX
的值为-left.value * 2
,positionY
的值为-top.value * 2
,实现了根据滑块位置控制大图的移动并放大俩倍的效果。
注: 之所以处理边界情况是为了美观,当小滑块在大图中的100-300距离内,滑块会随着鼠标而移动,即left
的值为elementX.value - 100
,例如,当鼠标位于中间(200,200)时,小滑块的左右角坐标(100,100),如果鼠标在水平方向上超过300或小于100,会将left
的值分别设置为200或0,因为在这个范围内小滑块已经到达边界,鼠标在这个范围内移动,对应的坐标则不会再改变,如下:
通过以上操作,就可以实现了一个简单的放大镜效果。用户在小图上悬停时,大图会随之改变,并在大图上出现放大镜效果,方便用户观看商品的细节。通过监听鼠标位置并计算滑块和大图的位置,实现了放大镜的跟随效果。
源码:
<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>
转载自:https://juejin.cn/post/7344567650456371215