兩站圖片滑動對比效果實現(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.pswp.cn/web/23014.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23014.shtml
英文地址,請注明出處:http://en.pswp.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;吸引了眾多業內專家、企…

ros1中的server服務的創建與使用函數指針類型別名請求處理函數

ros1中的server服務的創建與使用函數指針類型別名請求處理函數 法一: #include "ros/ros.h" //自定義消息 #include "trilateration/trilateration_srvs.h"void handleDeletePosint(const trilateration::trilateration_srvs::Request& req, trilate…

深圳比創達電子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優點&…

設計模式-單例模式(創建型)

創建型-單例模式 了解單例 單例模式是一種創建型設計模式&#xff0c;它提供了一種創建對象的最佳方式;它必須保證&#xff1a; 單例類只能有一個實例化對象&#xff1b;單例類必須創建自己的唯一實例&#xff1b;單例類必須給其他對象提供實例&#xff1b; 另外&#xff1a;…

【面試筆記】嵌入式軟件工程師,汽車電子軟件相關

文章目錄 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等操作系統的基本…

android 13 aosp 預置so庫

展訊對應的main.mk配置 device/sprd/qogirn**/ums***/product/***_native/main.mk $(call inherit-product-if-exists, vendor/***/build.mk)vendor/***/build.mk PRODUCT_PACKAGES \libtestvendor///Android.bp cc_prebuilt_library_shared{name:"libtest",srcs:…