前端自定义封装图片预览组件(支持多张图片预览 缩放):

news2024/11/25 14:27:21

封装图片预览组件:

<template>
    <div ref="previewWrapper" class="image-preview">
      <div class="overlay" v-if="showOverlay" @click="closePreview"></div>
      <div class="preview-container" v-wheelScale>
        <img :src="currentImageUrl" alt="Preview Image" @load="imageLoaded" ref="previewImage">
      </div>
      <div class="arrow arrow-left" @click="prevImage" :disabled="currentIndex === 0">&lt;</div>
      <div class="arrow arrow-right" @click="nextImage" :disabled="currentIndex === images.length - 1">&gt;</div>
    </div>
  </template>
  
  <script>
  export default {
    props: {
      images: {
        type: Array,
        required: true,
      },
    },
    data() {
      return {
        showOverlay: false,
        currentIndex: 0,
        currentImageUrl: '',
        scale: 1,
        initialMouseX: 0,
        initialScale: 1,
        isDragging: false,
      };
    },
    methods: {
      openPreview() {
        this.showOverlay = true;
        this.currentImageUrl = this.images[this.currentIndex];
        this.$refs.previewWrapper.style.display = 'flex';
        setTimeout(() => {
          this.$refs.previewWrapper.style.opacity = 1;
        }, 10);
      },
      closePreview() {
        this.showOverlay = false;
        setTimeout(() => {
          this.$refs.previewWrapper.style.opacity = 0;
          setTimeout(() => {
            this.$refs.previewWrapper.style.display = 'none';
          }, 0);
        }, 0);
      },
      nextImage() {
        this.currentIndex = (this.currentIndex + 1) % this.images.length;
        this.currentImageUrl = this.images[this.currentIndex];
      },
      prevImage() {
        this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
        this.currentImageUrl = this.images[this.currentIndex];
      },
      imageLoaded() {
        // 可以在此处调整图片的居中或其它布局逻辑
      },
    },
    mounted() {
      // 初始化时隐藏预览层
      this.$refs.previewWrapper.style.display = 'none';
    },
  };
  </script>
  
  <style scoped>
  .image-preview {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 999;
    display: none;
    justify-content: center;
    align-items: center;
    opacity: 0;
    transition: opacity 0.7s ease-in-out;
  }
  
  .overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.8);
    cursor: pointer;
  }
  
  .preview-container {
    position: relative;
    text-align: center;
    overflow: hidden;
    max-width: 90%;
    max-height: 90vh;
  }
  
  .arrow {
    width: 50px;
    height: 50px;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255, 255, 255, 0.8);
    padding: 10px;
    border-radius: 50%;
    cursor: pointer;
    z-index: 1;
    opacity: 0.5;
    transition: opacity 0.3s;
    border: none;
    font-size: 20px;
    line-height: 50px;
  }
  
  .arrow:hover {
    opacity: 1;
  }
  
  .arrow-left {
    left: 10px;
  }
  
  .arrow-right {
    right: 10px;
  }
  </style>

图片放大缩小依靠自定义指令实现:

自定义指定代码:src/utils/scale.js

