主頁 > 企業開發 > 手寫一個 React 圖片預覽組件

手寫一個 React 圖片預覽組件

2023-06-13 08:27:33 企業開發

原文鏈接: 手寫一個 React 圖片預覽組件

前幾天打算給博客添加一個圖片預覽的效果,可在網上找了半天也沒找到合適的庫,于是自己干脆自己手寫了個,

最終實作效果如下:

gif

實作原理

當滑鼠點擊圖片時生成一個半透明遮罩,并添加一個與點擊圖片位置大小都相同的圖片,之后通過 CSS 實作圖片的放大和居中,當再次點擊時,通過洗掉樣式實作圖片的回傳,

具體操作

添加遮罩和圖片

此處需要用到 ReactDom 的 createPortal() 方法,它可以將元素渲染到網頁中的指定位置,因為要考慮到圖片的回傳,所以圖片的位置不能用 getBoundingClientRect() 提供的相對于視圖視窗的坐標,而是要用到 offsetTopoffsetLeft 提供的相對于 offsetParent 的坐標,所以需要將遮罩和圖片渲染到 body 元素中,并且二者需要為同一級,具體實作代碼如下:

import { createPortal } from 'react-dom';
import { useState, useRef } from 'react';

function Mask({ props, setStatus, imgRef }) {
    const close = () => {
        setStatus(false);
    };
    return createPortal(
        <div onClick={close} className='cursor-zoom-out'>
            <div className='fixed bottom-0 left-0 right-0 top-0 bg-black/75'></div>
            <img
                {...props}
                className='absolute'
                style={{
                    top: imgRef.current.offsetTop,
                    left: imgRef.current.offsetLeft,
                    width: imgRef.current.offsetWidth,
                    height: imgRef.current.offsetHeight,
                }}
            />
        </div>,
        document.body
    );
}

export default function Img(props) {
    const [status, setStatus] = useState(false);
    const imgRef = useRef(null);
    return (
        <>
            <img
                {...props}
                ref={imgRef}
                className={`cursor-zoom-in ${status ? 'invisible' : ''}`}
                onClick={() => {
                    setStatus(true);
                }}
                loading='lazy'
            />
            {status && <Mask props={props} setStatus={setStatus} imgRef={imgRef} />}
        </>
    );
}

此時點擊圖片便會在 body 下生成一個遮罩和處在相同位置的圖片,再次點擊時則會關閉,

image-20230611151719597

添加影片效果

影片效果主要由 CSS 中的 transitiontransform 實作,而 transform 主要用到了其中的 scale()translate 函式,

scale() 的數值為圖片縮放的倍數,我們需要將圖片盡量縮放到原先尺寸,但不能超出螢屏,所以要分別求出圖片寬度和高度的最大縮放倍數,之后對比取最小值,但在計算圖片目標尺寸時,需要與螢屏尺寸對比取最小值,

const scaleX = Math.min(naturalWidth, viewportWidth) / width;
const scaleY = Math.min(naturalHeight, viewportHeight) / height;
const scale = Math.min(scaleX, scaleY);

translate() 的數值為圖片在 X 和 Y 軸上的偏移量,我們需要將圖片偏移到螢屏中心,所以要求出圖片中心點距螢屏中心點的橫縱距離

image-20230611142628123

const translateX = ((viewportWidth - width) / 2 - left) / scale;
const translateY = ((viewportHeight - height) / 2 - top) / scale;

具體計算函式如下

const calcFitScale = imgRef => {
    const { top, left, width, height } = imgRef.current.getBoundingClientRect();
    const { naturalWidth, naturalHeight } = imgRef.current;
    const viewportWidth = document.documentElement.clientWidth;
    const viewportHeight = document.documentElement.clientHeight;
    const scaleX = Math.min(Math.max(width, naturalWidth), viewportWidth) / width;
    const scaleY = Math.min(Math.max(height, naturalHeight), viewportHeight) / height;
    const scale = Math.min(scaleX, scaleY);
    const translateX = ((viewportWidth - width) / 2 - left) / scale;
    const translateY = ((viewportHeight - height) / 2 - top) / scale;
    return `scale(${scale}) translate(${translateX}px, ${translateY}px)`;
};

