两站图片滑动对比效果实现(VUE3)

在这里插入图片描述
像这种图片滑动对比的效果,网上还不少见吧,但是网上却不好找到完整现成的实现代码,我找到几个地方有类似的代码,但是都不好直接移植到代码里,因为很多都是使用原生html+css+js实现,太复杂了。反而不好应用到vue3中。
于是我借着他们的思路,自己实现了个。

前置条件

  1. 限制两张图片
  2. 图片大小必须一致,不一致会导致上层图片显示不全
  3. 底层图片必须存在,因为窗口的大小由他的大小决定

实现思路

  1. 将两张图片都看做是背景图,他们属于不同的层次
  2. 底层图片用相对定位,也就可以和其他元素一同正常展示,之后的所有元素都处于其包裹范围内,并且根据图片的大小确定整个样式的大小
  3. 上层图片用绝对定位,因为处于底层图片的包裹中,且大小是一样的,所以他们是完全重叠的,它的高度是和底层图片一致,但是宽度是可变的
  4. 利用input 的滑块模式 <input type=“range” v-model="width" />来改变上层图片的宽度,这里也就是利用了vue3的响应式。这是比原生方式实现的简便之处
  5. 由于input的原生样式无法改变,所以得额外做个滑块来实现自定义样式,然后其位置也受width的控制,实际上并不是点击该滑块来滑动的
  6. input是出于最上层的,但是将其隐藏了,所以点击的时候看起来像是点击那个滑块,实际上点击的是input实现的滑动。
  7. 然后再修饰下其他细节,即可

接下来逐步分解出实现的效果,这样就更好理解文字的意思了

第一步,将两张图片分别呈现

在这里插入图片描述

<template><div id="bottomImg" class="bottomImg" :style="{ height: imgHeigth, width: imgWidth, backgroundImage: 'url(' + bottomImg + ')' }"><div class="upperImg" :style="{ backgroundImage: 'url(' + upperImg + ')', width: 100 - upperImgWidth + '%' }"></div></div>
</template>
<script lang="ts" setup>import { ref, onMounted } from "vue";const imgHeigth = ref("0px"); // 图片高度const imgWidth = ref("0px"); // 图片宽度const bottomImg = ref(new URL("@/images/bottomImg.jpg", import.meta.url).href); // 底图const upperImg = ref(new URL("@/images/upperImg.jpg", import.meta.url).href); // 上层图const upperImgWidth = ref(50); // 上层图宽度// 首次加载时初始化onMounted(() => {getImgSize();});// 获取图片尺寸function getImgSize() {//加载图片获取图片真实宽度和高度var image = new Image();image.onload = function () {imgHeigth.value = image.height + "px";imgWidth.value = image.width + "px";};image.src = bottomImg.value; //img.src 应该是放在 onload 方法后边的, 因为当image的src发生改变,浏览器就会跑去加载这个src里的资源,先告诉浏览器图片加载完要怎么处理,再让它去加载图片}
</script>
<style>.bottomImg {position: relative; /*  相对定位 */overflow: hidden; /*  隐藏超出部分 */}.upperImg {position: absolute; /*  绝对定位 */top: 0;right: 0; /* 从右边开始铺开图片 */height: 100%;z-index: 1;background-position: right top; /* 改变定位方式,默认是左上角,要改为右上角,这样才能实现底图在左边,上层图在右边的效果*/border-left: 2px solid rgb(255, 255, 255, 0.5); /* 显示左边框 */background-repeat: no-repeat; /*  不重复 */}
</style>

代码有详细注释,现在两张图片分别展示的效果有了,但是还没法滑动。

第二步,添加滑块,图片动起来

在这里插入图片描述
就是加了个滑块,双向绑定宽度:

        <!--滑块控制上层图片的宽度 --><input class="inputRange" type="range" v-model="upperImgWidth" min="0" max="100" />

样式

    .inputRange {position: absolute;height: 100%; /* 这样就能点击任何位置都能实现移动滑块,而不仅仅是滑块所在的位置才有效*/z-index: 3; /* 处于最高层次 */left: -4px; /*因为原始样式有边界,为了效果更好而调整的,这些都是实测效果,不重要*/touch-action: auto;width: calc(100% + 4px); /*因为原始样式有边界,为了效果更好而调整的,这些都是实测效果,不重要*//* opacity: 0; */ /*隐藏滑块,这里为了演示,就不隐藏先*/}

到这一步,效果已经有了,接下来就是样式的优化。

