HTML5调用手机相册选取图片并压缩,修改input[file]默认样式

最近在HTML5项目中用到了上传图片功能,现在有HTML5前端网页也能访问手机相册或照相机,但是手机拍下来的图片动不动就好几M,这样上传几张图片上去,用户要骂街了,能不能在客户端压缩好图片后再上传了,节约用户的流量。那么前端如何压缩图片了?
实现图片压缩我们需要用到canvas。 好了开干吧!

如下代码使用React编写

使用input file 调用相机货相册

<input type="file" multiple accept="image/*"/>
复制代码

点击如上input file可以调起一个弹窗,让用户从相机或相册中获取图片。
默认的input file样式太丑,看着太别扭,我们先来把样式美化下吧。这里推荐两种方法,

方法一:
"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我们完全看不见。

方法二:
"leftFile" type="file" multiple accept="image/*" style={{display: 'none'}}/>
<a className="btn" onClick={()=>{
    this.refs.leftFile.click()
}}>Browsea>
复制代码

这种方法把input display设为none,点击其他元素触发input的click事件。

使用canvas压缩图片

1、获取到选取的图片

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 事件中获取到选择的文件或拍摄的照片

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

2、通过FileReader把图片转化为base64图像编码

通过onChange获取到图片后需要创建一个FileReader对象,我们需要调用readAsDataURL把文件转换为base64图像编码,如data:image/jpeg;base64……这种格式。让fileReader读取文件完毕后会触发onload方法,在onload方法中我们可以通过e.target.result来获取到base64的编码,如果读取失败,该值为null。onload是个异步方法,我们可以通过Promise和async,await来把异步变为同步,不至于嵌套太多callback。

// 读取文件并转化为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);
    })
}
复制代码

3、创建Image对象,给src赋值为fileReader读取的base64结果,然后同样在Image的onload方法中处理图片压缩。

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;
    })
}
复制代码

4、通过canvas压缩图片

使用JS实现图片的压缩效果,原理其实很简单,核心API就是使用canvas的drawImage()方法。 canvas的drawImage()方法API如下:

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区域内。 对于本文的图片压缩,最后四个参数是用不到的,只需前面五个参数就可以了。 核心代码

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()方法
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()方法
canvas.toBlob(callback, mimeType, qualityArgument)
复制代码

可以把canvas转换成Blob文件,通常用在文件上传中,因为是二进制的,对后端更加友好。

toBlob方法目前iOS不支持。

和toDataURL()方法相比,toBlob()方法是异步的,因此多了个callback参数,这个callback回调方法默认的第一个参数就是转换好的blob文件信息,本文demo的文件上传就是将canvas图片转换成二进制的blob文件。

完整代码
github.com/zhangyi5628…

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

转载于:https://juejin.im/post/5c8cbee8f265da2dbb127a49

你可能感兴趣的:(HTML5调用手机相册选取图片并压缩,修改input[file]默认样式)