export const initVWheelScale = (Vue) => {
    Vue.directive("wheelScale", (el, binding) => {
      const {
        maxScale = 5,
        minScale = 0.5,
        initScale = 1,
        cssVarName = "--scale",
      } = binding.arg || {}
      let currentScale = initScale || el.style.getPropertyValue(cssVarName) || 1
      setWheelScale(binding, {
        el,
        cssVarName,
        currentScale,
        minScale,
        maxScale,
      })
      if (el) {
        el.onwheel = (e) => {
          currentScale = el.style.getPropertyValue(cssVarName) || 1
   
          if (e.wheelDelta > 0) {
            currentScale = currentScale * 1 + 0.1
          } else {
            currentScale = currentScale * 1 - 0.1
          }
          setWheelScale(binding, {
            el,
            cssVarName,
            currentScale,
            minScale,
            maxScale,
          })
        }
      }
    })
  }
  // 设置 --scale 变量 缩放比例
  const setVarScale = (el, cssVarName, currentScale, minScale, maxScale) => {
    // 现在缩放范围
    if (currentScale > maxScale) {
      currentScale = maxScale
    } else if (currentScale < minScale) {
      currentScale = minScale
    }
    let cssText = el.style.cssText
    let cssTextList = cssText.split(";")
    let isExist = false
    let isExistIndex = -1
    for (let index = 0; index < cssTextList.length; index++) {
      const element = cssTextList[index]
      if (element.includes(cssVarName + ":")) {
        isExist = true
        isExistIndex = index
        break
      }
    }
    if (isExist) {
      cssTextList[isExistIndex] = `--scale: ${currentScale}`
    } else {
      cssTextList.push(`--scale: ${currentScale}`)
      //   el.setAttribute("style", `--scale: ${currentScale}`)
    }
    cssText = cssTextList.join(";")
    el.style.cssText = cssText
    return currentScale
  }
  // 设置 style.transform
  const setTransformCss = (el, cssVarName) => {
    let transformCssString = el.style.transform
    let regScaleGlobal = /scale\(.*?[ )]*[)]+[ ]*/g //匹配 Scale属性 全局
    if (regScaleGlobal.test(transformCssString)) {
      transformCssString = transformCssString.replace(
        regScaleGlobal,
        ` scale(var(${cssVarName})) `
      )
    } else {
      transformCssString += " " + `scale(var(${cssVarName}))`
    }
    el.style.transform = transformCssString
  }
  export const setWheelScale = (binding = {}, options) => {
    const { el, cssVarName, currentScale, minScale, maxScale } = options
    const nowScale = setVarScale(el, cssVarName, currentScale, minScale, maxScale)
    setTransformCss(el, cssVarName)
    // 缩放改变回调函数
    const wheelScaleHandle = binding.value || null
    if (wheelScaleHandle instanceof Function) {
      wheelScaleHandle({
        el,
        cssVarName,
        maxScale,
        minScale,
        currentScale: nowScale,
        setScale: (_scale) => {
          setWheelScale(binding, { ...options, currentScale: _scale })
        },
        binding,
      })
    }
  }
   

main.js中全局注册自定义指令:

import { initVWheelScale} from "@/utils/scale.js"
initVWheelScale(Vue)

在图片预览组件中使用:

组件的使用:

<template>
  <div>
    <!-- ...其他内容 -->
    <button @click="openPreview">预览图片</button>
    <image-preview :images="imageList" ref="imagePreview"></image-preview>
  </div>
</template>

<script>
import ImagePreview from '@/components/ImagePreview.vue'; // 引入你的图片预览组件

export default {
  components: {
    ImagePreview,
  },
  data() {
    return {
      imageList: [
        'https://img1.baidu.com/it/u=582697934,2565184993&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=539',
        'https://img2.baidu.com/it/u=3519181745,2349627299&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
        // 更多图片路径
      ],
    };
  },
  methods: {
    openPreview() {
      this.$refs.imagePreview.openPreview();
    },
  },
};
</script>

效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1632523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 | Leetcode C语言题解之第55题跳跃游戏

题目&#xff1a; 题解&#xff1a; #define max(a, b) (((a) > (b)) ? (a) : (b))bool canJump(int* nums, int numsSize){int cover 0;int i;// 只可能获取cover范围中的步数&#xff0c;所以i<coverfor(i 0; i < cover; i) {// 更新cover为从i出发能到达的最大…

OSPF大作业

一&#xff0c;拓扑 二&#xff0c;要求 1&#xff0c;r4为ISP上只配ip&#xff0c;r3与其他设备之间只使用公有IP 2&#xff0c;r3-r5/6/7为MGRE环境&#xff0c;r3为中心 3&#xff0c;整个OSPF的ip地址基于172.16.0.0/16划分 所以设备都可以访问r4的环回 4减少LSA的数量加快…