第三步,增加自定义的滑块

在这里插入图片描述
增加一个这样的滑块样式,其实他仅仅是个样式,它并不具备点击滑动的能力,他的位置是靠响应width的值来改变的

        <!-- 这是对外展示的滑块样式,仅仅是展示样式的,不然原生的样式不好看 --><span class="spanHandle" :style="{ left: 'calc(' + upperImgWidth + '% - 24px)' }"></span>

下面的样式不是重点,自己调整都是可以的,只需要注意使用绝对定位,z-index小于input即可。

  .spanHandle {position: absolute; /*绝对定位还是一样的*/z-index: 2; /* 样式很多,都不是关键的,只有这里,需要注意层次要低于inputRange*/height: 48px;width: 48px;position: center;font-size: 24px;border: 1px;border-radius: 50%;top: calc(90% - 24px);background-color: rgb(255, 255, 255, 0.5);}.spanHandle:before {left: 5px;transform: rotate(-45deg);}.spanHandle:after {right: -5px;transform: rotate(135deg);}.spanHandle:after,.spanHandle:before {border-left: 2px solid;border-top: 2px solid;content: "";height: 10px;position: absolute;top: 50%;transform-origin: 0 0;width: 10px;}

然后把上一步的opacity: 0;启用即可隐藏原始的input滑块

第四步,添加label和上层图片为空时的效果,这一步是可选的

效果就不贴了,就跟开头时差不多

完整代码

