最近在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> ) } } 复制代码