手写一个 React 图片预览组件

这篇具有很好参考价值的文章主要介绍了手写一个 React 图片预览组件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

原文链接: 手写一个 React 图片预览组件

前几天打算给博客添加一个图片预览的效果,可在网上找了半天也没找到合适的库,于是自己干脆自己手写了个。

最终实现效果如下:

手写一个 React 图片预览组件

实现原理

当鼠标点击图片时生成一个半透明遮罩,并添加一个与点击图片位置大小都相同的图片,之后通过 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 下生成一个遮罩和处在相同位置的图片,再次点击时则会关闭。

手写一个 React 图片预览组件

添加动画效果

动画效果主要由 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 轴上的偏移量,我们需要将图片偏移到屏幕中心,所以要求出图片中心点距屏幕中心点的横纵距离

手写一个 React 图片预览组件

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.toymoban.com/news/detail-480278.html

到了这里,关于手写一个 React 图片预览组件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 图片链接或pdf链接通过浏览器打开时,有时可以直接预览,有时却是下载,为什么?

    在前端开发中,有时候需要对一些文件链接进行特殊处理,比如对于一些图片链接或者PDF链接,有时我们需要通过浏览器打开进行预览,有时又不希望通过浏览器进行打开,而是希望能够直接下载到本地。但现实效果却往往跟我们相反,我们希望浏览器打开时,他却直接下载

    2024年02月10日
    浏览(60)
  • 解决:js 根据图片链接(image url)下载,有的打开预览,有的下载

    1、问题描述 https://*****/drugTestReport/20230515/202305151106111386737.png https://*****/drugTestReport/20230605/202306051540314553141.jpg 同样结构的两个图片链接,使用window.open(url),一个是打开预览,另一个是下载   2、解决方法,通过fetch请求url,获取blob类型,区分情况,统一成下载。  

    2024年02月09日
    浏览(52)
  • marked在vue项目中改变超链接跳转方式和图片放大预览

    这里我是另起一个js文件对marked的配置做了修改,参考如下 然后在vue文件中进行进行该文件的引用 最后格式化markdown文本

    2024年02月11日
    浏览(37)
  • React antd upload组件上传视频并实现视频预览

    记录问题:antd的upload组件文档中对于视频的上传预览没有明确的文档demo,在这里记录一下 项目需求:支持图片及视频的上传并实现预览,点击上传后不会立即请求接口上传资源,后续点击确定再上传 上代码

    2024年02月04日
    浏览(44)
  • uniAPP 视频图片预览组件

    效果图   思路:处理文件列表,根据文件类型归类 已兼容 H5  ios 设备,测试已通过 浙政钉,微信小程序 视频资源因为,没有预览图,用灰色图层加播放按钮代替

    2024年02月15日
    浏览(69)
  • vue实现预览图片及视频组件

       组件代码内容 MediaViewer.vue 引用  VideoOrImagePreview.vue 使用 注:element-plus,vue3

    2024年02月04日
    浏览(37)
  • 强大的图片预览组件Viewer.js

    ​ Viewer.js 是一款强大的图片查看器。我们通过Viewer.js 在页面上添加强大的图片查看功能,同时,这款优秀的插件配置操作起来也非常的方便。 Viewer.js分为2个版本,js版本和jquery版本,下载地址分别为 纯JS版本:https://github.com/fengyuanchen/viewerjs jQuery 版本:GitHub - fengyuanchen/j

    2024年01月18日
    浏览(40)
  • vue2自定义封装图片预览组件

    前言:预览图片现在已经有成熟的组件了,比如element ui的图片预览功能,但是现实开发过程中,element ui图片预览已经不满足需求了,比如涉及预览时删除图片以及下载图片 自定义封装图片预览组件 功能:滚轮滚动图片放大、还原图片、左旋转、右旋转、上一张、下一张、删

    2024年01月18日
    浏览(59)
  • el-upload 组件上传/移除/报错/预览文件,预览图片、pdf 等功能

    dialog.vue @/styles/dialog-style.scss

    2024年02月06日
    浏览(76)
  • Vue3.0实现图片预览组件(媒体查看器)

    前言: 最近项目中有个场景,一组图片、视频、音频、文件数据,要求点击图片可以放大预览,左右可以切换音视频、文件,支持鼠标及各种键控制 缩放,左右旋转,移动等功能,整理了一下,封了个组件,注释很全面,每块地方都有讲解,可以直接拿到项目中使用 先看下

    2023年04月09日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包