HTML5調用手機相冊選取圖片並壓縮,修改input[file]默認樣式

最近在HTML5項目中用到了上傳圖片功能,如今有HTML5前端網頁也能訪問手機相冊或照相機,可是手機拍下來的圖片動不動就好幾M,這樣上傳幾張圖片上去,用戶要罵街了,能不能在客戶端壓縮好圖片後再上傳了,節約用戶的流量。那麼前端如何壓縮圖片了?
實現圖片壓縮咱們須要用到canvas。 好了開幹吧!html

以下代碼使用React編寫前端

使用input file 調用相機貨相冊

<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

使用canvas壓縮圖片

一、獲取到選取的圖片

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)
    }
}
複製代碼

二、經過FileReader把圖片轉化爲base64圖像編碼

經過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);
    })
}
複製代碼

三、建立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;
    })
}
複製代碼

四、經過canvas壓縮圖片

使用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()方法
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> ) } } 複製代碼
相關文章
相關標籤/搜索