Repository: wuwhs/js-image-compressor
Branch: master
Commit: 8d2a8195183d
Files: 26
Total size: 99.4 KB
Directory structure:
gitextract_663mm2_7/
├── .gitignore
├── README-CN.md
├── README.md
├── demo/
│ ├── default/
│ │ ├── index.html
│ │ └── main.js
│ ├── grayscale/
│ │ ├── index.html
│ │ └── main.js
│ ├── index.css
│ ├── simple/
│ │ ├── index.html
│ │ └── main.js
│ └── watermark/
│ ├── index.html
│ └── main.js
├── dist/
│ └── image-compressor.js
├── examples/
│ ├── default/
│ │ ├── index.html
│ │ └── main.js
│ ├── grayscale/
│ │ ├── index.html
│ │ └── main.js
│ ├── index.css
│ ├── simple/
│ │ ├── index.html
│ │ └── main.js
│ └── watermark/
│ ├── index.html
│ └── main.js
├── index.js
├── package.json
├── src/
│ └── index.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: README-CN.md
================================================
## 简介
`js-image-compressor` 是一个实现轻量级图片压缩的 `javascript` 库,压缩后仅有 `5kb`,在前端页面即可实现对图片的压缩。在提供基本图片压缩功能同时,还暴露出图片处理相关公用方法,以及进行边界情况处理:
- 可以对待转化图片大小设置一定的阈值,使得图片转化成 `png` 格式在不理想情况下不至于过大,同时大于这个阈值则可以自动转化成 `jpeg` 格式,实现更优压缩;
- 可以限制输出图片宽高大小,从而防止意外情况发生,比如压缩运算过大使得浏览器奔溃;
- 默认对 `png` 输出图片添加透明底色,其他格式设为白色,避免“黑屏”发生;
- 读取 `jpeg` 格式图片的 `EXIF` 信息,矫正图片方位;
- 提供一些图片处理的常用工具函数(`image2Canvas`、`canvas2Blob` 和 `canvas2DataUrl` 等),用户还可以自定义图片输出的样式特征(比如可以灰度处理、加水印)。
文档语言:
- [英文](./README.md)
- [中文](./README-CN.md)
## 使用
### 安装引入
你可以通过npm去安装依赖:
```js
npm install js-image-compressor --save-dev
```
也可以在下载后,在 `dist` 目录下找到 `image-compress.min.js` 文件在页面中通过 `script` 引入:
```html
```
### 简单使用
你可以只传入待压缩图片对象,其他参数都是非必须的,插件按照默认参数自动完成图片压缩处理。不过这样输出的压缩图片符合以下特征:
- 默认按照 `0.8` 压缩率配置;
- 输出图片宽/高维持源图片宽/高;
- 一般的,输出图片格式保持源图片格式;
- 当 `png` 图片的 `size` 大于 `2m` 时,默认转化成 `jpeg` 格式图片;
- 给 `png` 图片填充透明色;
- 当输出图片 `size` 大于源图片时,将源图片当作输出图片返回;
- `jpeg` 格式图片,矫正翻转/旋转方向;
如果这些默认配置不能满足你的需求,可能需要其他参数配置。以下是一个简单使用配置:
```js
var options = {
file: file,
// 压缩前回调
beforeCompress: function (result) {
console.log('压缩之前图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
},
// 压缩成功回调
success: function (result) {
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
}
};
new ImageCompressor(options);
```
其中,钩子函数 `beforeCompress` 发生在读取图片之后,创建画布之前;钩子函数 `success` 函数发生在压缩完成生成图片之后。它们回调参数 `result` 是整合来尺寸、图片类型和大小等相关信息的 `blob` 对象。
### 标准使用
在标准使用中,我们可以根据自身需求自定义配置压缩比(`quality`)、输出图片类型(`mimeType`)、宽(`width`)、高(`height`)、最大宽(`maxWidth`)、最大高(`maxHeight`)、最小宽(`minWidth`)、最大高(`minHeight`)、png转jpeg阈值(`convertSize`)、是否矫正jpeg方向(`redressOrientation`)和是否宽松模式(`loose`)。
- 是否矫正jpeg方向(`redressOrientation`),`jpeg` 格式图片在某些iOS浏览器会按其方向呈现图像,这个选项可以控制恢复初始方向,默认为 `true`;
- 是否宽松模式(`loose`)、的意思是控制当压缩的图片 `size` 大于源图片,输出源图片,否则输出压缩后图片,默认是 `true`。
以下是标准配置:
```js
var options = {
file: file,
quality: 0.6,
mimeType: 'image/jpeg',
maxWidth: 2000,
maxHeight: 2000,
width: 1000,
height: 1000,
minWidth: 500,
minHeight: 500,
convertSize: Infinity,
loose: true,
redressOrientation: true,
// 压缩前回调
beforeCompress: function (result) {
console.log('压缩之前图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
},
// 压缩成功回调
success: function (result) {
console.log('压缩之后图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
},
// 发生错误
error: function (msg) {
console.error(msg);
}
};
new ImageCompressor(options);
```
`error` 钩子函数是图片压缩过程中错误回调,没有这个回调错误则会在插件中 `throw new Error(msg)` 形式抛出。
### 其他钩子函数
在压缩输出图片之前,我们还可以对画布进行一些自定义处理,融入元素。
以下是对图片进行灰度和加水印处理:
```js
var options = {
file: file,
// 图片绘画前
beforeDraw: function (ctx) {
vm.btnText = '准备绘图...';
console.log('准备绘图...');
ctx.filter = 'grayscale(100%)';
},
// 图片绘画后
afterDraw: function (ctx, canvas) {
ctx.restore();
vm.btnText = '绘图完成...';
console.log('绘图完成...');
ctx.fillStyle = '#fff';
ctx.font = (canvas.width * 0.1) + 'px microsoft yahei';
ctx.fillText(vm.watermarkText, 10, canvas.height - 20);
},
};
new ImageCompressor(options);
```
`beforeDraw` 是在画布创建后,图片绘画前的钩子函数,`afterDraw` 是在图绘画后的钩子函数。
### 工具函数
下图归纳了 `js-image-compressor` 插件从用户图片通过 `input` 的 `file` 本地上传到对图片压缩的详细过程,同时暴露出这些工具方法供用户使用。