這里講一下為什么要在生成偏移量的時候除以縮放倍數,因為 CSS 中 transform 的執行是有先后順序的,圖片進行 scale() 縮放后其 translate() 的偏移距離也會發生變化,所以需要在計算時提前考慮,倘若要先進行偏移后進行縮放,則可以不考慮此因素,

const translateX = (viewportWidth - width) / 2 - left;
const translateY = (viewportHeight - height) / 2 - top;
return `translate(${translateX}px, ${translateY}px) scale(${scale})`;

最終代碼

最后加上一點滾動監聽,螢屏監聽,遮罩透明度變化即可得到最終函式

import { createPortal } from 'react-dom';
import { useState, useRef, useEffect } from 'react';

function Mask({ props, setStatus, imgRef }) {
    const [transform, setTransform] = useState('');
    const [opacity, setOpacity] = useState(0.7);
    const close = () => {
        setOpacity(0);
        setTransform('');
        setTimeout(() => {
            setStatus(false);
        }, 300);
    };
    useEffect(() => {
        const handleResize = () => {
            setTransform(calcFitScale(imgRef));
        };
        window.addEventListener('resize', handleResize);
        handleResize();
        return () => window.removeEventListener('resize', handleResize);
    }, []);
    useEffect(() => {
        window.addEventListener('scroll', close);
        return () => window.removeEventListener('scroll', close);
    }, []);
    return createPortal(
        <div onClick={close} className="cursor-zoom-out">
            <div
                className="fixed bottom-0 left-0 right-0 top-0 bg-black"
                style={{
                    opacity,
                    transition: 'opacity 300ms cubic-bezier(0.4, 0, 0.2, 1)',
                }}
            ></div>
            <img
                {...props}
                className="absolute"
                style={{
                    transition: 'transform 300ms cubic-bezier(.2, 0, .2, 1)',
                    top: imgRef.current.offsetTop,
                    left: imgRef.current.offsetLeft,
                    width: imgRef.current.offsetWidth,
                    height: imgRef.current.offsetHeight,
                    transform: transform,
                }}
            />
        </div>,
        document.body
    );
}

export default function Img(props) {
    const [status, setStatus] = useState(false);
    const imgRef = useRef(null);
    return (
        <>
            <img
                {...props}
                ref={imgRef}
                className={`cursor-zoom-in ${status ? 'invisible' : ''}`}
                onClick={() => {
                    setStatus(true);
                }}
                loading="lazy"
            />
            {status && <Mask props={props} setStatus={setStatus} imgRef={imgRef} />}
        </>
    );
}

/**
 * 計算圖片縮放比例
 */
const calcFitScale = imgRef => {
    const margin = 5;
    const { top, left, width, height } = imgRef.current.getBoundingClientRect();
    const { naturalWidth, naturalHeight } = imgRef.current;
    const viewportWidth = document.documentElement.clientWidth;
    const viewportHeight = document.documentElement.clientHeight;
    const scaleX = Math.min(Math.max(width, naturalWidth), viewportWidth) / width;
    const scaleY = Math.min(Math.max(height, naturalHeight), viewportHeight) / height;
    const scale = Math.min(scaleX, scaleY) - margin / Math.min(width, height) + 0.002;
    const translateX = ((viewportWidth - width) / 2 - left) / scale;
    const translateY = ((viewportHeight - height) / 2 - top) / scale;
    return `scale(${scale}) translate3d(${translateX}px, ${translateY}px, 0)`;
};

transform 的初始值并沒有直接從 calcFitScale() 中獲取,而是通過在 useEffect() 進行賦值,因為如果一開始就給圖片定義了 transform ,則不會產生影片效果,

參考鏈接

Understanding translate after scale in CSS transforms

Why does order of transforms matter? rotate/scale doesn't give the same result as scale/rotate

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/555013.html

標籤:其他

上一篇:記錄--詳解 XSS(跨站腳本攻擊)

下一篇:返回列表