Docker本地部署overleaf后,挖掘用户加密逻辑

overleaf的用户信息&#xff0c;保存在mongo数据库的users集合中。 用户密码则存在hashedPassword字段中 从开源的代码services\web\app\src\Features\Authentication\AuthenticationManager.js第303行可以找到密码加密逻辑。 本地可以通过下面的代码生成overleaf用户密码信息…

IGM焊接机器人RTE 495伺服电机维修详情一览

在当今科技迅速发展的时代&#xff0c;机器人已成为各行各业不可或缺的重要工具。IGM机器人便是其中之一&#xff0c;其工业机械手伺服马达作为机器人的关键部件&#xff0c;确保机器人能够高效、稳定地运行。当出现IGM焊接机器人RTE 495伺服电机故障问题时&#xff0c;及时进行…

水库大坝安全白蚁监测系统解决方案

一、系统背景 白蚁作为河岸生态系统中的重要病害&#xff0c;不仅会导致水库大坝外部环境发生改变&#xff0c;甚至会引发水库大坝破坏&#xff0c;进而导致自身结构失去稳定性&#xff0c;严重影响水库大坝的正常运行。因此&#xff0c;治理水库大坝白蚁是确保水库大坝工程顺利…

Kubeedge:Metamanager源码速读(不定期更新)

Kubeedge源码版本&#xff1a;v1.15.1 在看Metamanager之前&#xff0c;先看一下Metamanager源码的目录结构&#xff08;位于edge/pkg下&#xff09;和官方文档&#xff1a; 目录结构如下面的两张图所示。请忽略绿色的文件高亮&#xff0c;这是Jetbrains goland对未提交修改的…

使用Pandas从Excel文件中提取满足条件的数据并生成新的文件

目录 一、引言 二、环境准备 三、读取Excel文件 四、数据筛选 五、保存为新的Excel文件 六、案例与代码总结 七、进阶用法与注意事项 八、结语 在数据处理的日常工作中&#xff0c;我们经常需要从大量数据中筛选出满足特定条件的数据集。Pandas是一个强大的Python数据分…

【产品经理】如果人人都是产品经理,那么如何提升自己的不可替代性?

任何职业都需要有危机感&#xff0c;不只是产品经理。 人有生老病死&#xff0c;相应的职场上也有升降变离。当乔布斯站在宇宙之巅望着芸芸众生说“活着就是为了改变世界”的时候&#xff0c;这话着实燃烧了我们一把。随之&#xff0c;马化腾、周鸿祎、张小龙、王小川等汹涌而入…

瓦片编辑器成功移植到小熊猫C++ 2.25.1版本,解决_findnext移植问题

移植之后出现绿色屏幕闪退 查了版本回滚直到不闪退&#xff0c;发现是在读取自定义文件上出问题 然后在找读取自定义文件函数&#xff0c;发现是读取图片部分出问题 然后就卡住了 调试半天&#xff0c;不是数据溢出&#xff0c;于是就看 函数_findnext,网上搜 ———_findn…

WebGL/Cesium 大空间相机抖动 RTE(Relative to Eye)实现原理简析

在浏览器中渲染大尺寸 3D 模型&#xff1a;Speckle 处理空间抖动的方法 WebGL/Cesium 大空间相机抖动 RTE(Relative to Eye)实现原理简析 注: 相机空间和视图空间 概念等效混用 1、实现的关键代码 const material new THREE.RawShaderMaterial({uniforms: {cameraPostion: {…

花生壳域名收费?那就用免费的dnsexit动态域名解析保姆级图文教程,效果杠杠的

