2 files added
1 files modified
New file |
| | |
| | | /** |
| | | * 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> |
| | |
| | | :src="curtainImg" |
| | | /> |
| | | </AtCurtain> |
| | | <CImageCompressor ref="compressor" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | 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, |
| | |
| | | }, |
| | | }, |
| | | methods: { |
| | | handleChange(files, operationType, index) { |
| | | triggerChange(files) { |
| | | const value = []; |
| | | files.forEach(file => { |
| | | if (file.type === 'btn') { |
| | |
| | | }); |
| | | 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; |
| | |
| | | this.showImg = false; |
| | | }, |
| | | handleFail(msg) { |
| | | console.log(msg); |
| | | Taro.showToast({ |
| | | title: msg, |
| | | icon: 'none', |
| | |
| | | if (file.type === 'btn') { |
| | | return; |
| | | } |
| | | // blob 二进制文件才上传 |
| | | // blob 临时文件才上传 |
| | | if (file.url.indexOf('blob') >= 0) { |
| | | uploadTeam.push( |
| | | new Promise((resolve, reject) => { |
New file |
| | |
| | | /** |
| | | * image picker |
| | | * @author Tevin |
| | | */ |
| | | |
| | | @import "../../common/sassMixin"; |
| | | |
| | | .c-image-compressor { |
| | | width: 0px; |
| | | height: 0px; |
| | | overflow: hidden; |
| | | } |