標籤雲
其他(160878) Python(38222) JavaScript(25493) Java(18225) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7247) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5874) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4589) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1964) Web開發(1951) C++(1933) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 手寫一個 React 圖片預覽組件

    前幾天打算給博客添加一個圖片預覽的效果,可在網上找了半天也沒找到合適的庫,于是自己干脆自己手寫了個。 ......

    uj5u.com 2023-06-13 08:27:33 more
  • 記錄--詳解 XSS(跨站腳本攻擊)

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言:我們知道同源策略可以隔離各個站點之間的 DOM 互動、頁面資料和網路通信,雖然嚴格的同源策略會帶來更多的安全,但是也束縛了 Web。這就需要在安全和自由之間找到一個平衡點,所以我們默認頁面中可以參考任意第三方資源,然后又引入 CSP ......

    uj5u.com 2023-06-13 08:22:13 more
  • js 0.07*100竟然是7.000000000000001

    無意中發現了一個問題:0.07*100=7.000000000000001,而不是7。 結果為什么是7.0000000000000001? 其實JavsScript中,變數在存盤時并不區分number和float型別,而是統一按float存盤。而javascript使用IEEE 754-2008 標 ......

    uj5u.com 2023-06-13 08:17:03 more
  • 前端vue可以左右滾動的切換的tabs tabs選項卡 滑動影片效果 自動

    前端vue可以左右滾動的切換的tabs tabs選項卡 滑動影片效果 自動寬度, 下載完整代碼請訪問https://ext.dcloud.net.cn/plugin?id=13003 效果圖如下: #### 使用方法 ```使用方法 swiperTabList: ["2023-06-10","202 ......

    uj5u.com 2023-06-13 08:16:57 more
  • 【React】: React的生命周期

    概述 生命周期的每個階段總是伴隨著一些方法的呼叫,這些方法就是生命周期的鉤子函式 鉤子函式的作用:為開發人員在不同操作階段提供了十幾 只有 類組件 才有生命周期 生命周期的圖片: 同時有: 1.1 創建時 的生命周期執行順序 撰寫以下代碼,從而驗證constructor,render,compone ......

    uj5u.com 2023-06-13 08:16:41 more
  • 前端vue uni-app多圖片上傳組件,支持單個檔案,多個檔案上傳 步驟條

    快速實作多圖片上傳組件,支持單個檔案,多個檔案上傳 步驟條step使用; 下載完整代碼請訪問uni-app插件市場地址:https://ext.dcloud.net.cn/plugin?id=12747 效果圖如下: 使用方法 使用方法 // 添加多張圖片(少于6張) addPhotoClick() ......

    uj5u.com 2023-06-13 08:16:29 more
  • 前端vue 宮格組件提供常見九宮格選單組件,擴充性好,可切換九宮格

    #### 快速實作vue uni-app宮格組件提供常見九宮格選單組件,擴充性好,可切換九宮格 十二宮格 十五宮格; 下載完整代碼請訪問uni-app插件市場地址:https://ext.dcloud.net.cn/plugin?id=12680 效果圖如下: ![](https://p3-juej ......

    uj5u.com 2023-06-13 08:16:23 more
  • js 0.07*100竟然是7.000000000000001

    無意中發現了一個問題:0.07*100=7.000000000000001,而不是7。 結果為什么是7.0000000000000001? 其實JavsScript中,變數在存盤時并不區分number和float型別,而是統一按float存盤。而javascript使用IEEE 754-2008 標 ......

    uj5u.com 2023-06-13 08:16:08 more
  • 記錄--詳解 XSS(跨站腳本攻擊)

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言:我們知道同源策略可以隔離各個站點之間的 DOM 互動、頁面資料和網路通信,雖然嚴格的同源策略會帶來更多的安全,但是也束縛了 Web。這就需要在安全和自由之間找到一個平衡點,所以我們默認頁面中可以參考任意第三方資源,然后又引入 CSP ......

    uj5u.com 2023-06-13 08:10:31 more
  • 前端vue 宮格組件提供常見九宮格選單組件,擴充性好,可切換九宮格

    #### 快速實作vue uni-app宮格組件提供常見九宮格選單組件,擴充性好,可切換九宮格 十二宮格 十五宮格; 下載完整代碼請訪問uni-app插件市場地址:https://ext.dcloud.net.cn/plugin?id=12680 效果圖如下: ![](https://p3-juej ......

    uj5u.com 2023-06-13 08:09:00 more