**最近做一個項目,手機端拍照,壓縮圖片,上傳到服務器,下面將會寫到用的技術和遇到的問題和解決辦法 … **
現在我們用手機上傳圖片到網上提交數據,但是可能圖片的文件比較大,像拍照拍出來的就有可能有2m,這個時候就需要把圖片壓縮進行處理。
一、圖片上傳前端壓縮的意義
- 對於大尺寸圖片的上傳,在前端進行壓縮可以省流量
- 上傳圖片尺寸較小,上傳速度會比較快,交互會更加流暢,同時大大降低了網絡異常導致上傳失敗風險。最大的意義提高了用戶體驗。
二、圖片壓縮(旋轉),上傳用到的技術
-
使用FileReader獲取圖片數據
HTML5定義了FileReader作爲文件API的重要成員用於讀取文件,根據W3C的定義,FileReader接口提供了讀取文件的方法和包含讀取結果的事件模型。
相關文章:
https://blog.csdn.net/zk437092645/article/details/8745647
https://www.cnblogs.com/hhhyaaon/p/5929492.html -
canvas
相關文章:
https://blog.csdn.net/xu_ya_fei/article/details/51568594
https://www.cnblogs.com/xingfuboke/p/5749170.html
http://www.w3school.com.cn/html5/canvas_drawimage.asp
- Exif.js ,Exif.js 提供了 JavaScript 讀取圖像的原始數據的功能擴展,例如:拍照方向、相機設備型號、拍攝時間、ISO 感光度、GPS 地理位置等數據。
相關文章:
http://code.ciaoca.com/javascript/exif-js/
- axios
三、遇到的問題
-
在ios手機上拍照壓縮之後的圖片,會出現逆時針旋轉90度的bug,Android手機不存在這樣的問題。
解決辦法:
(1). 獲取到照片拍攝的方向角,對非橫拍的IOS照片進行角度旋轉修正。
(2). 用exif.js讀取照片的拍攝信息,主要用到定位屬性Orientation。
(3). exif.js提供的一張照片的Orientation屬性如下:
旋轉角度 | 參數 |
---|---|
0° | 1 |
順時針90° | 6 |
逆時針90° | 8 |
180° | 3 |
四、bug的顯示
點擊文件選擇圖片就可以拍照,左側是蘋果手機上出現的問題,右側是正常的效果圖。
代碼展示
github https://github.com/lian-fei/phoneScan
<template >
<div class="root">
<!-- 點擊拍照 手機拍照,瀏覽器選擇照片,只能選擇一張-->
<h2>選擇圖片</h2>
<div>
<input type="file" accept="image/*" capture="camera" @change="imageFileChange">
</div>
<br>
<br>
<br>
<h3>壓縮之前的圖片</h3>
<img :src="compressFrondImage" alt="">
<h3>壓縮之後的圖片</h3>
<img :src="compressEndImage" alt="">
</div>
</template>
<script>
import EXIF from 'exif-js'
export default {
name: '',
data () {
return {
compressFrondImage: '', // 壓縮之前的圖片
compressEndImage: '' // 壓縮之後的圖片
}
},
components: {},
created () {},
mounted () {},
methods: {
/**
* 圖片文件發生變化
*/
imageFileChange (e) {
this.file = e.target.files[0]
console.log(this.file)
if (this.file) {
// FileReader
let reader = new FileReader()
reader.readAsDataURL(this.file)
reader.onload = (e) => {
console.log(e.target)
this.compressFrondImage = e.target.result
this.compressImages(this.compressFrondImage)
}
}
},
/**
* 壓縮圖片
*/
compressImages (res) {
let defaultImage = {
width: 1440,
height: 1080,
quality: 0.8, // 壓縮圖片的質量
orientation: '' // 獲取照片方向角屬性,用戶旋轉控制
}
var img = new Image()
img.src = res
let initSize = img.src.length
img.onload = () => {
// 方便手機測試
alert('壓縮之前寬度: ' + img.width)
alert('壓縮之前高度: ' + img.height)
// 方便瀏覽器測試
console.log('壓縮之前寬度: ' + img.width)
console.log('壓縮之前高度: ' + img.height)
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
if (img.width > defaultImage.width) {
img.height = img.height * (defaultImage.width / img.width)
img.width = defaultImage.width
}
if (img.height > defaultImage.height) {
img.width *= defaultImage.height / img.height
img.height = defaultImage.height
}
canvas.width = img.width
canvas.height = img.height
ctx.clearRect(0, 0, canvas.width, canvas.height)
EXIF.getData(this.file, () => { // IMG_FILE爲圖像數據
// 是否是iPhone手機,iPhone 拍照之後的壓縮是逆時針旋轉90,針對iphone做一下處理
if (navigator.userAgent.match(/iphone/i)) {
defaultImage.orientation = EXIF.getTag(this.file, 'Orientation')
// translate是平移變換,scale(-1,1)是向左翻轉,rotate是順時針旋轉。
// defaultImage.orientation = 6 // 測試iPhone手機
alert('Orientation:' + defaultImage.orientation) // 拍照方向
switch (Number(defaultImage.orientation)) {
case 2:
ctx.translate(img.width, 0)
ctx.scale(-1, 1)
ctx.drawImage(img, 0, 0, img.width, img.height)
break
case 3:
ctx.rotate(180 * Math.PI / 180)
ctx.drawImage(img, -img.width, -img.height, img.width, img.height)
break
case 4:
ctx.translate(img.width, 0)
ctx.scale(-1, 1)
ctx.rotate(180 * Math.PI / 180)
ctx.drawImage(img, -img.width, -img.height, img.width, img.height)
break
case 5:
ctx.translate(img.width, 0)
ctx.scale(-1, 1)
ctx.rotate(90 * Math.PI / 180)
ctx.drawImage(img, 0, -img.width, img.height, img.width)
break
case 6:
canvas.width = img.height
canvas.height = img.width
ctx.rotate(90 * Math.PI / 180)
ctx.drawImage(img, 0, 0, img.width, -img.height)
break
case 7:
ctx.translate(img.width, 0)
ctx.scale(-1, 1)
ctx.rotate(270 * Math.PI / 180)
ctx.drawImage(img, -img.height, 0, img.height, img.width)
break
case 8:
ctx.rotate(270 * Math.PI / 180)
ctx.drawImage(img, -img.height, 0, img.height, img.width)
break
default:
ctx.drawImage(img, 0, 0, img.width, img.height)
}
} else {
ctx.drawImage(img, 0, 0, img.width, img.height)
}
var imgUrl = canvas.toDataURL('image/jpeg', defaultImage.quality)
// 手機端測試
alert('壓縮率:' + ~~(100 * (initSize - imgUrl.length) / initSize) + '%')
alert('壓縮之後寬度: ' + img.width)
alert('壓縮之後高度: ' + img.height)
// 瀏覽器測試
console.log('壓縮前:' + initSize)
console.log('壓縮後:' + imgUrl.length)
console.log('壓縮率:' + ~~(100 * (initSize - imgUrl.length) / initSize) + '%')
console.log('壓縮之後寬度: ' + img.width)
console.log('壓縮之後高度: ' + img.height)
console.log('壓縮之後base64地址')
console.log(imgUrl)
// 壓縮之後的base64 圖片地址
this.compressEndImage = imgUrl
// TODO 上傳圖片文件
this.uploadImage()
})
}
},
/**
* 上傳圖片
*/
uploadImage: function () {
},
/**
* 瓦片壓縮 正在測試
*/
imagesCompress: function (img) {
// 用於壓縮圖片的canvas
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
// 瓦片canvas
var tCanvas = document.createElement('canvas')
var tctx = tCanvas.getContext('2d')
var initSize = img.src.length
var width = img.width
var height = img.height
// 如果圖片大於四百萬像素,計算壓縮比並將大小壓至400萬以下
var ratio
if ((ratio = width * height / 4000000)>1) {
ratio = Math.sqrt(ratio)
width /= ratio
height /= ratio
} else {
ratio = 1
}
canvas.width = width
canvas.height = height
// 鋪底色
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 如果圖片像素大於100萬則使用瓦片繪製
var count
if ((count = width * height / 1000000) > 1) {
count = ~~(Math.sqrt(count)+1) // 計算要分成多少塊瓦片
// 計算每塊瓦片的寬和高
var nw = ~~(width / count)
var nh = ~~(height / count)
tCanvas.width = nw
tCanvas.height = nh
for (var i = 0; i < count; i++) {
for (var j = 0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)
ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
}
}
} else {
ctx.drawImage(img, 0, 0, width, height)
}
// 進行最小壓縮
var ndata = canvas.toDataURL('image/jpeg', 0.8)
console.log('壓縮前:' + initSize)
console.log('壓縮後:' + ndata.length)
console.log('壓縮率:' + ~~(100 * (initSize - ndata.length) / initSize) + '%')
tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
return ndata
}
}
}
</script>
<style lang="less">
.root{
width:50%;
margin: 0 auto;
}
.root span {
font-size: 30px;
font-weight: bold;
color: rgb(245, 45, 144);
cursor: pointer;
&:hover {
color: rgb(221, 70, 39);
}
}
img{
width:100%;
}
</style>