stone

前端实现图片的裁剪和上传
图片裁剪上传是一个非常常见的功能,如上传头像,上传的图片需要指定比例,理论上来说,这是一件比较简单的事,但是!!由...
扫描右侧二维码阅读全文
16
2016/12

前端实现图片的裁剪和上传

图片裁剪上传是一个非常常见的功能,如上传头像,上传的图片需要指定比例,理论上来说,这是一件比较简单的事,但是!!由于安全的原因,js是不能直接访问的本地的文件。

主要的难点在于图片的读取和裁剪后的图片转换为数据流进行上传,下面我们慢慢分析:👇

Blob,DataURL,Base64

在前端处理文件对象的时候,需要和这几个对象打交道,理清他们之间的关系有利于我们理解上传的原理:

Blob

Blob(Binary Large Object) 大型二进制对象,很多人在学习数据库的时候可能都已经接触过Blob进行,但是这里HTML5中的Blob对象和mysql中的还是好区别的,数据库中Blob只是一个二进制容器,而HTML5中的Blob带有一些文件名,文件类型相关的信息。

在表单中传送文件,发送的就是Blob对象。

下面是一个小实验,创建一个Blob对象,然后将它发送出去:

var html = "<h1>hello world</h1>";
var htmlBlob = new Blob([html],{type:"text/plain"});

// 创建表单
var formData = new FormData();
formData.append("name","test");
formData.append("file",htmlBlob);

// 创建ajax
var request = new XMLHttpRequest();
request.open("POST","http://localhost/test");
request.send(formData);

从图中可以看到表单的Content-Type多了一个边界的标识符,通过这个方法,就可以在表单中传输多个文件,那就有另外一个问题了,如果文件过大怎么办?Blob支持分割操作,每一个Blob对象都可以记录自己的偏移位置,在服务器中可以重新进行组装。

Base64

Base64是一种编码方式,可以将任意字节组转换为文本,这样就可以通过文本的方式进行传输,例如放在url中。这样生成的文本文件可以放在协议中而不用拍里面出现一些特殊的字符破坏协议的解析。Base64处理的时候将文件转换为二进制进行表示,然后以6位为一组将其映射到64个字符之一。

Base64在实际应用中有一些变形,因为像url中,会将+,-换成其他的字符进行表示。

DataURL

DataURL是url的其中一种类型,可以将二进制数据放进url中,实质上还是利用了Base64进行编码,只不过在其头部加了对数据类型的描述。通过这样的机制,我们可以将一些小的图片直接放在html中,那么浏览器就不用打开一个新的连接进行下载。当然,这个技术还有很多的用途。例如传送一些小图片,因为经过了编码,不易发觉其内容。

下面是网上找的一小段dataurl,大家可以将它放到url中进行测试。

data:image/gif;base64,R0lGODlhMwAxAIAAAAAAAP///
yH5BAAAAAAALAAAAAAzADEAAAK8jI+pBr0PowytzotTtbm/DTqQ6C3hGX
ElcraA9jIr66ozVpM3nseUvYP1UEHF0FUUHkNJxhLZfEJNvol06tzwrgd
LbXsFZYmSMPnHLB+zNJFbq15+SOf50+6rG7lKOjwV1ibGdhHYRVYVJ9Wn
k2HWtLdIWMSH9lfyODZoZTb4xdnpxQSEF9oyOWIqp6gaI9pI1Qo7BijbF
ZkoaAtEeiiLeKn72xM7vMZofJy8zJys2UxsCT3kO229LH1tXAAAOw==

浏览器中的文件读取

由于安全的原因,所有浏览不能直接访问本地的文件系统,必须通过浏览器开放的api访问一个虚拟的文件系统。

在HTML5中提供了FileReader接口由于读取本地文件。

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>Document</title>
</head>

<body>
    <input type="file" id="input-file" onchange="showInput()">
    <img id="pic" alt="" src="" />
</body>

<script>
    function showInput() {
        var file = document.getElementById("input-file").files[0];

        var fileReader = new FileReader();

        fileReader.readAsDataURL(file);
        fileReader.onload = function(e) {
            var img = document.getElementById("pic");
            img.setAttribute('src', this.result);
        }
    }
</script>

</html>

上面是一下端示范代码,读取本地的一个图片然后显示出来,理解了dataur之后就很好理解这段代码了。
FileReader采用异步读取的方式,所以需要注册一个onload函数,表示在读取图片之后进行的操作。
FileReader还提供了很多不同的事件可以进行绑定。

图片的裁剪和上传

下面就是这篇文章的重点了,现在我们理一下这个思路,我们需要先将图片读取显示出来,用户进行裁剪,然后就裁剪后的结果转为二进制流使用post进行上传。

图片的读取显示比较简单,利用FileReader很容易实现,问题是怎么获取用户裁剪后的数据,并转换为二进制流。我使用的是一个叫cropper的插件(其他基本大同小异),其中有一个方法叫做getCroppedCanvas,可以获取到一个Canvas对象。然后调用canvas对象的toDataURL将其转换为DataURL的png文件(可以指定其他的文件格式),有了这个DataURL后就是想办法将其转换为Blob对象,方法也很简单,就是对base64编码进行解码,然后转换回而进行编码。就可以得到一个图片文件了。

function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
            bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type: mime});
}

最后就是怎么将这个文件发送给服务器了。采用的HTML5提供的FormData对象,利用它可以创建表单,然后将其发送出去。

var formData = new FormData();
formData.append('file',file);   //file为Blob对象
......

$.ajax({
    url: "/test/",
    type: "POST",
    data: formData,
    processData: false,
    contentType: false
})

请求中需要带将processData和contentType这个两个选项设置为false,这样发送出去的post请求的格式就是multipart/formData类型了。

如果使用thinkphp的话可以在post或者$_File对象中找到这个文件对象。

小坑

input[type=file]的装饰

<label class="btn btn-primary" for="open-file">上传图片
    <input type="file" name="banner" accept="image/*" id="open-file" style="position:absolute;clip:rect(0,0,0,0)">
</label>

html中的for标签是将某一个标签和表单的元素进行绑定,这种绑定分为显示绑定和隐式绑定,像上面的例子中,点击label标签就可以出发input的click事件。

thinkphp中的$_File对象

上面的例子中,因为我们的DataURL产生的Blob文件是不带文件名的,而thinkphp判断文件类型,即ext的时候就是根据文件类型进行判断,所以如果不加处理的话,这个Blob文件是不能进行正常的上传的。这或许算是一个小bug吧。解决方法就是强制在介绍到的$_File强制添加一个文件名,让它可以判断出一个正确的文件类型。

Last modification:September 7th, 2018 at 08:22 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment