手动封装一个图片放大镜组件 ( Vue 3 )

790 阅读1分钟

效果

image.png

核心解决问题

1.如何实现左侧遮罩的鼠标跟随效果
2.如何展示放大效果

基础结构

<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>

image.png

实现左侧遮罩的鼠标跟随效果

跟随思路

准备待移动的遮罩容器,通过绝对定位修改它的top,left来移动
实时监听鼠标位置,让这招容器跟着鼠标走

遮罩层

<div class="middle">
    <img src="https://yanxuanitem.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg" 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>

鼠标跟随

在@vueuse中,有一个工具方法:useMouseInElement vueuse.org/core/usemou…

import { useMouseInElement } from '@vueuse/core'

setup () {
  // 以哪个元素为基准,计算鼠标的位置
  const target = ref(null)
  const { elementX, elementY, isOutside } = useMouseInElement(target)
  return { currIndex, target, elementX, elementY, isOutside }
}
+ <div class="middle" ref="target"> // 加上target

image.png

sertup () {
  ...
  const layerStyle = reactive({ top: '0px', left: '0px' })
  watch([elementX,elementY,isOutside], () => {
  // top, left 用来确定绝对定位下 遮罩元素 的位置
  const top = elementY.value - 100
  const left = elementX.value - 100

  // 给遮罩元素赋值位置
  layerStyle.top = top + 'px'
  layerStyle.left = left + 'px'
  })
  return {...省略,layerStyle}
}

在模板上

+ <div class="layer" :style="layerStyle"></div>

image.png
遮罩层可以跟随移动了

位置修正

遮罩层不能出图片区域

// 遮罩元素的位置不能移出 中图 所在范围
if (top > 200) top = 200
if (top < 0) top = 0
if (left > 200) left = 200
if (left < 0) left = 0

image.png

效果实现

image.png

展示放大效果

放大思路

修改background-position-x,background-position-y
添加响应式数据largeStyle
根据当前左侧遮罩的位置来计算background-position的值

放大效果层

<div class="middle">
+   <div class="large" :style="{backgroundImage: `url(https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg)`}"></div>
    <img src="https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg" alt="">
    <div class="layer" :style="layerStyle"></div>
</div>

<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;
+  }
</style>

放大实现

setup () {
  ...
+ const largeStyle = reactive({
+   'background-position-x': '0px',
+   'background-position-y': '0px'
+ })
  watch([elementX,elementY,isOutside], () => {
    // top, left 用来确定绝对定位下 遮罩元素 的位置
    let top = elementY.value - 100
    let left = elementX.value - 100
    if (top > 200) top = 200
    if (top < 0) top = 0
    if (left > 200) left = 200
    if (left < 0) left = 0
    // 给遮罩元素赋值位置
    layerStyle.top = top + 'px'
    layerStyle.left = left + 'px'
+   largeStyle['background-position-x'] = -2 * left + 'px'
+   largeStyle['background-position-y'] = -2 * top + 'px'
  })
  return {+largeStyle}
}

在模板中

+ <div class="large" :style="{backgroundImage: `url(...)`,...largeStyle}"></div>

效果完成

image.png

鼠标移入显示移除隐藏

直接用它

image.png

<div
+ v-show="!isOutside"
  class="large"
  :style="{backgroundImage: `url(...)`,...largeStyle}">
</div>
<img src="https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg" alt="">
<div 
+ v-show="!isOutside" 
  class="layer" 
  :style="layerStyle">
</div>

最终效果

image.png

image.png