<template><div id="bottomImg" class="bottomImg" :style="{ height: imgHeigth, width: imgWidth, backgroundImage: 'url(' + bottomImg + ')' }"><span class="imgLabel">{{ bottomLabel }}</span><div v-if="upperImg" class="upperImg" :style="{ backgroundImage: 'url(' + upperImg + ')', width: 100 - upperImgWidth + '%' }"><span class="imgLabel">{{ upperLabel }}</span></div><div v-else class="upperUndefined" :style="{ width: 100 - upperImgWidth + '%' }"><span class="undefinedSpan">暂无结果</span></div><!-- 这是对外展示的滑块样式,仅仅是展示样式的,不然原生的样式不好看 --><span class="spanHandle" :style="{ left: 'calc(' + upperImgWidth + '% - 24px)' }"></span><!--滑块控制上层图片的宽度 --><input class="inputRange" type="range" v-model="upperImgWidth" min="0" max="100" /></div>
</template>
<script lang="ts" setup>import { ref, onMounted } from "vue";const imgHeigth = ref("0px"); // 图片高度const imgWidth = ref("0px"); // 图片宽度const bottomImg = ref(new URL("@/images/bottomImg.jpg", import.meta.url).href); // 底图const upperImg = ref(new URL("@/images/upperImg.jpg", import.meta.url).href); // 上层图const upperImgWidth = ref(50); // 上层图宽度const bottomLabel = ref("底图"); // 底图标签const upperLabel = ref("上层图"); // 上层图标签// 首次加载时初始化onMounted(() => {getImgSize();});// 获取图片尺寸function getImgSize() {//加载图片获取图片真实宽度和高度var image = new Image();image.onload = function () {imgHeigth.value = image.height + "px";imgWidth.value = image.width + "px";};image.src = bottomImg.value; //img.src 应该是放在 onload 方法后边的, 因为当image的src发生改变,浏览器就会跑去加载这个src里的资源,先告诉浏览器图片加载完要怎么处理,再让它去加载图片}
</script>
<style>.bottomImg {position: relative; /*  相对定位 */overflow: hidden; /*  隐藏超出部分 */}.upperImg {position: absolute; /*  绝对定位 */top: 0;right: 0; /* 从右边开始铺开图片 */height: 100%;z-index: 1;background-position: right top; /* 改变定位方式,默认是左上角,要改为右上角,这样才能实现底图在左边,上层图在右边的效果*/border-left: 2px solid rgb(255, 255, 255, 0.5); /* 显示左边框 */background-repeat: no-repeat; /*  不重复 */}.inputRange {position: absolute;height: 100%; /* 这样就能点击任何位置都能实现移动滑块,而不仅仅是滑块所在的位置才有效*/z-index: 3; /* 处于最高层次 */left: -4px; /*因为原始样式有边界,为了效果更好而调整的,这些都是实测效果,不重要*/touch-action: auto;width: calc(100% + 4px); /*因为原始样式有边界,为了效果更好而调整的,这些都是实测效果,不重要*/opacity: 0; /*隐藏滑块,这里为了演示,就不隐藏先*/}.spanHandle {position: absolute; /*决定定位还是一样的*/z-index: 2; /* 样式很多,都不是关键的,只有这里,需要注意层次要低于inputRange*/height: 48px;width: 48px;position: center;font-size: 24px;border: 1px;border-radius: 50%;top: calc(90% - 24px);background-color: rgb(255, 255, 255, 0.5);}.spanHandle:before {left: 5px;transform: rotate(-45deg);}.spanHandle:after {right: -5px;transform: rotate(135deg);}.spanHandle:after,.spanHandle:before {border-left: 2px solid;border-top: 2px solid;content: "";height: 10px;position: absolute;top: 50%;transform-origin: 0 0;width: 10px;}.imgLabel {font-size: 20px;color: aliceblue;text-shadow: 1px 1px #533d4a, 2px 2px #533d4a;}.upperUndefined {position: absolute;top: 0;right: 0;height: 100%;z-index: 1;font-size: 60px;background-color: rgb(255, 255, 255, 0.8);background-position: right top;border-left: 2px solid rgb(255, 255, 255, 0.5);}
</style>

封装成组件

上面的示例都是死的,封装成组件,就可以切换图片展示:
在这里插入图片描述

组件代码

<template><div id="bottomImg" class="bottomImg" :style="{ height: imgHeigth, width: imgWidth, backgroundImage: 'url(' + props.bottomImg + ')' }"><span class="imgLabel">{{ props.bottomLabel }}</span><div v-if="props.upperImg" class="upperImg" :style="{ backgroundImage: 'url(' + props.upperImg + ')', width: 100 - upperImgWidth + '%' }"><span class="imgLabel">{{ props.upperLabel }}</span></div><div v-else class="upperUndefined" :style="{ width: 100 - upperImgWidth + '%' }"><span class="undefinedSpan">暂无结果</span></div><span class="spanHandle" :style="{ left: 'calc(' + upperImgWidth + '% - 24px)' }"></span><input class="inputRange" type="range" v-model="upperImgWidth" min="0" max="100" /></div>
</template>
<script lang="ts" setup>import { ref, watch, onMounted } from "vue";const imgHeigth = ref("0px");const imgWidth = ref("0px");const upperImgWidth = ref(50);const props = defineProps({bottomImg: {type: String,default: "",},upperImg: {type: String,default: "",},bottomLabel: {type: String,default: "原图",},upperLabel: {type: String,default: "效果图",},});// 跟踪底层图片的变化,因为底层图片是基础watch(() => props.bottomImg,() => {getImgSize();upperImgWidth.value = 50;});// 首次加载时初始化onMounted(() => {getImgSize();});function getImgSize() {//加载图片获取图片真实宽度和高度var image = new Image();image.onload = function () {imgHeigth.value = image.height + "px";imgWidth.value = image.width + "px";};image.src = props.bottomImg; //img.src 应该是放在 onload 方法后边的, 因为当image的src发生改变,浏览器就会跑去加载这个src里的资源,先告诉浏览器图片加载完要怎么处理,再让它去加载图片}
</script>
<style>.bottomImg {position: relative;overflow: hidden;}.upperImg {position: absolute;top: 0;right: 0;height: 100%;z-index: 1;background-position: right top;border-left: 2px solid rgb(255, 255, 255, 0.5);}.imgLabel {font-size: 20px;color: aliceblue;text-shadow: 1px 1px #533d4a, 2px 2px #533d4a;}.upperUndefined {position: absolute;top: 0;right: 0;height: 100%;z-index: 1;font-size: 60px;background-color: rgb(255, 255, 255, 0.8);background-position: right top;border-left: 2px solid rgb(255, 255, 255, 0.5);}.undefinedSpan {display: flex;width: 100%;height: 100%;align-items: center;justify-content: center;color: #999;overflow: hidden;}.inputRange {position: absolute;height: 100%;z-index: 3;left: -4px;touch-action: auto;width: calc(100% + 4px);opacity: 0;}.spanHandle {position: absolute;z-index: 2;height: 48px;width: 48px;position: center;font-size: 24px;border: 1px;border-radius: 50%;top: calc(90% - 24px);background-color: rgb(255, 255, 255, 0.5);}.spanHandle:before {left: 5px;transform: rotate(-45deg);}.spanHandle:after {right: -5px;transform: rotate(135deg);}.spanHandle:after,.spanHandle:before {border-left: 2px solid;border-top: 2px solid;content: "";height: 10px;position: absolute;top: 50%;transform-origin: 0 0;width: 10px;}
</style>

调用示例代码

<template><div><div><button @click="changeBottomImg">切换底图</button><button @click="removeUpperImg">去除上层图</button><button @click="changeUpperImg">切换上层图</button></div><TwoImgCompare :bottom-img="bottomImg" bottom-label="原图" :upper-img="upperImg" upper-label="结果图"></TwoImgCompare></div>
</template>
<script lang="ts" setup>import TwoImgCompare from "@/components/twoImgCompare.vue";import { ref } from "vue";const bottomImg = ref(new URL("@/images/bottomImg.jpg", import.meta.url).href); // 底图const upperImg = ref(new URL("@/images/upperImg.jpg", import.meta.url).href); // 上层图// 切换底图const changeBottomImg = () => {bottomImg.value = new URL("@/images/bottomImg2.jpg", import.meta.url).href;};// 去除上层图const removeUpperImg = () => {upperImg.value = "";};// 切换上层图const changeUpperImg = () => {upperImg.value = new URL("@/images/upperImg2.jpg", import.meta.url).href;};
</script>

PS:开发该组件是用于自己开发的网站:极简AI工具箱,欢迎光临!

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

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

相关文章

Qt for Android 之 OpenCV编译(Windows下编译)

简介 前两天刚好更新了4.10, 这里以4.10作为示例进行编译&#xff0c; Qt版本是Qt6.6.2。 准备OpenCV的Android库 一. 使用官方编译好的库 1. 下载OpenCV android SDK opencv-4.10.0-android-sdk.zip 2. 解压缩 官方提供的包含了多个架构的opencv android库 二. 自行编译…

十三、【源码】ResultMap解析

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/13-resultMap ResultMap解析 分为两部分&#xff1a;解析和使用 1.解析 解析XML的时候单独解析所有的resultMap标签&#xff0c;封装成Re…

MySQL 核心模块揭秘 | 19 期 | 锁模块里有什么?什么样?

InnoDB 中管理表锁和行锁的锁模块&#xff0c;也就是传说中的锁子系统&#xff0c;在内存里是什么样的&#xff1f; 作者&#xff1a;操盛春&#xff0c;爱可生技术专家&#xff0c;公众号『一树一溪』作者&#xff0c;专注于研究 MySQL 和 OceanBase 源码。 爱可生开源社区出品…

LabVIEW开发EOL功能测试系统

LabVIEW开发EOL功能测试系统 介绍了一种基于LabVIEW开发的EOL功能测试系统方案&#xff0c;涵盖软件架构、工作流程、模块化设计、低耦合性、易于修改与维护、稳定性及硬件选型。系统通过高效的CAN通信实现对电机控制器的全面测试&#xff0c;确保运行可靠并支持未来的升级需求…

危机公关之负面信息优化技巧解析

当今时代&#xff0c;网络发布信息没有任何门槛&#xff0c;任何人可以通过互联网发布信息&#xff0c;这使负面信息产生的可能性大大提高&#xff0c;企业形成危机的可能性也大大提高。针对网络上的负面信息处理得当可能并不会对品牌造成伤害&#xff0c;处理不当就很可能给企…

QT之可拖动布局研究

1. 背景 最开始只用到了最基本的水平布局 、垂直布局。它的好处就是窗口整体缩放后&#xff0c;控件也自动等比例缩放。 但是比如水平布局之中的控件宽度比例、垂直布局之中的控件高度比例都是固定的。 平时也不怎么开发界面&#xff0c;最近有个需求&#xff0c;想界面上的…

Atlassian企业日技术分享:AI在ITSM中的创新实践与应用、Jira服务管理平台AI功能介绍

2024年5月17日&#xff0c;Atlassian中国合作伙伴企业日活动在上海成功举办。活动以“AI协同 创未来——如何利用人工智能提升团队协作&#xff0c;加速产品交付”为主题&#xff0c;深入探讨了AI技术在团队协作与产品交付中的创新应用与实践&#xff0c;吸引了众多业内专家、企…

深圳比创达电子EMC|EMC与EMI一站式解决方案:攻克电磁兼容难题

在当今这个科技日新月异、电子产品层出不穷的时代&#xff0c;电磁兼容&#xff08;EMC&#xff09;与电磁干扰&#xff08;EMI&#xff09;问题愈发凸显其重要性。为了确保电子设备的正常运行&#xff0c;减少电磁干扰对环境和人体的影响&#xff0c;EMC与EMI一站式解决方案成…

【回眸】Linux内核(十)system()函数与popen()函数

前言 system()函数的作用是执行一个shell脚本或者shell指令 popen与system()函数类似,不同点是popen()函数可以获取运行的shell脚本或者命令的输出结果 system() 函数参数 #include <stdlib.h> int system(const char *comand) 参考示例代码: #include <stdio.…

2023年全国消费品“增品种、提品质、创品牌”三品战略发展成果报告

来源&#xff1a;赛迪&欧特欧 近期历史回顾&#xff1a; 2023工业无线电磁环境白皮书——有色金属制造行业.pdf 2024出海企业人才发展实践指南.pdf 2024年全球电子商务市场.pdf 宝钢低碳钢铁技术策划及开发-钟勇.pdf 2023-2024年度中国智能制造产业发展报告.pdf 2024精准医…

【AI大模型】Function Calling

目录 什么是Function Calling 示例 1&#xff1a;调用本地函数 Function Calling 的注意事项 支持 Function Calling 的国产大模型 百度文心大模型 MiniMax ChatGLM3-6B 讯飞星火 3.0 通义千问 几条经验总结 什么是Function Calling Function Calling 是一种函数调用机…

【C++ | 构造函数】类的构造函数详解

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a;2024-06-06 0…

HCIA-RS基础-VLAN配置

目录 前言创建拓扑创建VLAN查看创建的VLAN配置trunk口并放行VLAN配置access接口查看所有vlan基本信息测试网络连通性命令合集 前言 VLAN定义&#xff1a;VLAN是一种将局域网内的设备从逻辑上划分成一个个网段&#xff0c;从而实现虚拟工作组的新兴数据交换技术。VLAN优点&…

【面试笔记】嵌入式软件工程师,汽车电子软件相关

文章目录 1. C语言基础1.1 const1.2 static1.3 回调函数的用法1.4 宏定义1.5 编译、链接过程1.6 堆与栈的区别&#xff1f;1.7 简单的字符串算法题&#xff0c;C语言实现1.7.1 给定一个字符串&#xff0c;按顺序筛选出不重复的字符组成字符串&#xff0c;输出该字符串1.7.2 给定…

Python3 迭代器和生成器

前言 本文主要介绍Python中的迭代器和生成器&#xff0c;主要内容包括 迭代器概述、生成器简介。 文章目录 前言一、迭代器简介二、生成器简介 一、迭代器简介 在 Python 中&#xff0c;迭代器(iterator)是一个实现了迭代器协议&#xff08;Iterator Protocol&#xff09;的…

opencv进阶 ——(十一)基于RMBG实现生活照生成寸照

实现步骤 1、检测人脸&#xff0c;可以使用opencv自带的级联分类器或者dlib实现人脸检测 2、放大人脸范围&#xff0c;调整到正常寸照尺寸 3、基于RMGB算法得到人像掩码 4、生成尺寸相同的纯色背景与当前人像进行ALPHA融合即可 alpha融合实现 void alphaBlend(cv::Mat&…

1 机器人软件开发学习所需通用技术栈(一)

机器人软件工程师技术路线&#xff08;如有缺失&#xff0c;欢迎补充&#xff09; 1. 机器人软件开发工程师技术路线 1.1 基础知识 C/C编程&#xff1a;掌握C/C语言基础&#xff0c;包括数据结构、算法、内存管理等。操作系统&#xff1a;了解Linux或Windows等操作系统的基本…

2.1 初识Windows程序

Windows程序设计是一种面向对象的编程。Windows操作系统以数据结构的形式定义了大量预定义的对象作为操作系统的数据类型。Windows动态链接库提供了各种各样的API接口函数供Windows应用程序调用。一个Windows应用程序是运行在Windows操作系统之上的。这些API接口函数的调用所实…

【Vue】路由的基本使用

文章目录 一、固定5个固定的步骤二、代码示例三、两个核心步骤四、完整代码 vue-router插件作用 修改地址栏路径时&#xff0c;切换显示匹配的组件 说明 Vue 官方的一个路由插件&#xff0c;是一个第三方包 官网 https://v3.router.vuejs.org/zh/ VueRouter的使用&#xff0…

TCP/IP协议介绍——三次握手四次挥手

TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff0c;传输控制协议/网际协议&#xff09;是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议&#xff0c;而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议…