【React】React前端图片裁剪组件
CrazyPanda发表于:2024-03-29 17:38:16浏览:302次
前端图片裁剪已经是很常见的需求了,在 react 的项目中推荐使用 react-image-crop 库,能勾勒出裁剪区域、宽高比例限制等等,但是还是有很多东西需要我们来做的,比如以下问题:
react-image-crop
react-image-crop 的基本使用
import ReactCrop from "react-image-crop"; import "react-image-crop/dist/ReactCrop.css"; const cropOnChange = (newCrop: any) => {}; const onImageLoaded = (image: any) => {}; <ReactCrop src={src} crop={crop} onChange={cropOnChange} onImageLoaded={onImageLoaded} > {props.children} </ReactCrop>;
限制图片的最小宽高
如果连展示位的最小宽高都不满足,那还裁剪什么
export function getImgSize(src: any, w: number, h: number): Promise<any> { return new Promise((resolve, reject) => { if (!src) return; const img = document.createElement("img"); img.src = src; img.onload = function() { const { width, height } = img; if (width < w || height < h) { reject(`请上传宽大于${w},高大于${h}的图片`); return; } resolve({ width, height, img }); }; }); }
原图按比例缩放
有的图片尺寸很大,但是我们要显示在一个屏幕之中,那么要采取原图按比例缩放,所以定高和定宽成了一个选择,那么会出现另一个问题,后面再说。
假设我们决定了定高:
const initImage = (image: any) => { const { width, height } = image; // 计算出现在高度和定高的比例 let radio = height / config.imgMinWH.h; // 那么现在宽度值 let cW = width / radio, cH = config.imgMinWH.h; if (width === 900) { cW = 900; radio = 1; let wRadio = cW / width; cH = height * wRadio; } // 设置image的宽高 image.width = cW; image.height = cH; setScaleRadio(radio); setCrop({ ...crop, ...{ x: 0, y: 0, width: cW, unit: "px" } }); };
然后按照比例在原图上裁剪
裁剪的话使用 canvas 来裁剪,那么有个很重要的问题,就是图片的跨域问题,图片不在一个域下的话,使用 canvas 会污染画布,这个时候别急,将使用其他方法来解决这个问题,oss 的话开启图片的 get 操作即可
const canvas = document.createElement("canvas"); const ctx: any = canvas.getContext("2d"); const canvasImage = () => { const img = new Image(); img.crossOrigin = "Anonymous"; img.src = signatureUrl(props.src || "", false); common.showLoading(); img.onload = function() { const { x, y, width, height } = crop; canvas.width = Math.floor(width * scaleRadio); canvas.height = Math.floor(height * scaleRadio); ctx.drawImage( img, Math.floor(x * scaleRadio), Math.floor(y * scaleRadio), img.width, img.height, 0, 0, img.width, img.height ); getImageFileInfo().then(fileInfo => { // 上传图片到接口 uploadImage(fileInfo); }); }; }; const getImageFileInfo = (): Promise<any> => { return new Promise((resolve, reject) => { // 将canvas转成图片base64的url,可以直接打开 // console.log(canvas.toDataURL('image/png')); // 将canvas转成Blob格式的二进制数据 canvas.toBlob(blob => { if (!blob) { console.error("Canvas is empty"); return; } resolve(blob); }); }); };
使用 ajax 请求图片来解决跨域
const canvas = document.createElement("canvas"); const ctx: any = canvas.getContext("2d"); const requestImg = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); const xhr = new XMLHttpRequest(); xhr.onload = function() { const url = URL.createObjectURL(this.response); const img = new Image(); img.onload = function() { canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; ctx.drawImage(this, 0, 0, img.naturalWidth, img.naturalHeight); URL.revokeObjectURL(url); const base64 = canvas.toDataURL(); console.log(base64); }; img.src = url; }; xhr.open("GET", props.src || "", true); xhr.responseType = "blob"; xhr.send(); };
image-core 完整组件
import React, { useState, useEffect } from "react"; import ReactCrop from "react-image-crop"; import { SuperUpload } from "@/components/Base"; import defaultConfig from "./config"; import "react-image-crop/dist/ReactCrop.css"; import { multipartUpload } from "@/components/Upload/method"; import { signatureUrl } from "@/utils/oss"; const style = require("./index.less"); export interface CoreImageInterface { imgMinWH?: { w: number, h: number }; visible: boolean; value?: string; config?: any; src?: string; aspect?: number; openCore: Function; srcChange: Function; onCancel?: Function; onChange?: Function; } export function getImgSize(src: any, w: number, h: number): Promise<any> { return new Promise((resolve, reject) => { if (!src) return; const img = document.createElement("img"); img.src = src; img.onload = function() { const { width, height } = img; if (width < w || height < h) { reject(`请上传宽大于${w},高大于${h}的图片`); return; } resolve({ width, height, img }); }; }); } export function makeUploadName() { return "core-image/" + +new Date(); } const canvas = document.createElement("canvas"); const ctx: any = canvas.getContext("2d"); const CoreImage: React.FC<CoreImageInterface> = props => { const [crop, setCrop]: [any, Function] = useState({ aspect: props.aspect || 3 / 1 }); const [scaleRadio, setScaleRadio] = useState(1); const [visible, setVisible] = useState(false); const [src, setSrc] = useState(""); const config = { ...defaultConfig, ...props.config }; useEffect(() => { setVisible(props.visible); if (!props.visible) { setCrop({ aspect: props.aspect || 3 / 1 }); setSrc(""); return; } initCrop(); }, [props.visible]); const initCrop = () => { const img = new Image(); const src = signatureUrl(props.src || "", false); img.src = src; img.onload = function() { setSrc(src || ""); }; }; const initImage = (image: any) => { const { width, height } = image; let radio = height / config.imgMinWH.h; let cW = width / radio, cH = config.imgMinWH.h; if (width === 900) { cW = 900; radio = 1; let wRadio = cW / width; cH = height * wRadio; } image.width = cW; image.height = cH; setScaleRadio(radio); setCrop({ ...crop, ...{ x: 0, y: 0, width: cW, unit: "px" } }); }; const onImageLoaded = (image: any) => { initImage(image); }; const cropOnChange = (newCrop: any) => { if (newCrop.width * scaleRadio < config.imgMinWH.w) return; setCrop(newCrop); // console.log(crop); }; const canvasImage = () => { const img = new Image(); img.crossOrigin = "Anonymous"; img.src = signatureUrl(props.src || "", false); common.showLoading(); img.onload = function() { const { x, y, width, height } = crop; canvas.width = Math.floor(width * scaleRadio); canvas.height = Math.floor(height * scaleRadio); ctx.drawImage( img, Math.floor(x * scaleRadio), Math.floor(y * scaleRadio), img.width, img.height, 0, 0, img.width, img.height ); getImageFileInfo().then(fileInfo => { uploadImage(fileInfo); }); }; }; const uploadImage = (fileInfo: Blob) => { const fileName = makeUploadName(); multipartUpload({ fileName, fileInfo }).then((res: any) => { common.hideLoading(); props.onChange && props.onChange(res.name); }); }; // ajax请求图片来解决跨域 // const requestImg = () => { // ctx.clearRect(0, 0, canvas.width, canvas.height); // const xhr = new XMLHttpRequest(); // xhr.onload = function() { // const url = URL.createObjectURL(this.response); // const img = new Image(); // img.onload = function() { // canvas.width = img.naturalWidth; // canvas.height = img.naturalHeight; // ctx.drawImage(this, 0, 0, img.naturalWidth, img.naturalHeight); // URL.revokeObjectURL(url); // const base64 = canvas.toDataURL(); // console.log(base64); // }; // img.src = url; // }; // xhr.open('GET', props.src || '', true); // xhr.responseType = 'blob'; // xhr.send(); // }; const getImageFileInfo = (): Promise<any> => { return new Promise((resolve, reject) => { // console.log(canvas.toDataURL('image/png')); canvas.toBlob(blob => { if (!blob) { console.error("Canvas is empty"); return; } resolve(blob); }); }); }; const openCore = () => { if (props.src) props.openCore(); }; const onCancel = () => { props.onCancel && props.onCancel(); }; const onConfirm = () => { canvasImage(); }; return ( <div> <> <div className={style.showBox}> <img style={{ width: `${config.thumbImageWH.w}px`, height: `${config.thumbImageWH.h}px` }} src={signatureUrl(props.value || "")} alt="" /> <div className={style.againOp}> <SuperUpload accept=".png,.jpg" isSelect={true} showUploadList={false} listType="text" imgMinWH={config.imgMinWH} imgText={<div className={style.btn}>重新上传</div>} onChange={res => props.srcChange(res)} ></SuperUpload> <div onClick={openCore} className={style.btn}> 裁剪头图 </div> </div> </div> </> <> {visible && src ? ( <div className={style.container}> <div className={style.core}> <ReactCrop src={src} crop={crop} onChange={cropOnChange} onImageLoaded={onImageLoaded} > {props.children} </ReactCrop> <div style={{ top: `${crop.y + crop.height / 2 - 15.5}px`, left: `${crop.x + crop.width / 2 - 46}px` }} className={style.size} >{`${Math.floor(crop.width * scaleRadio)} × ${Math.floor( crop.height * scaleRadio )}`}</div> <div style={{ top: `${crop.height + crop.y + 10}px`, left: `${crop.width + crop.x - 146}px` }} className={style.op} > <div onClick={onCancel} className={style.btn}> 取消 </div> <div onClick={onConfirm} className={style.btn}> 确定 </div> </div> </div> </div> ) : null} </> </div> ); }; export default CoreImage;
原文链接https://blog.csdn.net/qq_42036203/article/details/104882489
猜你喜欢
- 【AntDesignPro】Ant Design Pro学习记录—ProTable的使用(一)
- 目录一、关于ProTable二、使用步骤1.新建页面2.修改接口3.接口调用4.数据显示和检索1)不同类型内容显示2)列表检索3)列表内容样式设置5.其它1)render的简单使用2)图片点击预览3)翻页总结前言因为项目需要,确定了Ant Design Pro框架来开发后台管理端,刚接触这套框架,而且配套的资料真的很少,只能基于官方demo和网上不完整的学习经验一次次尝试,终于有个像样的结果,记录一下研究学习的成果,也给需要的同学一些帮助。本次学习研究基于Ant Design Pro V5版本,
- 【AntDesignPro】Ant Design Pro学习记录—自定义菜单选中
- 页面增删改查,打开子页面时,要让父页面菜单选中,参考官网给出的方案菜单的高级用法 - Ant Design Pro 使用的V5版本,直接设置 parentKeys:['/product'] 即可export default [ { path: '/product', // 不展示菜单
- 【AntDesignPro】Ant Design Pro学习记录—ModalForm的使用(四)
- 一、 ModalForm自定义footer按钮参考官网,Modal弹框是可以自定义按钮的,原想着ModalForm的modalprops可以设置自定义footer,结果设置一直不生效,最终还是使用Modal嵌套了ProForm实现了功能,在此记录一下。ant design pro使用的V5版本。1、Modal自定义footer参考官网https://ant-design.antgroup.com/components/modal-cn?from=msidevs.net#components-mo
- 【AntDesignPro】Ant Design Pro学习记录—ModalForm的使用(二)
- 目录一、ModalForm高度设置二、ModalForm点击阴影背景,不隐藏弹框三、ProFormSelect联动四、ProFormText关联赋值一、ModalForm高度设置在modalProps中设置bodyStyle:{height:500,overflowY:'scroll'}编辑效果如下:编辑二、ModalForm点击阴影背景,不隐藏弹框同样在modalProps里面,配置maskClosable: false,就可以实现点击弹框外阴影,不隐藏弹框<
- 【AntDesignPro】Ant Design Pro学习记录—播放视频和音频
- 在ant design pro中,使用video和audio标签播放视频和音频1、播放视频我使用ModalForm弹框显示,autoPlay设置是否自动播放,controls设置操作按钮是否显示,设置好宽度和高度即可src是配置视频文件的链接<ModalForm title="播放视频" &n
- 【React】React中Typecript的使用
- 目录一、创建React的TypeScript项目二、使用差别1、基本使用2、Props传值的差别3、State传参三、总结一、创建React的TypeScript项目见:如何在React项目中引入TypeScript?_duansamve的博客-CSDN博客二、使用差别1、基本使用其基本使用和javascript编写React项目时差不多这是一份.tsx文件代码:可以看到和之前的.jsx使用并无太大差别import React, { Component }
- 【AntDesignPro】Ant Design Pro学习记录—前后端一体化部署
- 目录前言一、系统配置二、ant design pro访问路径配置三、站点访问路径配置前言好长时间没记录了,使用ant design pro有一年了,期间陆续做了好几个项目,从陌生到熟练,还有好多钻研成果没记录,后续有时间陆续补上。之前几个项目一直是前后端分开部署的,需要配置两个站点域名访问,还要解决跨域session问题,这次把一体化部署记录一下。一、系统配置服务端用的LNMP,使用tp6框架,使用宝塔面板管理。tp站点先正常部署,步骤省略,见下图:配置网站目录和运行目录: 编辑配置伪
- 【AntDesignPro】L7Plot地理可视化组件的使用
- L7Plot介绍L7Plot 基于 L7 实现的开箱即用地理空间数据可视化图表库L7Plot 专注于地理可视化图表。以声明配置式的方式,降低用户使用成本;以常见地理图表分类的方式,降低用户选择成本;内部集成全国行政区域数据,降低用户使用地理数据心智;支持多图层及多图表层叠,方便用户定制复杂的业务场景;L7Plot 专注于地理数据可视化展示,不会涉及数据编辑能力。前言项目需要,使用antdesignpro做前端,并绘制可视化大屏,回执地图组件,于是使用了L7Plot。功能目的绘
- 【AntDesignPro】Ant Design Pro学习记录—默认主题配色修改
- 版本: Ant Design Pro V5先参考下官网定制主题 - Ant Design再参考这篇文章antd pro 修改全局样式_tankpanv的博客-CSDN博客_antd修改全局样式最后自己实验:第一步,在config.ts文件中配置theme: { 'primary-color': defaultSettings.primaryColor, },这种配置需
- 【AntDesignPro】Ant Design Pro学习记录—前后端分离跨域设置,解决跨域session不一致
- 目录前言一、为什么跨域二、跨域配置三、跨域请求session不一致前言第一次做前后端分离,也是踩了很多坑,记录一下AntDesignPro跨域解决的方式。服务器系统使用Nginx,服务端使用thinkphp6。AntDesignPro正式build放到服务器上后,提示登录成功,但一直登录不上,后来发现是跨域session不同导致的,登录的时候session和登录成功后session的id不同,导致提示登录成功,就是登录不进去的情况。一、为什么跨域不想知道为什么要跨域,只想知道怎么解决。二、跨域配
栏目分类全部>