WebApp【公共组件库】@前端(For Git Submodule)
Tevin
2021-06-19 20ef1d98b58ac24ae7de0e40111082a382f00abf
实现图片压缩组件,第一部分、第二部分
2 files added
1 files modified
236 ■■■■■ changed files
forms/imagePicker/CImageCompressor.vue 174 ●●●●● patch | view | raw | blame | history
forms/imagePicker/CImagePicker.vue 50 ●●●●● patch | view | raw | blame | history
forms/imagePicker/cImageCompressor.scss 12 ●●●●● patch | view | raw | blame | history
forms/imagePicker/CImageCompressor.vue
New file
@@ -0,0 +1,174 @@
/**
 * CImageCompressor
 * @author Tevin
 */
<template>
    <view class="c-image-compressor">
        <canvas
            :canvas-id="cavId"
            :style="{width:cavWidth+'px', height:cavHeight+'px'}"
            ref="canvas"
        />
    </view>
</template>
<script>
import Taro from '@tarojs/taro';
import { $ } from '@tarojs/extend';
import './cImageCompressor.scss';
export default {
    name: 'CImageCompressor',
    data() {
        return {
            // canvas id
            cavId: 'compressor' + parseInt(Math.random() * 10000 + 1000),
            // canvas 大小
            cavWidth: 0,
            cavHeight: 0,
            // 图片
            imgBlobs: [],
        };
    },
    methods: {
        $compress(tempPaths, callback) {
            if (typeof this.canvasContext === 'undefined') {
                this.canvasContext = Taro.createCanvasContext(this.cavId, this);
            }
            let curIndex = 0;
            const processImg = () => {
                this._compressImg(tempPaths[curIndex], () => {
                    curIndex++;
                    // 递归依次完成所有图片压缩
                    if (curIndex < tempPaths.length) {
                        processImg();
                    }
                    // 完成图片压缩
                    else {
                        console.log(this.imgBlobs);
                    }
                });
            };
            processImg();
        },
        _compressImg(tempPath, callback) {
            Taro.getImageInfo({
                src: tempPath,
                success: res => {
                    const size = this._limitSize(res);
                    const drawSet = this._creatDrawSet(res, size);
                    this._drawImage(tempPath, drawSet, compress => {
                        this.imgBlobs.push({
                            source: tempPath,
                            compress,
                        });
                        callback();
                    });
                },
            });
        },
        _limitSize(res) {
            const rate = res.width / res.height;
            let width, height;
            // 宽大于高,按宽算
            if (res.width >= res.height) {
                width = Math.min(res.width, 1600);
                height = Math.round(width / rate);
            }
            // 按高算
            else {
                height = Math.min(res.height, 1600);
                width = Math.round(height * rate);
            }
            return [width, height];
        },
        _setCanvasSize(width, height) {
            this.cavWidth = width;
            this.cavHeight = height;
            if (process.env.TARO_ENV === 'h5') {
                $(this.$refs.canvas.$el)
                    .find('canvas')
                    .attr('width', width)
                    .attr('height', height);
            }
            // TODO: 小程序中的 canvas 重设
        },
        _creatDrawSet(res, [width, height]) {
            const orientation = res.orientation || 'up';
            // 根据orientation值处理图片
            switch (orientation) {
                // 不需要旋转
                case 'up':
                    this._setCanvasSize(width, height);
                    return [['drawImage', [0, 0, width, height]]];
                // 需要需要旋转180度
                case 'down':
                    this._setCanvasSize(width, height);
                    return [
                        ['translate', [width / 2, height / 2]],
                        ['rotate', [(180 * Math.PI) / 180]],
                        ['drawImage', [-width / 2, -height / 2, width, height]],
                    ];
                // 需要顺时针旋转270度
                case 'left':
                    this._setCanvasSize(height, width);
                    return [
                        ['translate', [height / 2, width / 2]],
                        ['rotate', [(270 * Math.PI) / 180]],
                        ['drawImage', [-width / 2, -height / 2, width, height]],
                    ];
                // 需要顺时针旋转90度
                case 'right':
                    this._setCanvasSize(height, width);
                    return [
                        ['translate', [height / 2, width / 2]],
                        ['rotate', [(90 * Math.PI) / 180]],
                        ['drawImage', [-width / 2, -height / 2, width, height]],
                    ];
            }
        },
        _drawImage(tempPath, drawSet, callback) {
            drawSet.forEach(step => {
                const [key, params] = step;
                if (key === 'drawImage') {
                    // 使用图片
                    const img = new Image();
                    img.src = tempPath;
                    this.canvasContext.drawImage(img, ...params);
                } else {
                    this.canvasContext[key](...params);
                }
            });
            // 清空再绘制
            this.canvasContext.draw(false, () => {
                Taro.canvasToTempFilePath({
                    canvasId: this.cavId,
                    width: this.cavWidth,
                    height: this.cavHeight,
                    quality: 0.6,
                    fileType: 'jpeg',
                    success: res => {
                        callback(this._transBase64ToBlob(res.tempFilePath));
                    },
                });
            });
        },
        _transBase64ToBlob(base64) {
            const arr = base64.split(',');
            const mime = arr[0].match(/:(.*?);/)[1];
            const bstr = atob(arr[1]);
            let n = bstr.length;
            const u8arr = new Uint8Array(n);
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }
            const blob = new Blob([u8arr], { type: mime });
            return URL.createObjectURL(blob);
        },
    },
    beforeDestroy() {
        this.canvasContext = null;
    },
};
</script>
forms/imagePicker/CImagePicker.vue
@@ -35,6 +35,7 @@
                :src="curtainImg"
            />
        </AtCurtain>
        <CImageCompressor ref="compressor" />
    </view>
