两站图片滑动对比效果实现(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.rhkb.cn/news/342989.html

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

相关文章

视觉SLAM十四讲:从理论到实践(Chapter12:建图)

前言 学习笔记&#xff0c;仅供学习&#xff0c;不做商用&#xff0c;如有侵权&#xff0c;联系我删除即可 一、主要目标 1. 理解单目SLAM中稠密深度估计的原理。 2. 通过实验了解单目稠密重建的过程。 3. 了解几种RGB-D重建中的地图形式。 构建的地图也有多种功能分类&…

DexCap——斯坦福李飞飞团队泡茶机器人:更好数据收集系统的原理解析、源码剖析

前言 2023年7月&#xff0c;我司组建大模型项目开发团队&#xff0c;从最开始的论文审稿&#xff0c;演变成目前的两大赋能方向 大模型应用方面&#xff0c;以微调和RAG为代表 除了论文审稿微调之外&#xff0c;目前我司内部正在逐一开发论文翻译、论文对话、论文idea提炼、论…

CSS实现3个圆点加载动画

加载动画主要使用了css的animation和transform属性&#xff0c;animation用来实现动画效果&#xff0c;transform实现过渡&#xff0c;让动画看起来更真实 一、html <div class"loadding-box"><div class"dot1"></div><div class&qu…

VCAST创建单元测试工程

1. 设置工作路径 选择工作目录,后面创建的 UT工程 将会生成到这个目录。 2. 新建工程 然后填写 工程名称,选择 编译器,以及设置 基础路径。注意 Base Directory 必须要为代码工程的根目录,否则后面配置环境会失败。 这样工程就创建好了。 把基础路径设置为相对路径。 …

解决Windows Hosts 文件因为权限无法修改的问题

如何修改 Windows Hosts 文件并添加域名映射 在日常工作中&#xff0c;可能需要修改 Windows 的 hosts 文件&#xff0c;以将特定的域名映射到指定的 IP 地址。本文介绍三种方法来完成这一任务&#xff1a;直接手动编辑 hosts 文件&#xff0c;使用批处理文件自动完成任务&…

【docker】 /bin/sh: ./mvnw: No such file or directory解决方案.dockerignore被忽略

报错如下&#xff1a;解决方案很简单&#xff0c;但是容易让大家忽视的问题。 > CACHED [stage-1 2/4] WORKDIR /work/ …

OpenCV学习(4.4) 平滑图像

1.目的 在本教程中将学习&#xff1a; 用各种低通滤波器模糊图像。对图像应用自定义过滤器&#xff08;二维卷积&#xff09;。 在图像处理中&#xff0c;平滑图像是一种去噪和模糊技术&#xff0c;用于减少图像中的噪声和细节&#xff0c;使得图像看起来更加平滑。平滑处理…

Java核心: 为图片生成水印

今天干了一件特别不务正业的事&#xff0c;做了一个小程序用来给图片添加水印。事情的起因是需要将自己的身份证照片分享给别人&#xff0c;手边并没有一个趁手的工具来生成图片水印。很多APP提供了水印的功能&#xff0c;但会把我的图片上传到他们的服务器&#xff0c;身份证太…

OpenCV的“画笔”功能

类似于画图软件的自由笔刷功能&#xff0c;当按住鼠标左键&#xff0c;在屏幕上画出连续的线条。 定义函数&#xff1a; import cv2 import numpy as np# 初始化参数 drawing False # 鼠标左键按下时为True ix, iy -1, -1 # 鼠标初始位置# 鼠标回调函数 def mouse_paint(…

冯喜运:6.7今日外汇黄金原油走势分析及日内操作策略

【黄金消息面分析】&#xff1a;美国初请失业金人数超预期&#xff0c;市场对美联储9月降息预期升温&#xff0c;全球降息潮起&#xff0c;黄金市场受支撑。北京时间本周四&#xff0c;美国劳工部公布的数据显示&#xff0c;截至6月1日当周初请失业金人数增加至22.9万人&#x…

docker-compose 最新详细安装教程

方法1.安装Compose单机版 此方法是网上大部分教程的办法&#xff0c;官方不提倡这种方法安装&#xff1a; curl -SL https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose sudo chmod x /usr/local/…

Diffusers代码学习: IP-Adapter

从操作的角度来看&#xff0c;IP-Adapter和图生图是很相似的&#xff0c;都是有一个原始的图片&#xff0c;加上提示词&#xff0c;生成目标图片。但它们的底层实现方式是完全不一样的&#xff0c;我们通过源码解读来看一下。以下是ip adapter的实现方式 # 以下代码为程序运行…

BGP基础实验

BGP协议中的建邻&#xff0c;与宣告路由分开的 在任何一台BGP路由上&#xff0c;均可宣告本地路由表中通过任何形势获取的路由条目&#xff0c;将其共享给其他BGP邻居&#xff1b; 然后display ip rou查看 *>代表状态 *的意思是可用 >代表优 i和*>无关&#x…

【面试题-004】ArrayList 和 LinkList区别

文章目录 List和setArrayList扩容机制HashMap扩容机制HashMap初始容量&#xff08;Initial Capacity&#xff09;和负载因子&#xff08;Load Factor&#xff09;HashMap与HashTable区别 &#xff1f;HashMap底层数据结构&#xff1f;ConcurrentHashMap底层数据结构&#xff1f…

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-同振与顺振的用法

文章目录 前言联系我们实现步骤同振顺振 前言 什么是同振、顺振&#xff1f; 同振 &#xff1a;同振是指多个终端同时振铃顺振&#xff1a;顺振是指多个终端顺序振铃 联系我们 有意向了解呼叫中心中间件的用户&#xff0c;可以点击该链接添加工作人员的微信&#xff1a;顶顶…

Hi3519DV500 学习摘录

文章目录 一、问题1、open-vm-tools 安装2、pushd: not found3、autoreconf4、编译util-linux源码时报错 ERROR: You must have autopoint installed to 二、NFS1、服务器搭建2、u-boot常用命令3、配置4、问题 三、补缺1、make 一、问题 1、open-vm-tools 安装 open-vm-tools…

【51单片机】智能百叶窗项目

文章目录 功能演示&#xff1a;前置要求&#xff1a;主要功能&#xff1a;主要模块&#xff1a;主函数代码&#xff1a; 具体的仿真程序和代码程序已经免费放置在资源中&#xff0c;如有需要&#xff0c;可以下载进行操作。 功能演示&#xff1a; 前置要求&#xff1a; 编译软…

Visual Studio 2022创建dll并调用

需求&#xff1a; 创建A项目&#xff0c;有函数和类&#xff0c;将A项目生成DLL动态链接库 创建B项目&#xff0c;使用A项目生成的dll和lib相关文件 正常项目开发.h用于函数声明&#xff0c;.cpp用于函数实现&#xff0c;但是项目开发往往不喜欢将.cpp函数实现的代码发给别人&…

30天收入500万美金!揭秘超休闲手游《Royal Match》吸金秘诀!

据AppMagic发布的收入榜中&#xff0c;超休闲手游《Royal Match》成绩斐然&#xff0c;不仅在三消赛道排名第一&#xff0c;更是冲上了应用畅销榜第四名&#xff0c;30天内增收超500万美元&#xff01; 来源&#xff1a;AppMagic 6月畅销榜 三消解谜&#xff0c;作为全球范围内…

新品发布 | 飞凌嵌入式RK3562J核心板,智能工业时代的国产智慧引擎

飞凌嵌入式推出FET3562J-C全国产核心板&#xff0c;专为工业自动化及消费类电子设备设计&#xff0c;打造智能工业时代的国产智慧新引擎。 FET3562J-C核心板基于Rockchip RK3562J处理器开发设计&#xff0c;该处理器采用22nm先进制程工艺&#xff0c;集成了4个ARM Cortex-A53高…