Javascript:图片预览,压缩及上传

一、图片预览

图片预览有两种方式:

  1. FileReader把图片转化为base64格式的数据嵌入到HTML中。
  2. URL.createObjectURL()构造图片的URL并赋值给<img>标签。

1.

FileReader允许浏览器异步读取用户电脑上的文件,需要用File或者Blob指定要读取的文件。

FileReader有一个方法叫.readAsDataURL(),这个方法用来读取指定Blob或者File的内容,读取完成之后readyState会变成DONE,并且触发loadend事件。

读取完成之后,result属性包含了一个url,这个url是一个Data URLData URL以base64编码的形式存储文件的数据。

已知图片的src属性即可以接受图片的url,也可以接受一个Data URL,直接把编码后的图片信息内嵌到HTML中。

那么我们可以把这个Data URL赋值给img.src,这样就可以在页面上显示图片了。

代码如下:

<!-- upload.html -->
<div>
    <button type="button" id="button">点击添加图片</button>
    <input type="file" id="chooseFile"> <!-- hidden属性对其不起作用 -->
    <img src="" id="preview">
</div>
/* 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个部分组成:

  1. 前缀:data:
  2. mediatype;:一个MINE类型的声明,表示这串数据的编码类型,比如:image/png.如果缺省,默认为text/plain;charset=US-ASCII
  3. 可选的base64,:如果该数据是非文本类型的,那么要加上这个部分
  4. 数据本身

举个例子,这是一张图片的Data URL:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAASuCAYAAADI0RjrAAAMJGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdUk8kWnr8kISGhBSIgJfQ......

Data URLs的好处是:

  1. 我们可以把体积比较小的图片嵌入到HTML文件中,减少HTTP请求次数。
  2. 如果图片是在服务端用程序动态生成的,每个用户显示的都不同时,直接把图片的Data URL嵌入到网页中会很方便。
  3. 如果访问外部资源很麻烦或者受限时。

Data URLs的缺点是:

  1. base64的体积会比原数据体积大约 1/3
  2. 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字符串流
}

需要注意的是:

  1. canvas.toDataURL的质量参数只对image/jpegimage/webp有效。
  2. 如果图像本身是image/png,则 type 参数不能为非image/png的其他类型。
  3. 质量参数默认是0.92,质量参数太大反而会增加图片的体积。

三、 图片上传

假设我们没有压缩图片,那么直接使用FormData()上传图片就可以了。
现在假设我们已经压缩了图片,那么要怎么传图片呢?

可以直接把base64的数据传到后端再解码成图片。也可以在前端先解码再传到后端,因为base64数据的体积要比原数据的体积大 1/3 。

思路如下:

  1. .atob()方法对用base-64编码过的字符串进行解码,我们现在输出会得到一串二进制字符串。
  2. .charCodeAt()方法得到每一个字符的Unicode编码。
  3. new Uint8Array();把我们得到的Unicode编码转化为类型数组,类型数组拥有二进制支持,可以用来处理二进制文件。普通数组是无法生成二进制文件的。
  4. 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.html  -->
<body>

    <div id="imagesDiv">
        <input type="file" id="chooseFile">
    </div>

    <script src="../static/js/jquery-3.2.1.min.js"></script>

</body>
// 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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容