================================================
FILE: README.md
================================================
## Introduction
`js-image-compressor` is a `javascript` library that implements lightweight image compression. After compression, it is only `5kb`, and the image can be compressed on the front-end page. While providing basic image compression functions, it also exposes related public methods of image processing, as well as border case processing:
- A certain threshold can be set for the size of the converted image, so that the image converted to `png` format will not be too large under undesirable conditions, and at the same time larger than this threshold, it can be automatically converted to `jpeg` format for better compression;
- You can limit the width and height of the output image to prevent accidents, such as excessive compression operations that cause the browser to crash;
- By default, a transparent background color is added to the output image of `png`, and other formats are set to white to avoid "black screen";
- Read the `EXIF` information of `jpeg` format pictures, and correct the picture orientation;
- Provide some common tool functions for image processing (`image2Canvas`, `canvas2Blob` and `canvas2DataUrl`, etc.), and users can also customize the style features of image output (for example, grayscale processing, watermarking).
Document language:
- [English](./README.md)
- [Chinese](./README-CN.md)
## Use
### Installation and introduction
You can install dependencies through npm:
```js
npm install js-image-compressor --save-dev
```
You can also find the file `image-compress.min.js` in the `dist` directory after downloading and import it through `script` on the page:
```html
```
### Simple to use
You can only pass in the image object to be compressed, other parameters are optional, and the plug-in automatically completes the image compression processing according to the default parameters. However, the compressed image output in this way meets the following characteristics:
- The default configuration is based on `0.8` compression ratio;
- Output picture width/height maintains the source picture width/height;
- Generally, the output image format keeps the original image format;
- When the `size` of the `png` image is greater than `2m`, it will be converted into a `jpeg` format image by default;
- Fill the `png` picture with a transparent color;
- When the output picture `size` is larger than the source picture, the source picture will be returned as the output picture;
- `jpeg` format picture, correct the flip/rotation direction;
If these default configurations cannot meet your needs, other parameter configurations may be required. The following is a simple configuration:
```js
var options = {
file: file,
// Callback before compression
beforeCompress: function (result) {
console.log('Image size before compression:', result.size);
console.log('mime type:', result.type);
},
// Compression success callback
success: function (result) {
console.log('result:', result)
console.log('Image size after compression:', result.size);
console.log('mime type:', result.type);
console.log('Actual compression ratio:', ((file.size-result.size) / file.size * 100).toFixed(2) +'%');
}
};
new ImageCompressor(options);
```
Among them, the hook function `beforeCompress` occurs after the image is read and before the canvas is created; the hook function `success` function occurs after the compression is completed to generate the image. Their callback parameter `result` is a `blob` object that integrates relevant information such as size, picture type and size.
### Standard use
In standard use, we can customize the compression ratio (`quality`), output image type (`mimeType`), width (`width`), height (`height`), and maximum width (`maxWidth`) according to our own needs. ), maximum height (`maxHeight`), minimum width (`minWidth`), maximum height (`minHeight`), png to jpeg threshold (`convertSize`), whether to correct the jpeg direction (`redressOrientation`) and whether the loose mode ( `loose`).
- Whether to correct the jpeg orientation (`redressOrientation`), the `jpeg` format image will be presented according to its orientation in some iOS browsers, this option can control the restoration of the initial orientation, the default is `true`;
- Whether it is loose mode (`loose`), which means to control when the compressed image `size` is larger than the source image, output the source image, otherwise output the compressed image, the default is `true`.
The following is the standard configuration:
```js
var options = {
file: file,
quality: 0.6,
mimeType:'image/jpeg',
maxWidth: 2000,
maxHeight: 2000,
width: 1000,
height: 1000,
minWidth: 500,
minHeight: 500,
convertSize: Infinity,
loose: true,
redressOrientation: true,
// Callback before compression
beforeCompress: function (result) {
console.log('Image size before compression:', result.size);
console.log('mime type:', result.type);
},
// Compression success callback
success: function (result) {
console.log('Image size after compression:', result.size);
console.log('mime type:', result.type);
console.log('Actual compression ratio:', ((file.size-result.size) / file.size * 100).toFixed(2) +'%');
},
// An error occurred
error: function (msg) {
console.error(msg);
}
};
new ImageCompressor(options);
```
The `error` hook function is an error callback during the image compression process. Without this callback error, it will be thrown in the form of `throw new Error(msg)` in the plugin.
### Other hook functions
Before compressing the output image, we can also customize the canvas to incorporate elements.
The following is the grayscale and watermark processing of the picture:
```js
var options = {
file: file,
// Before picture painting
beforeDraw: function (ctx) {
vm.btnText ='Ready to draw...';
console.log('Ready to draw...');
ctx.filter ='grayscale(100%)';
},
// After the picture is painted
afterDraw: function (ctx, canvas) {
ctx.restore();
vm.btnText ='Drawing completed...';
console.log('Drawing completed...');
ctx.fillStyle ='#fff';
ctx.font = (canvas.width * 0.1) +'px microsoft yahei';
ctx.fillText(vm.watermarkText, 10, canvas.height-20);
},
};
new ImageCompressor(options);
```
`beforeDraw` is the hook function before the picture is drawn after the canvas is created, and `afterDraw` is the hook function after the picture is drawn.
### Tool functions
The following figure summarizes the detailed process of the `js-image-compressor` plug-in from uploading user pictures locally through the `file` of `input` to image compression, and at the same time exposes these tools and methods for users to use.

