React 实现图片裁剪功能
React 实现图片裁剪功能
本文将详细介绍如何在React中实现图片裁剪功能,重点讲解Canvas渲染图片时出现模糊问题的原因及解决方案。通过具体代码示例和详细解释,帮助读者掌握这一实用技术。
技术栈
本文涉及的技术栈包括:
- 前端框架:React
- 剪切组件:react-image-crop(可通过npm或yarn安装)
- UI框架:MUI
实现代码
下面是具体的实现代码:
import { Paper } from '@mui/material'
import React, { useRef, useState } from 'react'
import './test.css'
import ReactCrop from 'react-image-crop'
import 'react-image-crop/dist/ReactCrop.css'
function Test() {
// 设置剪切框的初始数据
const [crop, setCrop] = useState({unit: 'px',x:0,y:0,width:200,height:200})
// 获取canvas的真实DOM
const canvasRef = useRef()
// 获取img的真实DOM
const imgRef = useRef()
const [url,setUrl] = useState(null)
// 图片初次加载时的回调函数
const onImageLoad = () =>{
const canvas = canvasRef.current
const image = imgRef.current
image.setAttribute('crossOrigin', 'anonymous')
canvas.style.width = '200px';
canvas.style.height = '200px';
canvas.width = 200 * devicePixelRatio
canvas.height = 200 *devicePixelRatio
const context = canvas.getContext("2d")
let width = (200 / image.width) * image.naturalWidth
let height = (200 / image.height) * image.naturalHeight
context.drawImage(image, 0,0,width,height, 0,0,canvas.width,canvas.height)
}
// Crop位置发生位移时的回调函数
const onCropChange = (c) =>{
setCrop(c)
const canvas = canvasRef.current
const image = imgRef.current
canvas.style.width = '200px';
canvas.style.height = '200px';
canvas.width = image.width * devicePixelRatio
canvas.height = image.height * devicePixelRatio
const context = canvas.getContext("2d")
const width = c.width * (image.naturalWidth / image.width);
const height = c.height * (image.naturalHeight / image.height);
let x = c.x * (image.naturalWidth / image.width)
let y = c.y * (image.naturalHeight / image.height)
context.drawImage(image, x,y,width,height, 0,0,canvas.width,canvas.height)
}
return (
<div>
<Paper className='paper'>
<ReactCrop crop={crop} onChange={onCropChange} className='cropper'>
<img ref={imgRef} src= "https://cdn.pixabay.com/photo/2014/05/13/16/19/porto-343487_1280.jpg" onLoad={onImageLoad}/>
</ReactCrop>
<div>
<canvas ref={canvasRef} ></canvas>
</div>
</Paper>
</div>
)
}
export default Test
下面是对应的CSS代码:
.paper {
width: 600px;
height: 500px;
outline: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
.cropper {
width: 300px;
object-fit: cover;
margin-bottom: 5rem;
}
}
在Test组件中定义了一个图片剪辑区和预览区。可以看到,上面是剪切图片的区域,下面是用Canvas渲染的区域。
基本概念
像素
像素是数字图像的最基本单位。生活中谈到一个图片有多大,我们会用用 XX px * XX px来表示。
我们可以看到下面这张像素图(像素图是一种绘画风格),是由一个个有颜色的方格子组成,每个格子就是1个像素
这两张图都是由1个个像素组成的。
物理像素
物理像素就是我们电脑、手机的分辨率,比如电脑屏幕分辨率是1920*1080 ,可以理解为横向有1920个格子,纵向有1080个格子,总共有207.36万个格子,每个格子就是一个像素点。
逻辑像素
逻辑分辨率就是前端CSS里的px了,逻辑像素并没有固定的物理长度。不能说1px的长度是几毫米,还是几厘米。这只是一个逻辑概念。
逻辑像素和物理像素之间的关系
上图表示有一个屏幕,物理分辨率为 5 * 6 ,屏幕中有一个Logo 图片。图片分辨率为3 * 4
现在我们换一个分辨率为 10 * 12 的屏幕,发现它变小了,这是为什么呢?所谓的逻辑分辨率为3*4是指在屏幕上占用 3 * 4个 物理像素点,由于每个物理像素点变小,那么显示的图片也会随之变小。
DPR
为了使得同一个逻辑像素能在不同分辨率的设备上保持一样的效果,设备制造商指定了一个设备像素比(dpr)。意思就是1px 的逻辑像素对应多少的物理像素。
原来在分辨率5 * 6 设备上展示的图片在 10* 12 设备上变小的原因是单个像素点变小了,这个时候,只要使得1个px的逻辑像素对应的物理像素由1 变成 2 ,也就是DPR为2 ,这样就能完美解决这一问题了。
位图像素
位图(Bitmap)我们常见的很多图片都是位图。比如这张。位图的基本组成单位是像素,我们就把这些像素叫作位图像素。这张图片就是由千千万万个位图像素组成的,一个位图像素中包含了很多的二进制数据
我们看一下上面这张图的信息,分辨率1000* 420 指的是宽度上由1000个位图像素组成,高度上由420个位图像素组成,宽度指的是宽度上可以占1000个逻辑像素,高度指的是高度上可以占420个逻辑像素。位深度指的是单个元素能显示多少种颜色,这张图片是32位,可以显示2的32次方种颜色
位图像素和逻辑像素
位图像素可以理解为就是逻辑像素,位图像素是专门用来描述位图的,1px 位图像素和1px 逻辑像素并没有什么差别,但是要注意,图片的位图像素和页面的展示的宽高是不同的概念。就拿上个图片来说,位图像素是1000 *420 ,说明图片可以在页面宽度上占1000个逻辑像素,但实际的展示宽度却并不一定是1000px,
请看这里的css宽高,是640 *324.6 这是个响应式页面,页面缩小,元素也跟着缩小
随着页面的缩小,css 的尺寸也在缩小。可见图片自身的分辨率和在页面的展示宽高完全是两个不同的概念。
开始分析canvas绘制图片模糊的原因
前面说过DPR 能够解决逻辑像素在不同分辨率的设备上以相同效果显示的问题,但我们接下来遇到的问题也是由它造成的。
请看下图,左图是标准屏幕,右图是高清屏幕
当DPR 为1 时,1个位图(Bitmap) 像素 = 1个物理像素,此时图像显示出来是清晰的。
当DPR 为2时, 1个位图像素(Bitmap) = 2 * 2 个物理像素,此时图像显示出来是模糊的。这是为什么呢?因为在位图中像素已经是最小的单位了,不可以拿一个位图像素的去填充4个物理像素,剩余的3个位图像素会使用类似的颜色填充
为什么不使用原色填充呢?,这是因为在高清屏幕中使用原色填充,图案锯齿感非常明显,图像明显缺乏了一丝顺化。
那么怎么解决呢?这这个例子中,要想让图像显示我们就要想办法让 1个 位图像素 = 1 个物理像素
有一个好的办法,就是放大图片,那么放大多少呢?
在这个例子中, 我们要把宽度,和高度都放大2倍
1px * 2 * 1px * 2 = 4px 这样 左侧的1个逻辑像素就等于右侧的1个物理像素
在实际的编码中我们不可以用常量来进行放大,而要使用devicePixelRatio (缩写DPR)进行动态的放大,这里在Edge 控制台里面打印了当前的devicePixelRatio。
最终解决方案
这里把canvas 画布的width, height 都放大DPR倍。这时会有小伙伴有疑问了,你把原来的图片放大了,图片不就是我想要的尺寸了么? 没关系,我们可用以设置canvas.style.width和canvas.style.height ,这样就会canvas 展示在页面上的尺寸就是canvas.style.width和canvas.style.height 了,具体实现可以看实现代码