</template>
@@ -43,11 +44,13 @@
import { $ } from '@tarojs/extend';
import { AtInput, AtImagePicker, AtCurtain } from 'taro-ui-vue';
import { $fetcherCommon } from '@fetchers/FCommon';
import CImageCompressor from './CImageCompressor.vue';
import './cImagePicker.scss';
export default {
    name: 'CImagePicker',
    components: {
        CImageCompressor,
        AtInput,
        AtImagePicker,
        AtCurtain,
@@ -76,7 +79,7 @@
        },
    },
    methods: {
        handleChange(files, operationType, index) {
        triggerChange(files) {
            const value = [];
            files.forEach(file => {
                if (file.type === 'btn') {
@@ -86,6 +89,48 @@
            });
            this.itemRes.onChange(value);
        },
        handleChange(files, operationType, index) {
            // 添加图片
            if (operationType === 'add') {
                const needs = files
                    .map((file, index) => {
                        const fileInfo = file.file;
                        // 没有 file 信息对象,或者不是 blob 类型
                        if (!fileInfo || fileInfo.path.indexOf('blob') < 0) {
                            return false;
                        }
                        // 尺寸小于 2M 的图片
                        if (fileInfo.size < 1 * 1024 * 1024) {
                            return false;
                        }
                        return [fileInfo, index];
                    })
                    .filter(Boolean);
                // 存在需要压缩的图片,一次性压缩
                if (needs.length > 0) {
                    const files2 = [...files];
                    const needPath = needs.map(need => need[0].path);
                    this.$refs.compressor.$compress(needPath, compressedFiles => {
                        compressedFiles.forEach((cpFile, index) => {
                            const filesIndex = needs[index][1];
                            files2[filesIndex] = {
                                url: cpFile.path,
                                file: cpFile,
                            };
                        });
                        this.triggerChange(files2);
                    });
                }
                // 不存在,直接显示
                else {
                    this.triggerChange(files);
                }
            }
            // 删除图片,直接显示
            else {
                this.triggerChange(files);
            }
        },
        handleImgView(index, file) {
            this.curtainImg = file.url;
            this.showImg = true;
@@ -94,6 +139,7 @@
            this.showImg = false;
        },
        handleFail(msg) {
            console.log(msg);
            Taro.showToast({
                title: msg,
                icon: 'none',
@@ -109,7 +155,7 @@
                if (file.type === 'btn') {
                    return;
                }
                // blob 二进制文件才上传
                // blob 临时文件才上传
                if (file.url.indexOf('blob') >= 0) {
                    uploadTeam.push(
                        new Promise((resolve, reject) => {
forms/imagePicker/cImageCompressor.scss
New file
@@ -0,0 +1,12 @@
/**
 * image picker
 * @author Tevin
 */
@import "../../common/sassMixin";
.c-image-compressor {
    width: 0px;
    height: 0px;
    overflow: hidden;
}