================================================
FILE: demo/default/index.html
================================================
js-image-compressor
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: demo/default/main.js
================================================
var BTN_OK = '确定';
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
mimeType: 'auto',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6,
maxWidth: 0,
maxHeight: 0,
width: 0,
height: 0,
minWidth: 0,
minHeight: 0,
convertSize: Infinity,
loose: true,
redressOrientation: true
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault();
ev.stopPropagation();
var dt = ev.dataTransfer;
var file = dt.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file);
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this;
vm.btnText = '读取中...';
var options = {
file: file,
quality: this.quality,
mimeType: this.mimeType,
maxWidth: this.maxWidth,
maxHeight: this.maxHeight,
width: this.width,
height: this.height,
minWidth: this.minWidth,
minHeight: this.minHeight,
convertSize: this.convertSize,
loose: this.loose,
redressOrientation: this.redressOrientation,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...';
vm.originImgWidth = result.width;
vm.originImgHeight = result.height;
vm.originSize = result.size;
vm.originMimeType = result.type;
console.log('压缩之前图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url;
})
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK;
vm.imgName = result.name;
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
vm.outputImgWidth = result.width;
vm.outputImgHeight = result.height;
vm.outputSize = result.size;
vm.outputMimeType = result.type;
vm.compressRatio = ((file.size - result.size) / file.size * 100).toFixed(2) + '%';
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url;
})
// 上传到远程服务器
// util.upload('/upload.png', result);
},
// 发生错误
error: function (msg) {
vm.btnText = BTN_OK;
console.error(msg);
}
};
new ImageCompressor(options);
}
}
})
================================================
FILE: demo/grayscale/index.html
================================================
grayscale demo
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: demo/grayscale/main.js
================================================
var BTN_OK = '确定';
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault();
ev.stopPropagation();
var dt = ev.dataTransfer;
var file = dt.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file);
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this;
vm.btnText = '读取中...';
var options = {
file: file,
quality: this.quality,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...';
vm.imgName = result.name;
vm.originImgWidth = result.width;
vm.originImgHeight = result.height;
vm.originSize = result.size;
vm.originMimeType = result.type;
console.log('压缩之前图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url;
})
},
// 图片绘画前
beforeDraw: function (ctx) {
vm.btnText = '准备绘图...';
console.log('准备绘图...');
ctx.filter = 'grayscale(100%)';
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK;
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
vm.outputImgWidth = result.width;
vm.outputImgHeight = result.height;
vm.outputSize = result.size;
vm.outputMimeType = result.type;
vm.compressRatio = ((file.size - result.size) / file.size * 100).toFixed(2) + '%';
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url;
})
// 上传到远程服务器
// util.upload('/upload.png', result);
}
};
new ImageCompressor(options);
}
}
})
================================================
FILE: demo/index.css
================================================
.row {
display: flex;
border-bottom: 1px dashed #999;
}
.column {
padding: 20px;
}
.upload,
.origin,
.output {
width: 300px;
max-height: 300px;
overflow: hidden;
text-align: center;
}
.origin img,
.output img {
max-width: 100%;
max-height: 300px;
vertical-align: middle;
}
.drop-content {
display: flex;
align-items: center;
justify-content: center;
border: 1px dotted #999;
border-radius: 4px;
min-height: 100px;
height: 100%;
}
.drop-content label {
color: #3db8fd;
}
.drop-content label:hover {
cursor: pointer;
}
#file-elem {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
#file-elem:focus+label {
outline: thin dotted;
}
#file-elem:focus-within+label {
outline: thin dotted;
}
================================================
FILE: demo/simple/index.html
================================================
simple demo
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: demo/simple/main.js
================================================
var BTN_OK = '确定';
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault();
ev.stopPropagation();
var dt = ev.dataTransfer;
var file = dt.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file);
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this;
vm.btnText = '读取中...';
var options = {
file: file,
quality: this.quality,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...';
vm.imgName = result.name;
vm.originImgWidth = result.width;
vm.originImgHeight = result.height;
vm.originSize = result.size;
vm.originMimeType = result.type;
console.log('压缩之前图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url;
})
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK;
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
vm.outputImgWidth = result.width;
vm.outputImgHeight = result.height;
vm.outputSize = result.size;
vm.outputMimeType = result.type;
vm.compressRatio = ((file.size - result.size) / file.size * 100).toFixed(2) + '%';
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url;
})
// 上传到远程服务器
// util.upload('/upload.png', result);
}
};
new ImageCompressor(options);
}
}
})
================================================
FILE: demo/watermark/index.html
================================================
watermark demo
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: demo/watermark/main.js
================================================
var BTN_OK = '确定';
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6,
watermarkText: 'watermark'
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault();
ev.stopPropagation();
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault();
ev.stopPropagation();
var dt = ev.dataTransfer;
var file = dt.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0];
this.file = file;
this.compressImage(file);
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file);
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this;
vm.btnText = '读取中...';
var options = {
file: file,
quality: this.quality,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...';
vm.imgName = result.name;
vm.originImgWidth = result.width;
vm.originImgHeight = result.height;
vm.originSize = result.size;
vm.originMimeType = result.type;
console.log('压缩之前图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url;
})
},
// 图片绘画后
afterDraw: function (ctx, canvas) {
ctx.restore();
vm.btnText = '绘图完成...';
console.log('绘图完成...');
ctx.fillStyle = '#fff';
ctx.font = (canvas.width * 0.1) + 'px microsoft yahei';
ctx.fillText(vm.watermarkText, 10, canvas.height - 20);
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK;
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size);
console.log('mime 类型: ', result.type);
console.log('实际压缩率: ', ((file.size - result.size) / file.size * 100).toFixed(2) + '%');
vm.outputImgWidth = result.width;
vm.outputImgHeight = result.height;
vm.outputSize = result.size;
vm.outputMimeType = result.type;
vm.compressRatio = ((file.size - result.size) / file.size * 100).toFixed(2) + '%';
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url;
})
// 上传到远程服务器
// util.upload('/upload.png', result);
}
};
new ImageCompressor(options);
}
}
})
================================================
FILE: dist/image-compressor.js
================================================
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["ImageCompressor"] = factory();
else
root["ImageCompressor"] = factory();
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var WIN = window
var REGEXP_IMAGE_TYPE = /^image\//
var REGEXP_EXTENSION = /\.\w+$/
var util = {}
var defaultOptions = {
file: null,
quality: 0.8,
convertSize: 2048000,
loose: true,
redressOrientation: true,
}
/**
* 判断是否为函数
* @param {Any} value 任意值
* @returns {Boolean} 判断结果
*/
var isFunc = function (value) {
return typeof value === 'function'
}
/**
* 判断是否为图片类型
* @param {String} value 类型字符串
* @returns {Boolean} 判断结果
*/
var isImageType = function (value) {
return REGEXP_IMAGE_TYPE.test(value)
}
/**
* 图片类型转化为文件拓展名
* @param {String} value
*/
var imageTypeToExtension = function (value) {
var extension = isImageType(value) ? value.substr(6) : ''
if (extension === 'jpeg') {
extension = 'jpg'
}
return '.' + extension
}
/**
* 图片压缩构造函数
* @param {Object} options 相关参数
*/
function ImageCompressor(options) {
options = Object.assign({}, defaultOptions, options)
this.options = options
this.file = options.file
this.image = null
this.ParsedOrientationInfo = null
this.init()
}
var _proto = ImageCompressor.prototype
// WIN.ImageCompressor = ImageCompressor;
/* harmony default export */ __webpack_exports__["default"] = (ImageCompressor);
/**
* 初始化
*/
_proto.init = function () {
var _this = this
var file = this.file
var options = this.options
if (!file || !isImageType(file.type)) {
_this.error('请上传图片文件!')
return
}
if (!isImageType(options.mimeType)) {
options.mimeType = file.type
}
util.file2Image(
file,
function (img) {
if (isFunc(_this.beforeCompress)) {
_this.image = img
file.width = img.naturalWidth
file.height = img.naturalHeight
_this.beforeCompress(file)
}
if (file.type === 'image/jpeg' && options.redressOrientation) {
_this.getParsedOrientationInfo(function (info) {
_this.parsedOrientationInfo = info
_this.rendCanvas()
})
} else {
_this.parsedOrientationInfo = {
rotate: 0,
scaleX: 1,
scaleY: 1,
}
_this.rendCanvas()
}
},
_this.error
)
}
/**
* `Canvas` 渲染模块
*/
_proto.rendCanvas = function () {
var _this = this
var options = this.options
var image = this.image
var edge = this.getExpectedEdge()
var dWidth = edge.dWidth
var dHeight = edge.dHeight
var width = edge.width
var height = edge.height
var canvas = util.image2Canvas(
image,
dWidth,
dHeight,
_this.beforeDraw.bind(_this),
_this.afterDraw.bind(_this),
width,
height
)
util.canvas2Blob(
canvas,
function (blob) {
if (blob) {
blob.width = canvas.width
blob.height = canvas.height
}
_this.success(blob)
},
options.quality,
options.mimeType
)
}
/**
* 压缩之前,读取图片之后钩子函数
*/
_proto.beforeCompress = function () {
if (isFunc(this.options.beforeCompress)) {
this.options.beforeCompress(this.file)
}
}
/**
* 获取用户想要输出的边(宽高)
*/
_proto.getExpectedEdge = function () {
var image = this.image
var parsedOrientationInfo = this.parsedOrientationInfo
var rotate = parsedOrientationInfo.rotate
var options = this.options
var naturalWidth = image.naturalWidth
var naturalHeight = image.naturalHeight
var is90DegreesRotated = Math.abs(rotate) % 180 === 90
var temp
if (is90DegreesRotated) {
temp = naturalHeight
naturalHeight = naturalWidth
naturalWidth = temp
}
var aspectRatio = naturalWidth / naturalHeight
var maxWidth = Math.max(options.maxWidth, 0) || Infinity
var maxHeight = Math.max(options.maxHeight, 0) || Infinity
var minWidth = Math.max(options.minWidth, 0) || 0
var minHeight = Math.max(options.minHeight, 0) || 0
var width = Math.max(options.width, 0) || naturalWidth
var height = Math.max(options.height, 0) || naturalHeight
if (maxWidth < Infinity && maxHeight < Infinity) {
if (maxHeight * aspectRatio > maxWidth) {
maxHeight = maxWidth / aspectRatio
} else {
maxWidth = maxHeight * aspectRatio
}
} else if (maxWidth < Infinity) {
maxHeight = maxWidth / aspectRatio
} else if (maxHeight < Infinity) {
maxWidth = maxHeight * aspectRatio
}
if (minWidth > 0 && minHeight > 0) {
if (minHeight * aspectRatio > minWidth) {
minHeight = minWidth / aspectRatio
} else {
minWidth = minHeight * aspectRatio
}
} else if (minWidth > 0) {
minHeight = minWidth / aspectRatio
} else if (minHeight > 0) {
minWidth = minHeight * aspectRatio
}
if (height * aspectRatio > width) {
height = width / aspectRatio
} else {
width = height * aspectRatio
}
width = Math.floor(Math.min(Math.max(width, minWidth), maxWidth))
height = Math.floor(Math.min(Math.max(height, minHeight), maxHeight))
var dWidth = width
var dHeight = height
if (is90DegreesRotated) {
temp = dHeight
dHeight = dWidth
dWidth = temp
}
return {
dWidth: dWidth,
dHeight: dHeight,
width: width,
height: height,
}
}
/**
* 获取转化后的方向信息
* @param {Function} callback 回调函数
*/
_proto.getParsedOrientationInfo = function (callback) {
var _this = this
this.getOrientation(function (orientation) {
if (isFunc(callback)) {
callback(_this.parseOrientation(orientation))
}
})
}
/**
* 获取方向
* @param {Function} callback 回调函数
*/
_proto.getOrientation = function (callback) {
var _this = this
util.file2ArrayBuffer(this.file, function (result) {
if (isFunc(callback)) {
callback(_this.resetAndGetOrientation(result))
}
})
}
/**
* 从 `array buffer` 提取 `orientation` 值
* @param {ArrayBuffer} arrayBuffer - array buffer
* @returns {number} `orientation` 值
*/
_proto.resetAndGetOrientation = function (arrayBuffer) {
var dataView = new DataView(arrayBuffer)
var orientation
// 当图像没有正确的 Exif 信息时忽略 range error
try {
var littleEndian
var app1Start
var ifdStart
// JPEG 图片 (以 0xFFD8 开头)
if (dataView.getUint8(0) === 0xff && dataView.getUint8(1) === 0xd8) {
var length = dataView.byteLength
var offset = 2
while (offset + 1 < length) {
if (
dataView.getUint8(offset) === 0xff &&
dataView.getUint8(offset + 1) === 0xe1
) {
app1Start = offset
break
}
offset += 1
}
}
if (app1Start) {
var exifIDCode = app1Start + 4
var tiffOffset = app1Start + 10
if (util.getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
var endianness = dataView.getUint16(tiffOffset)
littleEndian = endianness === 0x4949
if (littleEndian || endianness === 0x4d4d) {
if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002a) {
var firstIFDOffset = dataView.getUint32(
tiffOffset + 4,
littleEndian
)
if (firstIFDOffset >= 0x00000008) {
ifdStart = tiffOffset + firstIFDOffset
}
}
}
}
}
if (ifdStart) {
var length = dataView.getUint16(ifdStart, littleEndian)
var offset
var i
for (i = 0; i < length; i += 1) {
offset = ifdStart + i * 12 + 2
if (
dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */
) {
// 8 是当前标签值的偏移
offset += 8
// 获取原始方向值
orientation = dataView.getUint16(offset, littleEndian)
// 以其默认值覆盖方向
dataView.setUint16(offset, 1, littleEndian)
break
}
}
}
} catch (e) {
console.error(e)
orientation = 1
}
return orientation
}
/**
* 逆向转化Exif获取到图片的方向信息
* @param {Number} orientation 方向标识
* @returns {Object} 转化结果
*/
_proto.parseOrientation = function (orientation) {
var rotate = 0
var scaleX = 1
var scaleY = 1
switch (orientation) {
// 水平翻转
case 2:
scaleX = -1
break
// 向左旋转180°
case 3:
rotate = -180
break
// 垂直翻转
case 4:
scaleY = -1
break
// 垂直翻转并且向右旋转90°
case 5:
rotate = 90
scaleY = -1
break
// 向右旋转90°
case 6:
rotate = 90
break
// 水平翻转并且向右旋转90°
case 7:
rotate = 90
scaleX = -1
break
// 向左旋转90°
case 8:
rotate = -90
break
default:
break
}
return {
rotate: rotate,
scaleX: scaleX,
scaleY: scaleY,
}
}
/**
* 画布上绘制图片前的一些操作:设置画布一些样式,支持用户自定义
* @param {CanvasRenderingContext2D} ctx Canvas 对象的上下文
* @param {HTMLCanvasElement} canvas Canvas 对象
*/
_proto.beforeDraw = function (ctx, canvas) {
var parsedOrientationInfo = this.parsedOrientationInfo
var rotate = parsedOrientationInfo.rotate
var scaleX = parsedOrientationInfo.scaleX
var scaleY = parsedOrientationInfo.scaleY
var file = this.file
var options = this.options
var fillStyle = 'transparent'
var width = canvas.width
var height = canvas.height
// `png` 格式图片大小超过 `convertSize`, 转化成 `jpeg` 格式
if (file.size > options.convertSize && options.mimeType === 'image/png') {
fillStyle = '#fff'
options.mimeType = 'image/jpeg'
}
// 覆盖默认的黑色填充色
ctx.fillStyle = fillStyle
ctx.fillRect(0, 0, width, height)
// 用户自定义画布样式
if (isFunc(options.beforeDraw)) {
options.beforeDraw.call(this, ctx, canvas)
}
ctx.save()
switch (rotate) {
case 90:
ctx.translate(width, 0)
break
case -90:
ctx.translate(0, height)
break
case -180:
ctx.translate(width, height)
break
}
ctx.rotate((rotate * Math.PI) / 180)
ctx.scale(scaleX, scaleY)
}
/**
* 画布上绘制图片后的一些操作:支持用户自定义
* @param {CanvasRenderingContext2D} ctx Canvas 对象的上下文
* @param {HTMLCanvasElement} canvas Canvas 对象
*/
_proto.afterDraw = function (ctx, canvas) {
var options = this.options
// 用户自定义画布样式
if (isFunc(options.afterDraw)) {
options.afterDraw.call(this, ctx, canvas)
}
}
/**
* 错误触发函数
* @param {String} msg 错误消息
*/
_proto.error = function (msg) {
var options = this.options
if (isFunc(options.error)) {
options.error.call(this, msg)
} else {
throw new Error(msg)
}
}
/**
* 成功触发函数
* @param {File|Blob} result `Blob` 对象
*/
_proto.success = function (result) {
var options = this.options
var file = this.file
var image = this.image
var edge = this.getExpectedEdge()
var naturalHeight = image.naturalHeight
var naturalWidth = image.naturalWidth
if (result && result.size) {
// 在非宽松模式下,用户期待的输出宽高没有大于源图片的宽高情况下,输出文件大小大于源文件,返回源文件
if (
!options.loose &&
result.size > file.size &&
!(edge.width > naturalWidth || edge.height > naturalHeight)
) {
console.warn('当前设置的是非宽松模式,压缩结果大于源图片,输出源图片')
result = file
} else {
var date = new Date()
result.lastModified = date.getTime()
result.lastModifiedDate = date
result.name = file.name
// 文件 `name` 属性中的后缀转化成实际后缀
if (result.name && result.type !== file.type) {
result.name = result.name.replace(
REGEXP_EXTENSION,
imageTypeToExtension(result.type)
)
}
}
} else {
// 在某些情况下压缩后文件为 `null`,返回源文件
console.warn('图片压缩出了点意外,输出源图片')
result = file
}
if (isFunc(options.success)) {
options.success.call(this, result)
}
}
/**
* 文件转化成 `data URL` 字符串
* @param {File} file 文件对象
* @param {Function} callback 成功回调函数
* @param {Function} error 取消回调函数
*/
util.file2DataUrl = function (file, callback, error) {
var reader = new FileReader()
reader.onload = function () {
callback(reader.result)
}
reader.onerror = function () {
if (isFunc(error)) {
error('读取文件失败!')
}
}
reader.readAsDataURL(file)
}
/**
* 文件转化成 `ArrayBuffer`
* @param {File} file 文件对象
* @param {Function} callback 成功回调函数
* @param {Function} error 取消回调函数
*/
util.file2ArrayBuffer = function (file, callback, error) {
var reader = new FileReader()
reader.onload = function (ev) {
callback(ev.target.result)
}
reader.onerror = function () {
if (isFunc(error)) {
error('读取文件失败!')
}
}
reader.readAsArrayBuffer(file)
}
/**
* 从 data view 中的字符代码获取字符字符串
* @param {DataView} dataView - The data view for read.
* @param {number} start - 起始位置
* @param {number} length - 读取长度
* @returns {string} 读取结果
*/
util.getStringFromCharCode = function (dataView, start, length) {
var str = ''
var i
length += start
for (i = start; i < length; i += 1) {
str += String.fromCharCode(dataView.getUint8(i))
}
return str
}
/**
* 文件转化成 `Image` 对象
* @param {File} file 文件对象
* @param {Function} callback 成功回调函数
* @param {Function} error 错误回调函数
*/
util.file2Image = function (file, callback, error) {
var image = new Image()
var URL = WIN.URL || WIN.webkitURL
if (
WIN.navigator &&
/(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(WIN.navigator.userAgent)
) {
// 修复IOS上webkit内核浏览器抛出错误 `The operation is insecure` 问题
image.crossOrigin = 'anonymous'
}
image.alt = file.name
image.onerror = function () {
if (isFunc(error)) {
error('图片加载错误!')
}
}
if (URL) {
var url = URL.createObjectURL(file)
image.onload = function () {
callback(image)
URL.revokeObjectURL(url)
}
image.src = url
} else {
this.file2DataUrl(
file,
function (dataUrl) {
image.onload = function () {
callback(image)
}
image.src = dataUrl
},
error
)
}
}
/**
* `url` 转化成 `Image` 对象
* @param {File} url `url`
* @param {Function} callback 成功回调函数
* @param {Function} error 失败回调函数
*/
util.url2Image = function (url, callback, error) {
var image = new Image()
image.src = url
image.onload = function () {
callback(image)
}
image.onerror = function () {
if (isFunc(error)) {
error('图片加载错误!')
}
}
}
/**
* `Image` 转化成 `Canvas` 对象
* @param {File} image `Image` 对象
* @param {Number} dWidth 目标宽度
* @param {Number} dHeight 目标高度
* @param {Function} beforeDraw 在图片绘画之前的回调函数
* @param {Function} afterDraw 在图片绘画之后的回调函数
* @param {Number} width 宽
* @param {Number} height 高
* @return {HTMLCanvasElement} `Canvas` 对象
*/
util.image2Canvas = function (
image,
dWidth,
dHeight,
beforeDraw,
afterDraw,
width,
height
) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
canvas.width = width || image.naturalWidth
canvas.height = height || image.naturalHeight
if (isFunc(beforeDraw)) {
beforeDraw(ctx, canvas)
}
ctx.save()
ctx.drawImage(image, 0, 0, dWidth, dHeight)
ctx.restore()
if (isFunc(afterDraw)) {
afterDraw(ctx, canvas)
}
return canvas
}
/**
* `Canvas` 转化成 `data URL` 对象
* @param {File} file `Canvas` 对象
* @param {Float} quality 输出质量比例
* @return {String} `data URL` 字符串
*/
util.canvas2DataUrl = function (canvas, quality, type) {
return canvas.toDataURL(type || 'image/jpeg', quality)
}
/**
* `data URL` 转化成 `Image` 对象
* @param {File} dataUrl `data URL` 字符串
* @param {Function} callback 成功回调函数
* @param {Function} error 失败回调函数
*/
util.dataUrl2Image = function (dataUrl, callback, error) {
var image = new Image()
image.onload = function () {
callback(image)
}
image.error = function () {
if (isFunc(error)) {
error('图片加载错误!')
}
}
image.src = dataUrl
}
/**
* `data URL` 转化成 `Blob` 对象
* @param {File} dataUrl `data URL` 字符串
* @param {String} type `mime`
* @return {Blob} `Blob` 对象
*/
util.dataUrl2Blob = function (dataUrl, type) {
var data = dataUrl.split(',')[1]
var mimePattern = /^data:(.*?)(;base64)?,/
var mime = dataUrl.match(mimePattern)[1]
var binStr = atob(data)
var len = data.length
var arr = new Uint8Array(len)
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i)
}
return new Blob([arr], { type: type || mime })
}
/**
* `Blob` 对象转化成 `data URL`
* @param {Blob} blob `Blob` 对象
* @param {Function} callback 成功回调函数
* @param {Function} error 失败回调函数
*/
util.blob2DataUrl = function (blob, callback, error) {
this.file2DataUrl(blob, callback, error)
}
/**
* `Blob`对象 转化成 `Image` 对象
* @param {Blob} blob `Blob` 对象
* @param {Function} callback 成功回调函数
* @param {Function} callback 失败回调函数
*/
util.blob2Image = function (blob, callback, error) {
this.file2Image(blob, callback, error)
}
/**
* `Canvas` 对象转化成 `Blob` 对象
* @param {HTMLCanvasElement} canvas `Canvas` 对象
* @param {Function} callback 回调函数
* @param {Float} quality 输出质量比例
* @param {String} type `mime`
*/
util.canvas2Blob = function (canvas, callback, quality, type) {
var _this = this
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var dataUrl = this.toDataURL(type, quality)
callback(_this.dataUrl2Blob(dataUrl))
},
})
}
canvas.toBlob(
function (blob) {
callback(blob)
},
type || 'image/jpeg',
quality || 0.8
)
}
/**
* 文件上传
* @param {String} url 上传路径
* @param {File} file 文件对象
* @param {Function} callback 回调函数
*/
util.upload = function (url, file, callback) {
var xhr = new XMLHttpRequest()
var fd = new FormData()
fd.append('file', file)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 上传成功
callback && callback(xhr.responseText)
} else {
throw new Error(xhr)
}
}
xhr.open('POST', url, true)
xhr.send(fd)
}
for (var key in util) {
if (util.hasOwnProperty(key)) {
ImageCompressor[key] = util[key]
}
}
/***/ })
/******/ ])["default"];
});
================================================
FILE: examples/default/index.html
================================================
js-image-compressor
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: examples/default/main.js
================================================
var BTN_OK = '确定'
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
mimeType: 'auto',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6,
maxWidth: 0,
maxHeight: 0,
width: 0,
height: 0,
minWidth: 0,
minHeight: 0,
convertSize: Infinity,
loose: true,
redressOrientation: true,
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault()
ev.stopPropagation()
var dt = ev.dataTransfer
var file = dt.files[0]
this.file = file
this.compressImage(file)
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0]
this.file = file
this.compressImage(file)
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file)
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this
vm.btnText = '读取中...'
var options = {
file: file,
quality: this.quality,
mimeType: this.mimeType,
maxWidth: this.maxWidth,
maxHeight: this.maxHeight,
width: this.width,
height: this.height,
minWidth: this.minWidth,
minHeight: this.minHeight,
convertSize: this.convertSize,
loose: this.loose,
redressOrientation: this.redressOrientation,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...'
vm.originImgWidth = result.width
vm.originImgHeight = result.height
vm.originSize = result.size
vm.originMimeType = result.type
console.log('压缩之前图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url
})
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK
vm.imgName = result.name
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
console.log(
'实际压缩率: ',
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
)
vm.outputImgWidth = result.width
vm.outputImgHeight = result.height
vm.outputSize = result.size
vm.outputMimeType = result.type
vm.compressRatio =
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url
})
// 上传到远程服务器
// util.upload('/upload.png', result);
},
// 发生错误
error: function (msg) {
vm.btnText = BTN_OK
console.error(msg)
},
}
new ImageCompressor(options)
},
},
})
================================================
FILE: examples/grayscale/index.html
================================================
grayscale demo
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: examples/grayscale/main.js
================================================
var BTN_OK = '确定'
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6,
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault()
ev.stopPropagation()
var dt = ev.dataTransfer
var file = dt.files[0]
this.file = file
this.compressImage(file)
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0]
this.file = file
this.compressImage(file)
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file)
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this
vm.btnText = '读取中...'
var options = {
file: file,
quality: this.quality,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...'
vm.imgName = result.name
vm.originImgWidth = result.width
vm.originImgHeight = result.height
vm.originSize = result.size
vm.originMimeType = result.type
console.log('压缩之前图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url
})
},
// 图片绘画前
beforeDraw: function (ctx) {
vm.btnText = '准备绘图...'
console.log('准备绘图...')
ctx.filter = 'grayscale(100%)'
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
console.log(
'实际压缩率: ',
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
)
vm.outputImgWidth = result.width
vm.outputImgHeight = result.height
vm.outputSize = result.size
vm.outputMimeType = result.type
vm.compressRatio =
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url
})
// 上传到远程服务器
// util.upload('/upload.png', result);
},
}
new ImageCompressor(options)
},
},
})
================================================
FILE: examples/index.css
================================================
.row {
display: flex;
border-bottom: 1px dashed #999;
}
.column {
padding: 20px;
}
.upload,
.origin,
.output {
width: 300px;
max-height: 300px;
overflow: hidden;
text-align: center;
}
.origin img,
.output img {
max-width: 100%;
max-height: 300px;
vertical-align: middle;
}
.drop-content {
display: flex;
align-items: center;
justify-content: center;
border: 1px dotted #999;
border-radius: 4px;
min-height: 100px;
height: 100%;
}
.drop-content label {
color: #3db8fd;
}
.drop-content label:hover {
cursor: pointer;
}
#file-elem {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
#file-elem:focus+label {
outline: thin dotted;
}
#file-elem:focus-within+label {
outline: thin dotted;
}
================================================
FILE: examples/simple/index.html
================================================
simple demo
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: examples/simple/main.js
================================================
var BTN_OK = '确定'
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6,
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault()
ev.stopPropagation()
var dt = ev.dataTransfer
var file = dt.files[0]
this.file = file
this.compressImage(file)
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0]
this.file = file
this.compressImage(file)
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file)
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this
vm.btnText = '读取中...'
var options = {
file: file,
quality: this.quality,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...'
vm.imgName = result.name
vm.originImgWidth = result.width
vm.originImgHeight = result.height
vm.originSize = result.size
vm.originMimeType = result.type
console.log('压缩之前图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url
})
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
console.log(
'实际压缩率: ',
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
)
vm.outputImgWidth = result.width
vm.outputImgHeight = result.height
vm.outputSize = result.size
vm.outputMimeType = result.type
vm.compressRatio =
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url
})
// 上传到远程服务器
// util.upload('/upload.png', result);
},
}
new ImageCompressor(options)
},
},
})
================================================
FILE: examples/watermark/index.html
================================================
watermark demo
源图片信息
mime类型:{{originMimeType}}
尺寸:{{originImgWidth}} x {{originImgHeight}}
大小:{{originSize}}
压缩图片信息
mime类型:{{outputMimeType}}
尺寸:{{outputImgWidth}} x {{outputImgHeight}}
大小:{{outputSize}}
实际压缩率:{{compressRatio}}
================================================
FILE: examples/watermark/main.js
================================================
var BTN_OK = '确定'
new Vue({
el: '#app',
data: function () {
return {
file: null,
btnText: BTN_OK,
imgName: '',
originImgUrl: '',
originMimeType: 'auto',
originSize: 0,
originImgWidth: 'auto',
originImgHeight: 'auto',
outputImgUrl: '',
outputMimeType: 'auto',
outputImgWidth: 'auto',
outputImgHeight: 'auto',
outputSize: 0,
compressRatio: 0,
quality: 0.6,
watermarkText: 'watermark',
}
},
methods: {
/**
* 拖拽文件进入元素
* @param {Event} ev 事件
*/
dragFileEnter: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 拖拽文件在元素上
* @param {Event} ev 事件
*/
drageFileOver: function (ev) {
ev.preventDefault()
ev.stopPropagation()
},
/**
* 放开文件拖拽
* @param {Event} ev 事件
*/
dropFile: function (ev) {
ev.preventDefault()
ev.stopPropagation()
var dt = ev.dataTransfer
var file = dt.files[0]
this.file = file
this.compressImage(file)
},
/**
* 上传文件改变事件
* @param {Event} ev 事件
*/
inputChange: function (ev) {
var file = ev.target.files[0]
this.file = file
this.compressImage(file)
},
/**
* 确定提交
*/
submit: function () {
this.compressImage(this.file)
},
/**
* 压缩图片
* @param {File} file `File` 对象
*/
compressImage: function (file) {
var vm = this
vm.btnText = '读取中...'
var options = {
file: file,
quality: this.quality,
// 压缩前回调
beforeCompress: function (result) {
vm.btnText = '处理中...'
vm.imgName = result.name
vm.originImgWidth = result.width
vm.originImgHeight = result.height
vm.originSize = result.size
vm.originMimeType = result.type
console.log('压缩之前图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
// 将上传图片在页面预览
ImageCompressor.file2DataUrl(result, function (url) {
vm.originImgUrl = url
})
},
// 图片绘画后
afterDraw: function (ctx, canvas) {
ctx.restore()
vm.btnText = '绘图完成...'
console.log('绘图完成...')
ctx.fillStyle = '#fff'
ctx.font = canvas.width * 0.1 + 'px microsoft yahei'
ctx.fillText(vm.watermarkText, 10, canvas.height - 20)
},
// 压缩成功回调
success: function (result) {
vm.btnText = BTN_OK
console.log('result: ', result)
console.log('压缩之后图片尺寸大小: ', result.size)
console.log('mime 类型: ', result.type)
console.log(
'实际压缩率: ',
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
)
vm.outputImgWidth = result.width
vm.outputImgHeight = result.height
vm.outputSize = result.size
vm.outputMimeType = result.type
vm.compressRatio =
(((file.size - result.size) / file.size) * 100).toFixed(2) + '%'
// 生成压缩后图片在页面展示
ImageCompressor.file2DataUrl(result, function (url) {
vm.outputImgUrl = url
})
// 上传到远程服务器
// util.upload('/upload.png', result);
},
}
new ImageCompressor(options)
},
},
})
================================================
FILE: index.js
================================================
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/image-compressor.min.js')
} else {
module.exports = require('./dist/image-compressor.js')
}
================================================
FILE: package.json
================================================
{
"name": "js-image-compressor",
"version": "2.0.0",
"description": "A simple image compressor of javascript",
"main": "index.js",
"scripts": {
"build": "webpack",
"prepublish": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"jsImageCompressor",
"imageToCanvas",
"toBlob"
],
"author": "wuwhs",
"license": "ISC",
"devDependencies": {
"terser-webpack-plugin": "^3.0.8",
"webpack": "^4.44.0",
"webpack-cli": "^3.3.12"
},
"repository": {
"type": "git",
"url": "https://github.com/wuwhs/js-image-compressor"
}
}
================================================
FILE: src/index.js
================================================
var WIN = window
var REGEXP_IMAGE_TYPE = /^image\//
var REGEXP_EXTENSION = /\.\w+$/
var util = {}
var defaultOptions = {
file: null,
quality: 0.8,
convertSize: 2048000,
loose: true,
redressOrientation: true,
}
/**
* 判断是否为函数
* @param {Any} value 任意值
* @returns {Boolean} 判断结果
*/
var isFunc = function (value) {
return typeof value === 'function'
}
/**
* 判断是否为图片类型
* @param {String} value 类型字符串
* @returns {Boolean} 判断结果
*/
var isImageType = function (value) {
return REGEXP_IMAGE_TYPE.test(value)
}
/**
* 图片类型转化为文件拓展名
* @param {String} value
*/
var imageTypeToExtension = function (value) {
var extension = isImageType(value) ? value.substr(6) : ''
if (extension === 'jpeg') {
extension = 'jpg'
}
return '.' + extension
}
/**
* 图片压缩构造函数
* @param {Object} options 相关参数
*/
function ImageCompressor(options) {
options = Object.assign({}, defaultOptions, options)
this.options = options
this.file = options.file
this.image = null
this.ParsedOrientationInfo = null
this.init()
}
var _proto = ImageCompressor.prototype
// WIN.ImageCompressor = ImageCompressor;
export default ImageCompressor
/**
* 初始化
*/
_proto.init = function () {
var _this = this
var file = this.file
var options = this.options
if (!file || !isImageType(file.type)) {
_this.error('请上传图片文件!')
return
}
if (!isImageType(options.mimeType)) {
options.mimeType = file.type
}
util.file2Image(
file,
function (img) {
if (isFunc(_this.beforeCompress)) {
_this.image = img
file.width = img.naturalWidth
file.height = img.naturalHeight
_this.beforeCompress(file)
}
if (file.type === 'image/jpeg' && options.redressOrientation) {
_this.getParsedOrientationInfo(function (info) {
_this.parsedOrientationInfo = info
_this.rendCanvas()
})
} else {
_this.parsedOrientationInfo = {
rotate: 0,
scaleX: 1,
scaleY: 1,
}
_this.rendCanvas()
}
},
_this.error
)
}
/**
* `Canvas` 渲染模块
*/
_proto.rendCanvas = function () {
var _this = this
var options = this.options
var image = this.image
var edge = this.getExpectedEdge()
var dWidth = edge.dWidth
var dHeight = edge.dHeight
var width = edge.width
var height = edge.height
var canvas = util.image2Canvas(
image,
dWidth,
dHeight,
_this.beforeDraw.bind(_this),
_this.afterDraw.bind(_this),
width,
height
)
util.canvas2Blob(
canvas,
function (blob) {
if (blob) {
blob.width = canvas.width
blob.height = canvas.height
}
_this.success(blob)
},
options.quality,
options.mimeType
)
}
/**
* 压缩之前,读取图片之后钩子函数
*/
_proto.beforeCompress = function () {
if (isFunc(this.options.beforeCompress)) {
this.options.beforeCompress(this.file)
}
}
/**
* 获取用户想要输出的边(宽高)
*/
_proto.getExpectedEdge = function () {
var image = this.image
var parsedOrientationInfo = this.parsedOrientationInfo
var rotate = parsedOrientationInfo.rotate
var options = this.options
var naturalWidth = image.naturalWidth
var naturalHeight = image.naturalHeight
var is90DegreesRotated = Math.abs(rotate) % 180 === 90
var temp
if (is90DegreesRotated) {
temp = naturalHeight
naturalHeight = naturalWidth
naturalWidth = temp
}
var aspectRatio = naturalWidth / naturalHeight
var maxWidth = Math.max(options.maxWidth, 0) || Infinity
var maxHeight = Math.max(options.maxHeight, 0) || Infinity
var minWidth = Math.max(options.minWidth, 0) || 0
var minHeight = Math.max(options.minHeight, 0) || 0
var width = Math.max(options.width, 0) || naturalWidth
var height = Math.max(options.height, 0) || naturalHeight
if (maxWidth < Infinity && maxHeight < Infinity) {
if (maxHeight * aspectRatio > maxWidth) {
maxHeight = maxWidth / aspectRatio
} else {
maxWidth = maxHeight * aspectRatio
}
} else if (maxWidth < Infinity) {
maxHeight = maxWidth / aspectRatio
} else if (maxHeight < Infinity) {
maxWidth = maxHeight * aspectRatio
}
if (minWidth > 0 && minHeight > 0) {
if (minHeight * aspectRatio > minWidth) {
minHeight = minWidth / aspectRatio
} else {
minWidth = minHeight * aspectRatio
}
} else if (minWidth > 0) {
minHeight = minWidth / aspectRatio
} else if (minHeight > 0) {
minWidth = minHeight * aspectRatio
}
if (height * aspectRatio > width) {
height = width / aspectRatio
} else {
width = height * aspectRatio
}
width = Math.floor(Math.min(Math.max(width, minWidth), maxWidth))
height = Math.floor(Math.min(Math.max(height, minHeight), maxHeight))
var dWidth = width
var dHeight = height
if (is90DegreesRotated) {
temp = dHeight
dHeight = dWidth
dWidth = temp
}
return {
dWidth: dWidth,
dHeight: dHeight,
width: width,
height: height,
}
}
/**
* 获取转化后的方向信息
* @param {Function} callback 回调函数
*/
_proto.getParsedOrientationInfo = function (callback) {
var _this = this
this.getOrientation(function (orientation) {
if (isFunc(callback)) {
callback(_this.parseOrientation(orientation))
}
})
}
/**
* 获取方向
* @param {Function} callback 回调函数
*/
_proto.getOrientation = function (callback) {
var _this = this
util.file2ArrayBuffer(this.file, function (result) {
if (isFunc(callback)) {
callback(_this.resetAndGetOrientation(result))
}
})
}
/**
* 从 `array buffer` 提取 `orientation` 值
* @param {ArrayBuffer} arrayBuffer - array buffer
* @returns {number} `orientation` 值
*/
_proto.resetAndGetOrientation = function (arrayBuffer) {
var dataView = new DataView(arrayBuffer)
var orientation
// 当图像没有正确的 Exif 信息时忽略 range error
try {
var littleEndian
var app1Start
var ifdStart
// JPEG 图片 (以 0xFFD8 开头)
if (dataView.getUint8(0) === 0xff && dataView.getUint8(1) === 0xd8) {
var length = dataView.byteLength
var offset = 2
while (offset + 1 < length) {
if (
dataView.getUint8(offset) === 0xff &&
dataView.getUint8(offset + 1) === 0xe1
) {
app1Start = offset
break
}
offset += 1
}
}
if (app1Start) {
var exifIDCode = app1Start + 4
var tiffOffset = app1Start + 10
if (util.getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
var endianness = dataView.getUint16(tiffOffset)
littleEndian = endianness === 0x4949
if (littleEndian || endianness === 0x4d4d) {
if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002a) {
var firstIFDOffset = dataView.getUint32(
tiffOffset + 4,
littleEndian
)
if (firstIFDOffset >= 0x00000008) {
ifdStart = tiffOffset + firstIFDOffset
}
}
}
}
}
if (ifdStart) {
var length = dataView.getUint16(ifdStart, littleEndian)
var offset
var i
for (i = 0; i < length; i += 1) {
offset = ifdStart + i * 12 + 2
if (
dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */
) {
// 8 是当前标签值的偏移
offset += 8
// 获取原始方向值
orientation = dataView.getUint16(offset, littleEndian)
// 以其默认值覆盖方向
dataView.setUint16(offset, 1, littleEndian)
break
}
}
}
} catch (e) {
console.error(e)
orientation = 1
}
return orientation
}
/**
* 逆向转化Exif获取到图片的方向信息
* @param {Number} orientation 方向标识
* @returns {Object} 转化结果
*/
_proto.parseOrientation = function (orientation) {
var rotate = 0
var scaleX = 1
var scaleY = 1
switch (orientation) {
// 水平翻转
case 2:
scaleX = -1
break
// 向左旋转180°
case 3:
rotate = -180
break
// 垂直翻转
case 4:
scaleY = -1
break
// 垂直翻转并且向右旋转90°
case 5:
rotate = 90
scaleY = -1
break
// 向右旋转90°
case 6:
rotate = 90
break
// 水平翻转并且向右旋转90°
case 7:
rotate = 90
scaleX = -1
break
// 向左旋转90°
case 8:
rotate = -90
break
default:
break
}
return {
rotate: rotate,
scaleX: scaleX,
scaleY: scaleY,
}
}
/**
* 画布上绘制图片前的一些操作:设置画布一些样式,支持用户自定义
* @param {CanvasRenderingContext2D} ctx Canvas 对象的上下文
* @param {HTMLCanvasElement} canvas Canvas 对象
*/
_proto.beforeDraw = function (ctx, canvas) {
var parsedOrientationInfo = this.parsedOrientationInfo
var rotate = parsedOrientationInfo.rotate
var scaleX = parsedOrientationInfo.scaleX
var scaleY = parsedOrientationInfo.scaleY
var file = this.file
var options = this.options
var fillStyle = 'transparent'
var width = canvas.width
var height = canvas.height
// `png` 格式图片大小超过 `convertSize`, 转化成 `jpeg` 格式
if (file.size > options.convertSize && options.mimeType === 'image/png') {
fillStyle = '#fff'
options.mimeType = 'image/jpeg'
}
// 覆盖默认的黑色填充色
ctx.fillStyle = fillStyle
ctx.fillRect(0, 0, width, height)
// 用户自定义画布样式
if (isFunc(options.beforeDraw)) {
options.beforeDraw.call(this, ctx, canvas)
}
ctx.save()
switch (rotate) {
case 90:
ctx.translate(width, 0)
break
case -90:
ctx.translate(0, height)
break
case -180:
ctx.translate(width, height)
break
}
ctx.rotate((rotate * Math.PI) / 180)
ctx.scale(scaleX, scaleY)
}
/**
* 画布上绘制图片后的一些操作:支持用户自定义
* @param {CanvasRenderingContext2D} ctx Canvas 对象的上下文
* @param {HTMLCanvasElement} canvas Canvas 对象
*/
_proto.afterDraw = function (ctx, canvas) {
var options = this.options
// 用户自定义画布样式
if (isFunc(options.afterDraw)) {
options.afterDraw.call(this, ctx, canvas)
}
}
/**
* 错误触发函数
* @param {String} msg 错误消息
*/
_proto.error = function (msg) {
var options = this.options
if (isFunc(options.error)) {
options.error.call(this, msg)
} else {
throw new Error(msg)
}
}
/**
* 成功触发函数
* @param {File|Blob} result `Blob` 对象
*/
_proto.success = function (result) {
var options = this.options
var file = this.file
var image = this.image
var edge = this.getExpectedEdge()
var naturalHeight = image.naturalHeight
var naturalWidth = image.naturalWidth
if (result && result.size) {
// 在非宽松模式下,用户期待的输出宽高没有大于源图片的宽高情况下,输出文件大小大于源文件,返回源文件
if (
!options.loose &&
result.size > file.size &&
!(edge.width > naturalWidth || edge.height > naturalHeight)
) {
console.warn('当前设置的是非宽松模式,压缩结果大于源图片,输出源图片')
result = file
} else {
var date = new Date()
result.lastModified = date.getTime()
result.lastModifiedDate = date
result.name = file.name
// 文件 `name` 属性中的后缀转化成实际后缀
if (result.name && result.type !== file.type) {
result.name = result.name.replace(
REGEXP_EXTENSION,
imageTypeToExtension(result.type)
)
}
}
} else {
// 在某些情况下压缩后文件为 `null`,返回源文件
console.warn('图片压缩出了点意外,输出源图片')
result = file
}
if (isFunc(options.success)) {
options.success.call(this, result)
}
}
/**
* 文件转化成 `data URL` 字符串
* @param {File} file 文件对象
* @param {Function} callback 成功回调函数
* @param {Function} error 取消回调函数
*/
util.file2DataUrl = function (file, callback, error) {
var reader = new FileReader()
reader.onload = function () {
callback(reader.result)
}
reader.onerror = function () {
if (isFunc(error)) {
error('读取文件失败!')
}
}
reader.readAsDataURL(file)
}
/**
* 文件转化成 `ArrayBuffer`
* @param {File} file 文件对象
* @param {Function} callback 成功回调函数
* @param {Function} error 取消回调函数
*/
util.file2ArrayBuffer = function (file, callback, error) {
var reader = new FileReader()
reader.onload = function (ev) {
callback(ev.target.result)
}
reader.onerror = function () {
if (isFunc(error)) {
error('读取文件失败!')
}
}
reader.readAsArrayBuffer(file)
}
/**
* 从 data view 中的字符代码获取字符字符串
* @param {DataView} dataView - The data view for read.
* @param {number} start - 起始位置
* @param {number} length - 读取长度
* @returns {string} 读取结果
*/
util.getStringFromCharCode = function (dataView, start, length) {
var str = ''
var i
length += start
for (i = start; i < length; i += 1) {
str += String.fromCharCode(dataView.getUint8(i))
}
return str
}
/**
* 文件转化成 `Image` 对象
* @param {File} file 文件对象
* @param {Function} callback 成功回调函数
* @param {Function} error 错误回调函数
*/
util.file2Image = function (file, callback, error) {
var image = new Image()
var URL = WIN.URL || WIN.webkitURL
if (
WIN.navigator &&
/(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(WIN.navigator.userAgent)
) {
// 修复IOS上webkit内核浏览器抛出错误 `The operation is insecure` 问题
image.crossOrigin = 'anonymous'
}
image.alt = file.name
image.onerror = function () {
if (isFunc(error)) {
error('图片加载错误!')
}
}
if (URL) {
var url = URL.createObjectURL(file)
image.onload = function () {
callback(image)
URL.revokeObjectURL(url)
}
image.src = url
} else {
this.file2DataUrl(
file,
function (dataUrl) {
image.onload = function () {
callback(image)
}
image.src = dataUrl
},
error
)
}
}
/**
* `url` 转化成 `Image` 对象
* @param {File} url `url`
* @param {Function} callback 成功回调函数
* @param {Function} error 失败回调函数
*/
util.url2Image = function (url, callback, error) {
var image = new Image()
image.src = url
image.onload = function () {
callback(image)
}
image.onerror = function () {
if (isFunc(error)) {
error('图片加载错误!')
}
}
}
/**
* `Image` 转化成 `Canvas` 对象
* @param {File} image `Image` 对象
* @param {Number} dWidth 目标宽度
* @param {Number} dHeight 目标高度
* @param {Function} beforeDraw 在图片绘画之前的回调函数
* @param {Function} afterDraw 在图片绘画之后的回调函数
* @param {Number} width 宽
* @param {Number} height 高
* @return {HTMLCanvasElement} `Canvas` 对象
*/
util.image2Canvas = function (
image,
dWidth,
dHeight,
beforeDraw,
afterDraw,
width,
height
) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
canvas.width = width || image.naturalWidth
canvas.height = height || image.naturalHeight
if (isFunc(beforeDraw)) {
beforeDraw(ctx, canvas)
}
ctx.save()
ctx.drawImage(image, 0, 0, dWidth, dHeight)
ctx.restore()
if (isFunc(afterDraw)) {
afterDraw(ctx, canvas)
}
return canvas
}
/**
* `Canvas` 转化成 `data URL` 对象
* @param {File} file `Canvas` 对象
* @param {Float} quality 输出质量比例
* @return {String} `data URL` 字符串
*/
util.canvas2DataUrl = function (canvas, quality, type) {
return canvas.toDataURL(type || 'image/jpeg', quality)
}
/**
* `data URL` 转化成 `Image` 对象
* @param {File} dataUrl `data URL` 字符串
* @param {Function} callback 成功回调函数
* @param {Function} error 失败回调函数
*/
util.dataUrl2Image = function (dataUrl, callback, error) {
var image = new Image()
image.onload = function () {
callback(image)
}
image.error = function () {
if (isFunc(error)) {
error('图片加载错误!')
}
}
image.src = dataUrl
}
/**
* `data URL` 转化成 `Blob` 对象
* @param {File} dataUrl `data URL` 字符串
* @param {String} type `mime`
* @return {Blob} `Blob` 对象
*/
util.dataUrl2Blob = function (dataUrl, type) {
var data = dataUrl.split(',')[1]
var mimePattern = /^data:(.*?)(;base64)?,/
var mime = dataUrl.match(mimePattern)[1]
var binStr = atob(data)
var len = data.length
var arr = new Uint8Array(len)
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i)
}
return new Blob([arr], { type: type || mime })
}
/**
* `Blob` 对象转化成 `data URL`
* @param {Blob} blob `Blob` 对象
* @param {Function} callback 成功回调函数
* @param {Function} error 失败回调函数
*/
util.blob2DataUrl = function (blob, callback, error) {
this.file2DataUrl(blob, callback, error)
}
/**
* `Blob`对象 转化成 `Image` 对象
* @param {Blob} blob `Blob` 对象
* @param {Function} callback 成功回调函数
* @param {Function} callback 失败回调函数
*/
util.blob2Image = function (blob, callback, error) {
this.file2Image(blob, callback, error)
}
/**
* `Canvas` 对象转化成 `Blob` 对象
* @param {HTMLCanvasElement} canvas `Canvas` 对象
* @param {Function} callback 回调函数
* @param {Float} quality 输出质量比例
* @param {String} type `mime`
*/
util.canvas2Blob = function (canvas, callback, quality, type) {
var _this = this
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var dataUrl = this.toDataURL(type, quality)
callback(_this.dataUrl2Blob(dataUrl))
},
})
}
canvas.toBlob(
function (blob) {
callback(blob)
},
type || 'image/jpeg',
quality || 0.8
)
}
/**
* 文件上传
* @param {String} url 上传路径
* @param {File} file 文件对象
* @param {Function} callback 回调函数
*/
util.upload = function (url, file, callback) {
var xhr = new XMLHttpRequest()
var fd = new FormData()
fd.append('file', file)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 上传成功
callback && callback(xhr.responseText)
} else {
throw new Error(xhr)
}
}
xhr.open('POST', url, true)
xhr.send(fd)
}
for (var key in util) {
if (util.hasOwnProperty(key)) {
ImageCompressor[key] = util[key]
}
}
================================================
FILE: webpack.config.js
================================================
const TerserWebpackPlugin = require('terser-webpack-plugin')
const path = require('path')
module.exports = {
mode: 'none',
entry: {
'image-compressor': './src/index.js',
'image-compressor.min': './src/index.js',
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
library: 'ImageCompressor',
libraryExport: 'default',
libraryTarget: 'umd',
},
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin({
include: /\.min\./,
}),
],
},
}