上传选择、拖拽、剪裁功能
前言
平时我们上传文件、拖拽图片,基本上都是用 antd、elementUI 等库,非常方便,但这不代表我们不需要了解这些东西,实际上图片这些知识点也不是很多,了解总比不知道强
假设我们碰到一个场景,我们的页面效果很简单,就展示一些好看的图片,还支持上传,不需要使用那么庞大的库,客户就要快,此时我们没必要引用那么大的 antd、elementUI 等库,我们直接写一个又快效率又高,那么多号,引入三方库后,还要担心打包文件偏大客户嫌弃问题
下面就介绍上传按钮的自定义、拖拽、剪裁
功能
上传选择按钮效果调整
我们先写一个 upload 的样式 css,实际可以使用图片,更适合我们的场景,这里就简写了
//这个样式作为 upload 的样式
.upload {
width: 200px;
height: 200px;
box-shadow: 1px 1px 10 10 #333;
background: linear-gradient(red, green, blue);
cursor: pointer;
}
看起来长这样,实际可以用图片代替
我们将 input 嵌入到 div 中,div 作为样式,input 填充父视图,并隐藏,这样就可以实现点击上传了
<div className="upload">
<input
style={{
width: "100%",
height: "100%",
opacity: 0,
}}
type="file"
onChange={(e) => e.target.files)}
/>
</div>
实际上传,还可以通过下面这样,隐藏 input 和 他的时间,通过点击指定的节点,然后响应 input 的 dom(input 的 dom 可以再其他任意地方,也可以点击的时候新创建一个,选择完毕删除),这样也同样实现上传按钮效果的自定义
<div
className="upload"
onClick={() => {
const dom = document.querySelector("input");
if (!dom) return;
dom.click();
}}
>
<input
type="file"
style={{ opacity: 0, pointerEvents: "none" }}
onChange={(e) => onInputChanged(e.target.files)}
/>
</div>
onChange 回调
onChange 回调中 e.target 就是我们 input 组件,其中的 files 就是我们选择的图片 file 数组,可以使用 file 上传,如果是文件夹,则 file 的 type 则为空,可以通过该参数过滤、错误提示等
const onInputChanged = (fileList: FileList | null) => {
if (!fileList) return;
const files = Array.from(fileList).filter((e) => e.type);
//过滤掉 type 为空的,那不是文件,可能是文件夹,想要文件夹需要一些额外的三方库支持
console.log(fileList, files);
};
上传拖拽实现
图片拖拽实际上就是走的 onDrop
方法,通过这个方法,可以获取到拖拽进来的文件集合,.dataTransfer.files
可以获取到拖拽进来的文件
但是有一个问题,就是浏览器有一个默认行为,一些浏览器中,拖拽进来的图片默认会开启一个新的窗口打开图片,我们可以通过 preventDefault 方法阻止用户的默认行为,onDragEnter
、onDragOver
、onDragLeave
、onDrop
我们均阻止一下即可,否则仍然可能另起窗口打开图片
ps
:onDragEnter
拖拽进入时回调、 onDragOver
拖拽进入持续触发、onDragLeave
拖拽离开元素触发、onDrop
在元素中松手触发,实际上以前我也写过一个 threejs 的浏览器拖拽到 3d 场景的组件库功能,实际上就用到了此类方法衔接拆分
<div
className="upload"
onDragEnter={(e) => {
//拖拽进入出发
// console.log("onDrag", e);
e.preventDefault();
}}
onDragOver={(e) => {
//拖拽进入持续触发
// console.log("onDragOver", e);
e.preventDefault();
}}
onDragLeave={(e) => {
//拖拽离开元素触发
// console.log("onDragLeave", e);
e.preventDefault();
}}
onDrop={(e) => {
//拖拽到div里面
//e.dataTransfer.files 保存的就是拖拽进来转化的file信息
console.log("onDrop", e.dataTransfer.files);
e.preventDefault();
//拿到拖拽进来的图片,直接回调即可
onInputChanged(e.dataTransfer.files);
}}
onClick={() => {
const dom = document.querySelector("input");
if (!dom) return;
dom.click();
}}
>
...input
</div>
图片剪裁功能
剪裁之前,我们先把拿到的图片显示
const file = files[0];
//读取file
const reader = new FileReader();
reader.onload = (e) => {
const img = document.querySelector("img1") as HTMLImageElement;;
//直接给指定节点赋值图片
img.src = e.target.result
};
reader.readAsDataURL(file);
上面完成显示后,剪裁的话,实际上在图片上放置一个可以移动缩放的框
就行了,用于确定裁剪区域
,还需要写一些相关事件(懒得写),这里就不多介绍了
确定剪裁区域后(x, y, width, height),下面介绍,将剪裁后的图片绘制问题
图片剪裁实际上就用到了 canvas 功能,功能也很简单,只需要使用 canvas 上下文 drawImage 即可
const img = document.querySelector("img1") as HTMLImageElement;
//我们希望图片剪裁到 120 x 120 分辨率
const canvas = document.createElement('canvas')
canvas.width = 120
canva.height = 120
const ctx = canvas.getContext('2d')
//直接剪裁即可,只需要设置原图剪裁区域,和目标绘制区域即可
ctx?.drawImage(img, x, y, width, height, 0, 0, 120, 120)
ps
:如果出现canvas一些区域模糊,实际上是一些视网膜屏(多倍像素屏),可以将原图也按照指定比例(scale、radio)缩放绘制,就不会出现问题了(一般移动设备会出现此类问题)
下面介绍写一下 drawImage,其可以理解为,将原图片,将其按照一定裁剪方式,放到另一个空白的内容上
,超出部分剪裁,其一共声明了三个方法
//将原图整个放到另一个空白内容上(cancas),原图距离空白纸左侧 dx,上侧 dy距离,绘制图片,因此会有留白(取决于canvas背景),由于没有设置dw、dh,不会缩放,超出部分不会被绘制
drawImage(image: CanvasImageSource, dx: number, dy: number): void;
//将原图整个放到另一个空白内容上(cancas),设置设置相对空白纸的dx,dy,并且设定目标宽度dw、高度dh(原图会被压缩绘制到canvan指定区域)
drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;
//将原图整个放到另一个空白内容上(cancas),发现多了四个参数sx、sy、sw、sh
//sx、sy、sw、sh相当于对原图区域进行裁剪,将裁剪后的内容,作为图片源绘制到空白纸(canvas)上
//设置设置相对空白内容上(cancas)的dx,dy,并且设定目标宽度、高度(原图会被压缩绘制到canvan指定区域)
drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
具体参数更细致的如下所示,可以看参考自 MDN,实际上这个地方比较难以理解的主要是下面几个参数
-
绘制到上下文的元素。允许任何的画布图像源,例如:
HTMLImageElement
、SVGImageElement
、HTMLVideoElement
、HTMLCanvasElement
、ImageBitmap
、OffscreenCanvas
或VideoFrame
。 -
- 需要绘制到目标上下文中的,源
image
的子矩形(裁剪)的左上角 X 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
- 需要绘制到目标上下文中的,源
-
- 需要绘制到目标上下文中的,源
image
的子矩形(裁剪)的左上角 Y 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
- 需要绘制到目标上下文中的,源
-
- 需要绘制到目标上下文中的,源
image
的子矩形(裁剪)的宽度。如果不指定,整个矩形(裁剪)从坐标的sx
和sy
开始,到image
的右下角结束。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
- 需要绘制到目标上下文中的,源
-
- 需要绘制到目标上下文中的,
image
的矩形(裁剪)选择框的高度。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
- 需要绘制到目标上下文中的,
-
- 源
image
的左上角在目标画布上 X 轴坐标。
- 源
-
- 源
image
的左上角在目标画布上 Y 轴坐标。
- 源
-
-
image
在目标画布上绘制的宽度。允许对绘制的图像进行缩放。如果不指定,在绘制时image
宽度不会缩放。注意,这个参数不包含在 3 参数语法中。
-
-
-
image
在目标画布上绘制的高度。允许对绘制的图像进行缩放。如果不指定,在绘制时image
高度不会缩放。注意,这个参数不包含在 3 参数语法中。
-