一、图片预览
图片预览有两种方式:
- 用
FileReader
把图片转化为base64格式的数据嵌入到HTML中。 - 用
URL.createObjectURL()
构造图片的URL并赋值给标签。
1.
FileReader
允许浏览器异步读取用户电脑上的文件,需要用File或者Blob指定要读取的文件。
FileReader
有一个方法叫.readAsDataURL()
,这个方法用来读取指定Blob或者File的内容,读取完成之后readyState
会变成DONE
,并且触发loadend
事件。
读取完成之后,result属性包含了一个url,这个url是一个Data URL
,Data URL
以base64编码的形式存储文件的数据。
已知图片的src属性即可以接受图片的url,也可以接受一个Data URL,直接把编码后的图片信息内嵌到HTML中。
那么我们可以把这个Data URL
赋值给img.src
,这样就可以在页面上显示图片了。
代码如下:
/* index.css */
/* 隐藏input框,因为太丑了,令人疲惫,这样我们就可以定义自己美美的输入框了 */
input {
display: none;
}
// index.js
var log = console.log.bind(console)
$('#button').on('click', function () { // 点击按钮触发input框的click事件
$('#chooseFile').click()
})
$('#chooseFile').on('change', function () {
var file = this.files[0]
// log(file)
var reader = new FileReader()
reader.onload = function (e) {
// log(e.target)
$('#preview').attr('src', e.target.result) // 把图片的base64编码形式赋值给img.src
}
reader.readAsDataURL(file)
})
关于Data URLs
Data URLs由4个部分组成:
- 前缀:
data:
-
mediatype;
:一个MINE类型的声明,表示这串数据的编码类型,比如:image/png.如果缺省,默认为text/plain;charset=US-ASCII - 可选的
base64,
:如果该数据是非文本类型的,那么要加上这个部分 - 数据本身
举个例子,这是一张图片的Data URL:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAASuCAYAAADI0RjrAAAMJGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdUk8kWnr8kISGhBSIgJfQ......
Data URLs的好处是:
- 我们可以把体积比较小的图片嵌入到HTML文件中,减少HTTP请求次数。
- 如果图片是在服务端用程序动态生成的,每个用户显示的都不同时,直接把图片的Data URL嵌入到网页中会很方便。
- 如果访问外部资源很麻烦或者受限时。
Data URLs的缺点是:
- base64的体积会比原数据体积大约 1/3
- Data URL形式的图片不会被浏览器缓存,这意味着每次访问页面都要加载一次图片。这种情况可以通过CSS文件把Data URL设成背景图片的url来避免。
比如:
div { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAASuCAYAAADI0RjrAAAMJGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdUk8kWnr8kISGhBSIgJfQ......")
关于base64:
Base64编码原理与应用
阮一峰的网络日志——Base64笔记
2.
URL
接口是一个包含若干静态方法的对象。
URL用于解析,构建,规范化和编码URLs。
URL接口可以在Web Worker中使用。
当使用一个没有实现该构造器的用户代理时,可以通过 Window.URL
属性来访问该对象(基于 Webkit 和 Blink 内核的浏览器均可用 Window.webkitURL
代替)。
URL = URL || window.URL || window.webkitURL
URL有一个.createObjectURL()
方法,该方法接收一个本地的blob作为参数,创建一个指向该blob的url,在几乎任何可以使用url的地方都可以使用.createObjectURL()
方法创建的url。
这意味着,我们可以使用它来实现图片的预览:
// index.js
$('#chooseFile').on('change', function () {
var file = this.files[0]
var url = URL.createObjectURL(file) // 创建一个指向选择的图片的url
// log(url)
$('#preview').attr('src', url) // 将url赋值给img.src
})
.createObjectURL()
创建的路径的格式是这样的:blob:null/元素本身组成的路径
。
比如:blob:null/8629b731-05a1-4b74-8a5a-b87878eba849
null表示域名为空。
URL.createObjectURL()
的性能要比使用FileReader高很多,如果只是单纯想要预览图片,那么使用URL.createObjectURL()
比较好。
二、图片压缩
canvas 有一个 toDataURL(type, quantity)
方法,就是把canvas的数据输出为一个Data URL,该方法可以设置图片质量。利用这一特性,就可以用来压缩图片。
function compress(img) { // img为图片的Data URL
var canvas = document.createElement('canvas') // 创建一个canvas
var context = canvas.getContext('2d')
var width = img.width
var height = img.height
canvas.width = width // 把canvas的宽高设置为图片的宽高
canvas.height = height
// 开始画图
context.fillStyle = '#fff'
context.fillRect(0, 0, canvas.width, canvas.height)
context.drawImage(img, 0, 0, canvas.width, canvas.height)
var base64Data = canvas.toDataURL('image/jpeg', 0.4) // 压缩图片,质量参数太多反而会加大图片体积!
canvas = context = null
// log(base64Data.length)
img = null
return base64Data // 返回压缩后的base64字符串流
}
需要注意的是:
-
canvas.toDataURL
的质量参数只对image/jpeg
或image/webp
有效。 - 如果图像本身是
image/png
,则 type 参数不能为非image/png
的其他类型。 - 质量参数默认是0.92,质量参数太大反而会增加图片的体积。
三、 图片上传
假设我们没有压缩图片,那么直接使用FormData()
上传图片就可以了。
现在假设我们已经压缩了图片,那么要怎么传图片呢?
可以直接把base64的数据传到后端再解码成图片。也可以在前端先解码再传到后端,因为base64数据的体积要比原数据的体积大 1/3 。
思路如下:
- 用
.atob()
方法对用base-64编码过的字符串进行解码,我们现在输出会得到一串二进制字符串。 - 用
.charCodeAt()
方法得到每一个字符的Unicode编码。 - 用
new Uint8Array();
把我们得到的Unicode编码转化为类型数组,类型数组拥有二进制支持,可以用来处理二进制文件。普通数组是无法生成二进制文件的。 - 用
Blob()
构造函数构造一个文件。
代码如下:
// 注意b64Data必须把前面的类似于“data:image/png;base64,"的字串截掉
// contentType指图片类型,比如:image/png, image/jpeg 等等
function b64ToBlob(b64Data, contentType) {
contentType = contentType || ''
var byteCharacters = atob(b64Data) // 解码base64数据为二进制字符串
var buffer = [] // 注意,Blob第一个参数必须是一个数组
// 类型数组用来处理二进制文件
var aBuffer = new ArrayBuffer(byteCharacters.length)
var uBuffer = new Uint8Array(aBuffer)
for (var i = 0; i < byteCharacters.length; i++) {
uBuffer[i] = byteCharacters.charCodeAt(i) // 得到Unicode编码,存进类型数组
}
buffer.push(uBuffer)
var blob = new Blob(buffer, { // 生成一个二进制文件
type: contentType
})
// log(blob)
return blob
}
现在已经完成了图片的预览,压缩,以及压缩后的转码。
之后就是ajax上传的问题了,先构建一个FormData,把图片append进去,传到后台。最后的完整实现:
// index.js
var log = console.log.bind(console)
// 上传图片
$('#chooseFile').on('change', function () {
var file = this.files[0]
if (!file.type.match('image.*')) { // 不是图片则返回
alert('只能上传图片');
return false;
}
var reader = new FileReader()
reader.onload = function () {
var img = new Image()
var originData = this.result
img.onload = function () {
var compressUrl = compress(img) // 压缩图片
var blob = b64ToBlob(compressUrl.split(',')[1], 'image/jpeg') // 压缩成jpeg格式
var form_data = new FormData()
form_data.append('file', blob)
$.ajax({
type: 'POST',
url: '你的url',
data: form_data,
processData: false, // 必须
contentType: false, // 必须
success: function (data, status, xhr) {
console.log(data)
},
error: function (error) {
console.log(error)
}
})
}
img.src = originData // 把图片数据赋值给img
}
reader.readAsDataURL(file) // 读取图片信息
})
// 压缩图片
function compress(img) {
var canvas = document.createElement('canvas')
var context = canvas.getContext('2d')
var width = img.width
var height = img.height
// log(img.width, img.height)
// log(width, height)
canvas.width = width
canvas.height = height
context.fillStyle = '#fff'
context.fillRect(0, 0, canvas.width, canvas.height)
context.drawImage(img, 0, 0, canvas.width, canvas.height)
var base64Data = canvas.toDataURL('image/jpeg', 0.4)
canvas = context = null
// log(base64Data.length)
img = null
return base64Data
}
// base64转化为二进制文件
function b64ToBlob(b64Data, contentType) {
contentType = contentType || ''
var byteCharacters = atob(b64Data) // 解码base64数据为二进制字符串
var buffer = [] // 注意,Blob第一个参数必须是一个数组
// 类型数组用来处理二进制文件
var aBuffer = new ArrayBuffer(byteCharacters.length)
var uBuffer = new Uint8Array(aBuffer)
for (var i = 0; i < byteCharacters.length; i++) {
uBuffer[i] = byteCharacters.charCodeAt(i) // 得到Unicode编码,存进类型数组
}
buffer.push(uBuffer)
// 普通数组是无法生成二进制文件的
var blob = new Blob(buffer, { // 生成一个二进制文件
type: contentType
})
// log(blob)
return blob
}