Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >玩转前端图片上传

玩转前端图片上传

作者头像
JowayYoung
发布于 2020-04-01 08:57:16
发布于 2020-04-01 08:57:16
3.1K00
代码可运行
举报
文章被收录于专栏:JowayYoung谈前端JowayYoung谈前端
运行总次数:0
代码可运行

本文讲的图片上传,主要是针对上传头像的。大家都知道,上传头像一般都会分成以下 4 个步骤:

选择图片 -> 预览图片 -> 裁剪图片 -> 上传图片

接下来,就详细的介绍每个步骤具体实现。

选择图片

选择图片有什么好讲的呢?不就一个 input[type=file] ,然后点击就可以了吗?确实是这样的,但是,我们想要做得更加的友好一些,比如需要过滤掉非图片文件, 或只允许从摄像头拍照获取图片等,还是需要进行一些简单配置的。

下面就先来看看最简单的选择图片:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" />

这时候,点击这个 input , 在 iOS 手机的显示如下:

img

其中的 “浏览” 选项,可以查看到非图片类型的文件,这并不是我们想要的结果,毕竟我们只想要图片类型。可以通过 accept 属性来实现,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" accept="image/*">

这样就可以过滤掉非图片类型了。但是图片的类型可能也太多了, 有些可能服务器不支持,所以,如果想保守一些,只允许 jpgpng 类型,可以写成这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" accept="image/jpg, image/jpeg, image/png">

或:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" accept=".jpg, .jpeg, .png">

OK, 过滤非图片的需求搞定了。但是有时候 ,产品还要求只能从摄像头采集图片,比如需要上传证件照,防止从网上随便找别人的证件上传,那capture 属性就可以派上用场了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" accept="image/*" capture>

这时候,就不能从文件系统中选择照片了,只能从摄像头采集。到了这一步,可能觉得很完美了,但是还有个问题,可能有些变态产品要求默认打开前置摄像头采集图片,比如就是想要你的自拍照片。capture 默认调用的是后置摄像头。默认启用前置摄像头可以设置 capture="user" ,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input type="file" accept="image/*" capture="user">

好啦,关于选择图片的就讲么这么多了,有个注意的地方是,可能有些配置在兼容性上会有一些问题,所以需要在不同的机型上测试一下看看效果。

下面再来谈谈预览图片的实现。

预览图片

在远古时代,前端并没有预览图片的方法。当时的做法时,用户选择图片之后,立刻把图片上传到服务器,然后服务器返回远程图片的 url 给前端显示。这种方法略显麻烦,而且会浪费用户的流量,因为用户可能还没有确定要上传,你却已经上传了。幸好,远古时代已经离我们远去了,现代浏览器已经实现了前端预览图片的功能。常用的方法有两个,分别是 URL.createObjectURL()FileReader 。虽然他们目前均处在 w3c 规范中的 Working Draft 阶段, 但是大多数的现代浏览器都已经良好的支持了。下面就介绍一下如何使用这两个方法。

1. 使用 URL.createObjectURL 预览

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。用法用下:

objectURL = URL.createObjectURL(object);

其中,object 参数指 用于创建 URL 的 File 对象、Blob 对象或者 MediaSource 对象。

对于我们的 input[type=file] 而言, input.files[0] 可以获取到当前选中文件的 File 对象。示例代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="inputFile" type="file" accept="image/*">
<img src="" id="previewImage" alt="图片预览">
<script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change', function() {
        const file = this.files[0];
        $previewImage.src = file ? URL.createObjectURL(file) : '';
    }, this);
</script>

具体用法可以参考 MDN上的 URL.createObjectURL(),

2. 使用 FileReader 预览

FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。同理的,我们也可以通过 input.files[0] 获取到当前选中的图片的 File 对象。

特别注意,FileReader 和 是异步读取文件或数据的!

下面是使用 FileReader 预览图片的示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="inputFile" type="file" accept="image/*">
<img src="" id="previewImage" alt="图片预览">
<script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change', function() {
        const file = this.files[0];
        const reader = new FileReader();
        reader.addEventListener('load', function() {
            $previewImage.src = reader.result;
        }, false);
        if (file) {
            reader.readAsDataURL(file);
        }
    }, false)
</script>

会发现, FileReader 会相对复杂一些.

更多关于 FileReader 的用法 ,可以参考 MDN 文档 FileReader

两种方法的对比

我个人更加倾向于使用 URL.createObjectURL() 。主要原先它的 API 简洁,同步读取,并且他返回的是一个 URL ,比 FileReaer 返回的base64 更加精简。兼容性上,两者都差不多,都是在 WD 的阶段。性能上的对比, 在 chrome 上, 选择了一张 2M 的图片, URL.createObjectURL() 用时是 0 , 而 FileReader 用时 20ms 左右。0 感觉不太合理,虽然这个方法立刻就会返回一个 URL ,但是我猜测实际上这个 URL 指定的内容还没有生成好,应该是异步生成的,然后才渲染出来的。所以并没有很好的办法来对比他们的性能。

如果想要学习更多关于图片预览,可以阅读以下两篇文章:

  • 使用FileReader实现前端图片预览
  • js图片/视频预览 - URL.createObjectURL()

裁剪图片

关于图片的裁剪,很自然的会想到使用 canvas ,确实是要通过 canvas, 但是如果全部我们自己来实现,可能需要做比较多的工作,所以为了省力,我们可以站在巨人的肩膀上。比较优秀的图片裁剪库是 cropperjs , 该库可以对图片进行缩放、移动和旋转。

cropperjs 的详细配置这里就不展开了 ,需要的可以自己去看文档就好。下面我们就以这个库为基础,实现一个裁剪人脸的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input id="inputFile" type="file" accept="image/*">
<img class="preview-image" id="previewImage" src="" alt="">
<!-- cropper裁剪框 -->
<div class="cropper" id="cropper">
    <div class="inner">
        <div class="face-container">
            <img class="cropper-image" id="cropperImage">
        </div>
        <div class="tips">请将面部区域置于人脸框架内</div>
        <div class="toolbar">
            <div class="btn" id="confirm">确认</div>
        </div>
    </div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>

<style>
    .preview-image,
    .cropper-image {
        max-width: 100%;
    }
    .cropper {
        display: none;
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background: #ccc;
        font-size: 0.27rem;
        text-align: center;
    }
    .inner {
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
    .face-container {
        position: relative;
        width: 320px;
        height: 320px;
        margin: 50px auto;
    }
    .cropper-modal {
        background: url('https://ok.166.net/gameyw-misc/opd/squash/20191028/152551-m37snfsyu1.png') center no-repeat;
        background-size: 100% 100%;
        opacity: 1;
    }
    .cropper-bg {
        background: none;
    }
    .cropper-view-box {
        opacity: 0;
    }
    .tips {
        font-size: 16px;
    }
    .toolbar {
        display: flex;
        justify-content: center;
        margin: 50px 0;
    }
    .btn {
        width: 150px;
        line-height: 40px;
        font-size: 20px;
        text-align: center;
        color: #fff;
        background: #007fff;
    }
</style>

<script>
    const $ = document.getElementById.bind(document);
    const $cropper = $('cropper');
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    const $cropperImage = $('cropperImage');
    const $confirmBtn = $('confirm')
    let cropperInstance = null;
    // 选择图片后,显示图片裁剪框
    $inputFile.addEventListener('change', function() {
        const file = this.files[0];
        if (!file) return;
        $cropperImage.src = URL.createObjectURL(file);
        showCropper();
    }, false);
    // 点击确认按钮,将裁剪好的图片放到 img 标签显示。
    $confirmBtn.addEventListener('click', function() {
        const url = cropperInstance.getCroppedCanvas().toDataURL("image/jpeg", 1.0);
        $cropper.style.display = 'none';
        $previewImage.src = url;
    }, false);
    function showCropper() {
        $cropper.style.display = 'block';
        cropperInstance && cropperInstance.destroy();
        cropperInstance = new Cropper($cropperImage, {
            viewMode: 1,
            aspectRatio: 1,
            autoCropArea: 1,
            dragMode: 'move',
            guides: false,
            highlight: false,
            cropBoxMovable: false,
            cropBoxResizable: false
        });
    }
</script>

效果图如下:

img

上传

前面的操作已经完成了图片上传前的准备,包括选择图片、预览图片、编辑图片等,那接下来就可以上传图片了。上面的例子中,使用了 cropperInstance.getCroppedCanvas() 方法来获取到对应的 canvas 对象 。有了 canvas 对象就好办了,因为 canvas.toBlob() 方法可以取得相应的 Blob 对象,然后,我们就可以把这个 Blob 对象添加到 FromData 进行无刷新的提交了。大概的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function uploadFile() {
    cropperInstance.getCroppedCanvas().toBlob(function(blob) {
        const formData = new FormData();
        formData.append('avatar', blob);
        fetch('xxxx', {
            method: 'POST',
            body: formData
        });
    });
}

这段代码并不能真正执行,因为我们还没有对应的后端服务器。如果想要尝试上传图片的朋友,可以参考一下这篇文章 写给新手前端的各种文件上传攻略,从小图片到大文件断点续传,由于篇幅原因,这里就不展开啦。

后记

关于图片上传的介绍,差不多不到些结束了。但是之前在 iPhone 和 小米 手机上,遇到一个奇怪的问题:就是我使用前置摄像头自拍出来的照片,选择之后 ,会自逆时针旋转 90 度,比如像下图:

img

拍照的时候明明就是正着拍的,为什么预览就会变成横着了呢?当时第一次遇到这个问题的时候,也觉得好奇怪。后来查了一下,得知这是因为拍照时,相机都会记录拍照的角度信息,可能 iPhone 前置摄像头记录的角度信息和其他的有点不一样,而 iPhone 自己的相册在浏览照片时,自动纠正了角度 ,而浏览器却没有纠正,所以才会出现这个旋转。

为了解决这个问题,需要使用 EXIF 这个库来处理。

我刚刚试了一下,发现我的 iPhone 现在竟然不会有这个问题了,大概是半年前,当时在做一个需求时,自拍的图片会发生这种旋转,有可能是 iOS 系统升级后, 已经修复了这个问题。而现在身边又没有小米手机, 所以也不好复现。还好,当时我保存了一张会自动旋转的图片。大家可以到这里下载:

https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png

这图片下载后,用电脑的图片查看器打开是正常的,但是,在浏览器中,选择这个图片后,使用 URL.createObjectURL()FileReader 来预览就会发生旋转。甚至直接 img 标签引入也会逆时针旋转了 90 度,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<img src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png">

效果如下:

img

下面就以这张图片为例,介绍一下如何使用 EXIF 来检测图片角度。关于 EXIF 的详细用法大家可以到 github 的主页上查看 https://github.com/exif-js/exif-js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<img id="exifImage" src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png" alt="">

<script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script>
<script>
const $exifImage = document.getElementById('exifImage');
$exifImage.onload = function() {
    EXIF.getData($exifImage, function() {
        let allMetaData = EXIF.getAllTags(this);
        console.log(allMetaData.Orientation); // 6
    });
};
</script>

上面代码的输出 allMetaData.Orientation 的结果为 6 , 那 6 到底是什么意思呢?可以参考这个篇文章 http://sylvana.net/jpegcrop/exif_orientation.html 里面有个表格:

img

如果这个表格看不太懂,再参考一下这篇文章 JPEG Orientation,里有个图:

img

可以看出,摄像头信息是逆时针旋转了 90 度。那要怎么纠正呢?可以使用 CSStransfrom: rotate(-90deg) 顺时针旋转 90 度抵消掉这个角度就好。

事实上, CropperJS 也会检测图片的 EXIF 信息,并且会自动纠正角度的,详情参考 https://github.com/fengyuanchen/cropperjs#checkorientation这里也提到了,但只支持读取 jpg 图片的 EXIF 信息,而我们这张图片是 PNG 所以并不支持。

有个 CSS 属性叫做 image-orientation , 它有个值叫做 from-image , 就是使用图片的 EXIF 数据来旋转的。可惜,目前 chrome 不支持该属性。有兴趣的可以了解一下。

好啦,就先写到这里啦,有问题的欢迎在评论区交流哈~

PS: 已经有大半年没有更新过文章了,先定个小目标,以后每两个星期更新一篇。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IQ前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用Debian11 live cd格盘重装Debian
继上一篇文章《更新Debian 11并给根分区扩容》中我的评论所说的那样,dns解析服务十分不稳定,日常无响应,于是我使用之前用来给根分区扩容的那个Debian11 live cd把Debian彻底的格盘重装了一次,现以此文章记录过程,另外,这篇文章中也有相当多的图片,实测这篇文章完整加载需要369MB的流量
冰漪叶
2022/10/24
2K0
使用Debian11 live cd格盘重装Debian
UEFI安装win10+manjaro双系统
微软官方 Windows 10 ISO 直接下载网页:https://www.microsoft.com/zh-cn/software-download/windows10ISO
全栈程序员站长
2022/09/07
4.5K0
UEFI安装win10+manjaro双系统
华硕怎么安装linux系统教程,华硕笔记本系统如何安装win10和linux 双系统[通俗易懂]
稍微了整理了一下win10和linux双系统的安装教程,第一个选项是进入U盘linux live,等等) 第一点设置boot挂载点。
全栈程序员站长
2022/09/15
6.2K0
轻松安装 Debian 桌面系统的方法
你需要为 Debian 准备什么样配置的机器?这取决于你想用什么类型的 桌面环境。例如,GNOME 桌面系统可以在 4GB 内存上运行,但在 8GB 内存上更流畅一些。如果你只有 4GB 或更少的内存,还是建议尝试 KDE、Cinnamon 或 Xfce 桌面系统。
用户4988085
2021/09/17
6.1K0
win10中安装centos7双系统
开机按F2,进入bios,在boot项中,boot mode为UEFI,则为uefi启动方式。
全栈程序员站长
2022/09/15
2.5K0
win10中安装centos7双系统
入门学习SLAM(Windows &Ubuntu 16.04 双系统安装图片教程)
入门学习SLAM计划是一个系列,从开始记录大家的学习过程,每一步我们都是有规划的。前一段时间发现了一篇安装Windows + Ubuntu 16.04 双系统安装详细教程 ,然后放在公众号上供大家参考,也是学习SLAM的第一步了。
小白学视觉
2019/10/24
1.9K0
windows系统下安装linux(ubuntu)双系统
windows系统下安装linux(ubuntu)双系统 原创程序员爱酸奶(QuellanAn) 最后发布于2018-03-09 13:41:13 阅读数 24907 收藏 展开 最近在找工作,很多面试要求上都写着熟悉linux系统,擅长shell编程,虽然在学校学过操作系统的课程,但是到现在出来工作,学的课程忘得也差不多了,并且那些linux命令不经常使用的话,还是很容易忘记的,以前也有一段时间想学学linux,但是一直没有狠下心来,总是在网站(实验楼https://www.shiyanlou.com/)上学习,但是那种其实很难坚持下来,在window系统上装过虚拟机,然后再虚拟机上装的ubuntu,但是相当于一个软件使用,使用起来是相当的卡,体验感太差,终于下定决心装一个linux系统了,不过感觉还是离不开windows系统,毕竟做java开发从学校到现在都是用window系统,怕一下子换了有点适应不过来,所以就装了一个双系统。 好了,废话不过说,直接开始,首先需要准备的东西: U盘(容量>8G)、UltraISO刻录软件、Ubuntu 镜像文件 1.U盘是做启动盘的,就像用老毛桃或大白菜把U盘制作成启动盘装window系统一样。最好3.0端口,比较快。 2.在电脑上下一个UltraISO软件,可以百度UltraISO(https://cn.ultraiso.net/xiazai.html 免费下载试用就可以了)第一个就可以。 3.ubuntu镜像文件,这个可以在官网上下载,我用的是ubuntu 15.10.你们可以选择自己想要的镜像文件就行网址:http://old-releases.ubuntu.com/releases/ (希望对你们有用)。
Python之道
2020/04/09
12.7K0
U盘pe(理论大白菜、优启通、微PE都可以) 装ESXI方案 (非通用UltraISO重做启动U盘),省U盘「建议收藏」
此文是我发的一篇的准备工作,因为ESXi 6.7刚发布的原因,很多同学等着升级,故而先写了出来。原文如下:
全栈程序员站长
2022/10/03
8.3K0
U盘pe(理论大白菜、优启通、微PE都可以) 装ESXI方案 (非通用UltraISO重做启动U盘),省U盘「建议收藏」
深度操作系统Deepin的安装
目前新买品牌机大都已预装 Windows 10 系统,且是 UEFI 模式启动。因此内置硬盘也肯定是 GPT 格式,先腾出适当硬盘空间,
简单并不简单
2019/08/09
11.8K0
深度操作系统Deepin的安装
Windows10+Ubuntu双系统安装
最近因为毕设重新回归Ubuntu,手头有一台装了Win10的ThinkPad X240s,最终成功完成了Windows 10 教育版和Ubuntu Kylin 15.10 的双系统配置,下文(多图慎入)是我完成整个过程的手记。 安装方式 Ubuntu是很多Linux初学者最理想的选择,如果你恰好对Windows系列审美疲劳或者累觉不爱,那就要听好,有三种方法助你走进Ubuntu新世界。 虚拟机安装 原料:Ubuntu Kylin的ISO、VMware或VirtualBox 优点:一条龙服务,安全简单 缺
小小科
2018/05/02
4.4K0
Windows10+Ubuntu双系统安装
安装win10+黑苹果双系统零基础教程
镜像链接迅雷资源https://mirrors.dtops.cc/iso/MacOS/daliansky_macos/macOS%20Catalina%2010.15.4%2819E287%29%20Installer%20for%20Clover%205109%20and%20WEPE%20Support%20UEFI%20and%20MBR.dmg
全栈程序员站长
2022/08/12
2.9K0
安装win10+黑苹果双系统零基础教程
win10下安装Ubuntu16.04双系统「建议收藏」
我们首先去Ubuntu的官网下载一个Ubuntu16.04的iso镜像文件。当然里面也有优麒麟,其实就是把Ubuntu16.04汉化了一下,个人推荐安装Ubuntu16.04 体验上可能好一些。
全栈程序员站长
2022/09/15
1.4K0
win10下安装Ubuntu16.04双系统「建议收藏」
手把手教你安装win10+Ubuntu16.04的双系统(全网最详细)
本系列为小白入门整个AI项目教程,主要涉及双系统的搭建,linux的使用,安装caffe-gpu版本,利用caffe实现目标检测,并移植模型到android移动端,也就是手机端进行目标检测,本篇为安装双系统的教程。
小小詹同学
2019/05/15
26.9K1
手把手教你安装win10+Ubuntu16.04的双系统(全网最详细)
Ubuntu16.04安装ros_u盘安装双系统
下载Ubuntu16.04 我们首先去Ubuntu官网下一个Ubuntu16.04的iso镜像文件。
全栈程序员站长
2022/10/02
1.7K0
Ubuntu16.04安装ros_u盘安装双系统
Window和Linux双系统安装历程
然后这单独分出来的 20g 就作为一个新的未分配的分区,到时就可以给 Linux 用。
星哥玩云
2022/07/18
8.3K0
Window和Linux双系统安装历程
OPNSense 构建企业级防火墙--安装使用(一)
OPNsense是一个开源,易于使用且易于构建的基于FreeBSD的防火墙和路由平台,OPNsense包括昂贵的商业防火墙中提供的大多数功能。它带来了丰富的商业产品功能集,具有开放和可验证来源的优势。OPNsense同时具有完善的GUI管理界面,基本上脱离命令行完全使用GUI配置所有功能,OPNsense GUI 使用了伟大的 Bootstrap framework。
Kevin song
2020/02/26
11.3K0
OPNSense 构建企业级防火墙--安装使用(一)
Linux笔记(二): WIN 10 Ubuntu 双系统
(一)  说明 记录一次ubuntu安装过程及遇到的问题。 环境:WIN 10 单硬盘 (二)  ubuntu ISO文件下载 ubuntu 18.04 https://www.ubuntu.com/
free赖权华
2018/07/04
6.7K1
如何安装WINDOWS系统(三)
首先打开DG硬盘分区,然后就能看到所有的磁盘信息了,讲这个是为了有些朋友想把硬盘数据都清空,然后再重装系统的。
简单并不简单
2019/07/12
1.7K0
如何安装WINDOWS系统(三)
win7 + manjaro linux 双系统安装
这里统一采用efi引导,因此windows系统必须win7 64 或更新系统。 1、在别的电脑选择一个PE系统写入U盘,比如大白菜,真的大白菜。 2、PE系统中,将磁盘置为GPT格式(GUID) 3、PE系统,使用镜像加载工具加载win7 64 iso,点击setup.exe开始安装windows 7 系统。 选择磁盘构建,会自动生成efi分区,esp分区。(这是uefi引导的关键) 选择一个主分区进行安装。 4、下载Manjaro linux的iso。 5、安装win7完成后,使用工具Win32 Disk
斯武丶风晴
2018/03/29
10.6K0
3.0 Windows和Linux双系统安装(3)
版权声明:本文为王小雷原创文章,未经博主允许不得转载 https://blog.csdn.net/dream_an/article/details/50099809
王小雷
2019/05/27
8.1K0
推荐阅读
相关推荐
使用Debian11 live cd格盘重装Debian
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验