最近在HTML5項目中用到了上傳圖片功能,如今有HTML5前端網頁也能訪問手機相冊或照相機,可是手機拍下來的圖片動不動就好幾M,這樣上傳幾張圖片上去,用戶要罵街了,能不能在客戶端壓縮好圖片後再上傳了,節約用戶的流量。那麼前端如何壓縮圖片了?
實現圖片壓縮咱們須要用到canvas。 好了開幹吧!html
以下代碼使用React編寫前端
<input type="file" multiple accept="image/*"/>
複製代碼
點擊如上input file能夠調起一個彈窗,讓用戶從相機或相冊中獲取圖片。
默認的input file樣式太醜,看着太彆扭,咱們先來把樣式美化下吧。這裏推薦兩種方法,react
<input id="uploadFile" type="file" multiple accept="image/*"/>
<div className="btn-upload"> <label htmlFor="uploadFile">上傳圖片</label> </div>
複製代碼
這種方法是把input[file]的opacity設爲0,label的for屬性設置爲input[file]的ID,這樣點擊label就是點擊input file是同樣的效果,而input file咱們徹底看不見。ios
<input ref="leftFile" type="file" multiple accept="image/*" style={{display: 'none'}}/>
<a className="btn" onClick={()=>{ this.refs.leftFile.click() }}>Browse</a>
複製代碼
這種方法把input display設爲none,點擊其餘元素觸發input的click事件。git
render() {
const {
prefixCls, className, children, style
} = this.props
const cls = classNames({
[prefixCls]: true,
[className]: className
})
return (
<div className={cls} style={style} onClick={() => { this.refs.inputFile.click() }}> <input ref="inputFile" type="file" multiple accept="image/*" onChange={this.onChange}/> { children } {/*自定義選取圖片按鈕樣式*/} </div> ) } 複製代碼
無論文件域是用何種方式打開的,均可以在 change 事件中獲取到選擇的文件或拍攝的照片github
onChange = async (e) => {
const { onChange, compress } = this.props
let files = e.target.files
const len = files.length
let newFiles = []
for(let i = 0; i < len; i++) {
const result = await this.fileReader(files[i])
const data = await this.compress(result)
newFiles.push(data)
}
}
複製代碼
經過onChange獲取到圖片後須要建立一個FileReader對象,咱們須要調用readAsDataURL把文件轉換爲base64圖像編碼,如data:image/jpeg;base64……這種格式。讓fileReader讀取文件完畢後會觸發onload方法,在onload方法中咱們能夠經過e.target.result來獲取到base64的編碼,若是讀取失敗,該值爲null。onload是個異步方法,咱們能夠經過Promise和async,await來把異步變爲同步,不至於嵌套太多callback。web
// 讀取文件並轉化爲base64
fileReader = (file) => {
return new Promise(function (resolve, reject) {
const reader = new FileReader();
reader.onload = (e) => {
resolve(e.target.result)
};
reader.onerror = reject
reader.readAsDataURL(file);
})
}
複製代碼
compress = (res) => {
return new Promise(function (resolve, reject) {
const img = new Image()
img.onload = function() {
//壓縮圖片
resolve(blob) // 返回處理結果
}
img.onerror = function() {
reject(new Error('圖片加載失敗'))
}
img.src = res;
})
}
複製代碼
使用JS實現圖片的壓縮效果,原理其實很簡單,核心API就是使用canvas的drawImage()方法。 canvas的drawImage()方法API以下:canvas
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
複製代碼
雖然看上去有9大參數,但不用慌,實際上能夠看出就3個參數:後端
img就是圖片對象,能夠是頁面上獲取的DOM對象,也能夠是虛擬DOM中的圖片對象。
sx,sy,swidth,sheight 在canvas畫布上畫一片區域用來放置圖片,sx,sy爲左上角座標,swidth,sheight指區域大小。若是沒有指定後面4個參數,則圖片會被拉伸或縮放在這片區域內。
x,y,width,height 就是圖片在canvas畫布上顯示的大小和位置。若是這裏的width,height的值就是圖片的原始尺寸,則最終的表現效果是圖片剪裁於swidth,sheight區域內。 對於本文的圖片壓縮,最後四個參數是用不到的,只需前面五個參數就能夠了。 核心代碼less
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
// 清除畫布
context.clearRect(0, 0, targetWidth, targetHeight);
// 圖片壓縮
context.drawImage(img, 0, 0, targetWidth, targetHeight);
// 獲取base64格式信息
const dataUrl = canvas.toDataURL(imageType);
複製代碼
canvas.toDataURL(mimeType, qualityArgument)
複製代碼
能夠把圖片轉換成base64格式信息,純字符的圖片表示法。
其中:
mimeType表示canvas導出來的base64圖片的類型,默認是png格式,也便是默認值是'image/png',咱們也能夠指定爲jpg格式'image/jpeg'或者webp等格式。file對象中的file.type就是文件的mimeType類型,在轉換時候正好能夠直接拿來用(若是有file對象)。
qualityArgument表示導出的圖片質量,只要導出爲jpg和webp格式的時候此參數纔有效果,默認值是0.92,是一個比較合理的圖片質量輸出參數,一般狀況下,咱們無需再設定。
canvas.toBlob(callback, mimeType, qualityArgument)
複製代碼
能夠把canvas轉換成Blob文件,一般用在文件上傳中,由於是二進制的,對後端更加友好。
toBlob方法目前iOS不支持。
和toDataURL()方法相比,toBlob()方法是異步的,所以多了個callback參數,這個callback回調方法默認的第一個參數就是轉換好的blob文件信息,本文demo的文件上傳就是將canvas圖片轉換成二進制的blob文件。
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import './index.less'
export default class InputImage extends Component {
constructor(props) {
super(props)
}
static defaultProps = {
prefixCls: 'yh-input-image',
compress: true,
className: '',
style: null,
onChange: ()=>{},
maxWidth: 400,
maxHeight: 400,
fileType: 'blob',
imageType: 'image/png'
}
static propTypes = {
prefixCls: PropTypes.string,
compress: PropTypes.bool,
className: PropTypes.string,
style: PropTypes.object,
onChange: PropTypes.func,
maxWidth: PropTypes.number,
maxHeight: PropTypes.number,
fileType: PropTypes.oneOf(['base64', 'blob']), // 返回壓縮後的的文件類型
imageType: PropTypes.string // 指定返回的圖片格式
}
compress = (res) => {
// console.log('res:', res)
let { maxWidth, maxHeight, fileType, imageType } = this.props
// imageType = `image/${imageType}`
return new Promise(function (resolve, reject) {
const img = new Image()
img.onload = function() {
let originWidth = this.width, originHeight = this.height
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
let targetWidth = originWidth, targetHeight = originHeight;
// 圖片尺寸超過400x400的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更寬,按照寬度限定尺寸
targetWidth = maxWidth;
targetHeight = Math.round(maxWidth * (originHeight / originWidth));
} else {
targetHeight = maxHeight;
targetWidth = Math.round(maxHeight * (originWidth / originHeight));
}
}
// canvas對圖片進行縮放
canvas.width = targetWidth;
canvas.height = targetHeight;
// 清除畫布
context.clearRect(0, 0, targetWidth, targetHeight);
// 圖片壓縮
context.drawImage(img, 0, 0, targetWidth, targetHeight);
if (fileType === 'base64') {
// 獲取base64格式信息
const dataUrl = canvas.toDataURL(imageType);
resolve(dataUrl)
} else {
// 把canvas轉化爲blob二進制文件
if (canvas.toBlob) {
canvas.toBlob(function(blob) {
resolve(blob)
}, imageType)
} else { // ios 不支持toB
let data = canvas.toDataURL(imageType);
//dataURL 的格式爲 「data:image/png;base64,****」,逗號以前都是一些說明性的文字,咱們只須要逗號以後的就好了
data = data.split(',')[1];
data = window.atob(data);
let ia = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
ia[i] = data.charCodeAt(i);
}
//canvas.toDataURL 返回的默認格式就是 image/png
let blob = new Blob([ia], {
type: imageType
});
resolve(blob)
}
}
}
img.onerror = function() {
reject(new Error('圖片加載失敗'))
}
img.src = res;
});
}
// 讀取文件並轉化爲base64
fileReader = (file) => {
console.log('file:', file)
return new Promise(function (resolve, reject) {
const reader = new FileReader();
reader.onload = (e) => {
resolve(e.target.result)
};
reader.onerror = reject
reader.readAsDataURL(file);
})
}
onChange = async (e) => {
const { onChange, compress } = this.props
let files = e.target.files
const len = files.length
console.log('files:', files)
let newFiles = []
if (compress) {
for(let i = 0; i < len; i++) {
const result = await this.fileReader(files[i])
const data = await this.compress(result)
// console.log('data:', data)
newFiles.push(data)
}
}
console.log('newFiles:', newFiles)
onChange && onChange({
files,
newFiles
})
}
render() {
const {
prefixCls, className, children, style
} = this.props
const cls = classNames({
[prefixCls]: true,
[className]: className
})
return (
<div className={cls} style={style} onClick={() => { this.refs.inputFile.click() }}> <input ref="inputFile" type="file" multiple accept="image/*" onChange={this.onChange}/> { children } {/**/} </div> ) } } 複製代碼