免费dnsexit动态域名解析教程 在互联网上有很多不同的域名解析服务&#xff0c;其中dnsexit是一个流行的免费动态域名解析服务&#xff0c;它允许用户动态更新其IP地址&#xff0c;确保域名始终指向正确的服务器。以下是一个dnsexit动态域名解析的图文教程&#xff0c;帮助你了…

OpenCV 实现重新映射

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV 实现霍夫圆变换 下一篇 :OpenCV实现仿射变换 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 一个。使用 OpenCV 函数 cv&#xff1a;&#xff1a;remap 实现简单的重新…

AIGC遇到制造业

AIGC (AlGeneratedContet&#xff0c;即由AI生成的内容) &#xff0c;是指通过AI技术生成的各种文本、图片、音频和视频等形式的内容。AI技术的发展使得AI生成的内容在质量和多样性方面取得了巨大的进步。通过AI生成的内容&#xff0c;可以快速、批量地满足人们对于个性化、多样…

持续记录|UNIAPP适配APP遇到的问题以及解决方案

在使用UNIAPP开发APP的时候遇到的一些奇奇怪怪问题记录 组件样式丢失 问题&#xff1a;组件引入界面中&#xff0c;在小程序和H5环境下样式正常&#xff0c;而在APP中却出现高度异常问题 解决&#xff1a;增加view标签将组件包裹起来即可正常显示 解决前&#xff1a; 解决后…

考试实况:云计算HCIE考试中我是这样做的

大家好&#xff0c;我是誉天云计算HCIE学员黄同学&#xff0c;4月11日已成功获取到hcie证书。 考取云计算IE大概花费了4个半月时间&#xff08;3个月学习1个半月备考&#xff09;&#xff0c;由于我是脱产考试的&#xff0c;所以备考时间给自己定了1-1个半月时间考下来。整个课…

在config.json文件中配置出来new mars3d.graphic.PolylineCombine({大量线合并渲染类型的geojson图层

在config.json文件中配置出来new mars3d.graphic.PolylineCombine({大量线合并渲染类型的geojson图层 问题场景&#xff1a; 1.浏览官网示例的时候图层看到大量线数据合并渲染的示例 2.矢量数据较大量级的时候&#xff0c;这种时候怎么在config.json文件中尝试配置呢&#x…

【Java并发知识总结 | 第九篇】ThreadLocal总结

文章目录 9.ThreadLocal总结9.1ThreadLocal是什么&#xff1f;9.2ThreadLocal的作用&#xff1f;9.3使用ThreadLocal9.4ThreadLocal原理9.5ThreadLocal问题&#xff1a;内存泄漏/溢出9.6为什么key要设计成弱引用&#xff1f;9.7ThreadLocal中的强弱引用关系9.8ThreadLocalMap怎…

web安全---CSRF漏洞/OWASP-CSRFTester的使用

what 跨站请求伪造 Cross Site Request Forgery how 攻击者诱骗点击恶意网页&#xff0c;盗用&#xff08;伪造&#xff09;受害者的身份&#xff0c;以受害者的名义向服务器发送恶意请求,而这种恶意请求在服务端看起来是正常请求 CSRF&&XSS区别 他们最本质区别就…

利用RunnerGo数据大屏强化测试管理与决策

测试平台中的数据大屏在提供实时监控、统计分析、效率提升、制定策略和促进沟通等方面具有重要的意义。它为测试团队提供更全面、更直观的数据支持&#xff0c;有助于提高测试质量和效率&#xff0c;减少风险&#xff0c;并加强团队协作和沟通。 数据大屏也是RunnerGo的核心特…

21 Debian如何配置Apache2(1)配置文件摊开看

作者&#xff1a;网络傅老师 特别提示&#xff1a;未经作者允许&#xff0c;不得转载任何内容。违者必究&#xff01; Debian如何配置DNS服务&#xff08;2&#xff09;主从服务器 《傅老师Debian小知识库系列之20》——原创 前言 傅老师Debian小知识库特点&#xff1a; 1、…