avatar

麦兜的小站

MDO.INK

  • 首页
  • 随笔
  • 知识库
  • 归档
  • 动态
  • 标签
  • 关于
Home 上传选择、拖拽、剪裁
文章

上传选择、拖拽、剪裁

Posted 2025-01-27 Updated 2025-01- 27
By power 已删除用户
26~33 min read

文章主要介绍了上传选择、拖拽、剪裁功能的实现。包括上传选择按钮效果调整,通过样式和代码实现点击上传;上传拖拽实现,需阻止浏览器默认行为;图片剪裁功能,先显示图片,再用 canvas 的 drawImage 进行剪裁,还对其参数做了详细说明。

关联问题: 如何优化剪裁效果 拖拽能支持多图吗 上传样式能更丰富吗

前言

平时我们上传文件、拖拽图片,基本上都是用 antd、elementUI 等库,非常方便,但这不代表我们不需要了解这些东西,实际上图片这些知识点也不是很多,了解总比不知道强

假设我们碰到一个场景,我们的页面效果很简单,就展示一些好看的图片,还支持上传,不需要使用那么庞大的库,客户就要快,此时我们没必要引用那么大的 antd、elementUI 等库,我们直接写一个又快效率又高,那么多号,引入三方库后,还要担心打包文件偏大客户嫌弃问题

下面就介绍上传按钮的自定义、拖拽、剪裁功能

上传选择按钮效果调整

我们先写一个 upload 的样式 css,实际可以使用图片,更适合我们的场景,这里就简写了

.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);

    console.log(fileList, files);
};

上传拖拽实现

图片拖拽实际上就是走的 onDrop 方法,通过这个方法,可以获取到拖拽进来的文件集合,.dataTransfer.files 可以获取到拖拽进来的文件

但是有一个问题,就是浏览器有一个默认行为,一些浏览器中,拖拽进来的图片默认会开启一个新的窗口打开图片,我们可以通过 preventDefault 方法阻止用户的默认行为,onDragEnter、onDragOver、onDragLeave、onDrop我们均阻止一下即可,否则仍然可能另起窗口打开图片

ps:onDragEnter拖拽进入时回调、 onDragOver拖拽进入持续触发、onDragLeave拖拽离开元素触发、onDrop在元素中松手触发,实际上以前我也写过一个 threejs 的浏览器拖拽到 3d 场景的组件库功能,实际上就用到了此类方法衔接拆分

<div
    className="upload"
    onDragEnter={(e) => {


        e.preventDefault();
    }}
    onDragOver={(e) => {


        e.preventDefault();
    }}
    onDragLeave={(e) => {


        e.preventDefault();
    }}
    onDrop={(e) => {


        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];

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;

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,其可以理解为,将原图片,将其按照一定裁剪方式,放到另一个空白的内容上,超出部分剪裁,其一共声明了三个方法

drawImage(image: CanvasImageSource, dx: number, dy: number): void;


drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;




drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;

具体参数更细致的如下所示,可以看参考自 MDN,实际上这个地方比较难以理解的主要是下面几个参数

  • image

  • 绘制到上下文的元素。允许任何的画布图像源,例如:HTMLImageElement、SVGImageElement、HTMLVideoElement、HTMLCanvasElement、ImageBitmap、OffscreenCanvas 或 VideoFrame。

  • sx 可选

    • 需要绘制到目标上下文中的,源 image 的子矩形(裁剪)的左上角 X 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
  • sy 可选

    • 需要绘制到目标上下文中的,源 image 的子矩形(裁剪)的左上角 Y 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
  • sWidth 可选

    • 需要绘制到目标上下文中的,源 image 的子矩形(裁剪)的宽度。如果不指定,整个矩形(裁剪)从坐标的 sx 和 sy 开始,到 image 的右下角结束。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
  • sHeight 可选

    • 需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
  • dx

    • 源 image 的左上角在目标画布上 X 轴坐标。
  • dy

    • 源 image 的左上角在目标画布上 Y 轴坐标。
  • dWidth

    • image 在目标画布上绘制的宽度。允许对绘制的图像进行缩放。如果不指定,在绘制时 image 宽度不会缩放。注意,这个参数不包含在 3 参数语法中。
  • dHeight

    • image 在目标画布上绘制的高度。允许对绘制的图像进行缩放。如果不指定,在绘制时 image 高度不会缩放。注意,这个参数不包含在 3 参数语法中。
前端
License:  CC BY 4.0
Share

Further Reading

Jun 25, 2025

Vue实现顶部导航跟随页面联动效果

需求背景 最近也是应我一位同学的要求,给他的公司制作一个官网,那也就是拾起了一些基础的知识,搜索了一些其它的文章有实现这种效果的,但大多导航都是在侧边,然后点击导航对应的部分内容滑到最上面,跟我这次设...

Jun 20, 2025

Share.js - 一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点

一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点等社交网站,使用字体图标。 有两种安装方式: 使用npm npmi...

Jun 19, 2025

node express实现热更新

在使用node进行开发的时候,每次修改文件,都需要重启express服务很麻烦 我们可以使用nodemon这个库,修改文件后可以自动重启express服务。 安装: npminstall--save-...

OLDER

nvm是一个nodejs版本管理工具

NEWER

Laravel 中使用 PHP 分词库 (jieba) 和 (scws)

Recently Updated

  • 如何实现接口幂等性
  • 10个npm工具包
  • How to set up PHP7.4 on MacOS.
  • Automa:一键自动化,网页数据采集与工作流程优化专家Automa:解锁自动化
  • Mac 下用 brew 搭建 LNMP

Trending Tags

thinkphp clippings

Contents

©2025 麦兜的小站. Some rights reserved.

Using the Halo theme Chirpy