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
<script src="../dist/image-compressor.js"></script>
```
### 简单使用
你可以只传入待压缩图片对象,其他参数都是非必须的,插件按照默认参数自动完成图片压缩处理。不过这样输出的压缩图片符合以下特征:
- 默认按照 `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
<script src="../dist/image-compressor.js"></script>
```
### 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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js-image-compressor</title>
<link rel="stylesheet" href="../index.css">
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div class="drop-content" @drop="dropFile" @dragenter="dragFileEnter" @dragover="drageFileOver">
<input id="file-elem" type="file" accept="image/*" @change="inputChange">
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>mime 类型:</span>
<select v-model="mimeType">
<option value="auto">auto</option>
<option value="image/png">image/png</option>
<option value="image/jpeg">image/jpeg</option>
<option value="image/webp">image/webp</option>
<option value="image/gif">image/gif</option>
</select>
</p>
<p>
<span>maxWidth:</span>
<input type="text" v-model="maxWidth">
</p>
<p>
<span>maxHeight:</span>
<input type="text" v-model="maxHeight">
</p>
<p>
<span>width:</span>
<input type="text" v-model="width">
</p>
<p>
<span>height:</span>
<input type="text" v-model="height">
</p>
<p>
<span>minWidth:</span>
<input type="text" v-model="minWidth">
</p>
<p>
<span>minHeigth:</span>
<input type="text" v-model="minHeight">
</p>
<p>
<span>png转jpeg阈值:</span>
<select v-model="convertSize">
<option :value="1024000">1mb</option>
<option :value="2048000">2mb</option>
<option :value="5120000">5mb</option>
<option :value="10240000">10mb</option>
<option :value="Infinity">Infinity</option>
</select>
</p>
<p>
<span>宽松模式:</span>
<select v-model="loose">
<option :value="true">是</option>
<option :value="false">否</option>
</select>
</p>
<p>
<span>矫正jpeg方向:</span>
<select v-model="redressOrientation">
<option :value="true">是</option>
<option :value="false">否</option>
</select>
</p>
<p>
<span>压缩率:</span>
<input type="number" max="1" min="0.05" step="0.05" v-model="quality">
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>grayscale demo</title>
<link rel="stylesheet" href="../index.css">
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div class="drop-content" @drop="dropFile" @dragenter="dragFileEnter" @dragover="drageFileOver">
<input id="file-elem" type="file" accept="image/*" @change="inputChange">
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>压缩率:</span>
<input type="number" max="1" min="0.05" step="0.05" v-model="quality">
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>simple demo</title>
<link rel="stylesheet" href="../index.css">
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div class="drop-content" @drop="dropFile" @dragenter="dragFileEnter" @dragover="drageFileOver">
<input id="file-elem" type="file" accept="image/*" @change="inputChange">
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>压缩率:</span>
<input type="number" max="1" min="0.05" step="0.05" v-model="quality">
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watermark demo</title>
<link rel="stylesheet" href="../index.css">
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div class="drop-content" @drop="dropFile" @dragenter="dragFileEnter" @dragover="drageFileOver">
<input id="file-elem" type="file" accept="image/*" @change="inputChange">
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>压缩率:</span>
<input type="number" max="1" min="0.05" step="0.05" v-model="quality">
</p>
<p>
<span>水印:</span>
<input type="text" v-model="watermarkText">
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>js-image-compressor</title>
<link rel="stylesheet" href="../index.css" />
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div
class="drop-content"
@drop="dropFile"
@dragenter="dragFileEnter"
@dragover="drageFileOver"
>
<input
id="file-elem"
type="file"
accept="image/*"
@change="inputChange"
/>
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>mime 类型:</span>
<select v-model="mimeType">
<option value="auto">auto</option>
<option value="image/png">image/png</option>
<option value="image/jpeg">image/jpeg</option>
<option value="image/webp">image/webp</option>
<option value="image/gif">image/gif</option>
</select>
</p>
<p>
<span>maxWidth:</span>
<input type="text" v-model="maxWidth" />
</p>
<p>
<span>maxHeight:</span>
<input type="text" v-model="maxHeight" />
</p>
<p>
<span>width:</span>
<input type="text" v-model="width" />
</p>
<p>
<span>height:</span>
<input type="text" v-model="height" />
</p>
<p>
<span>minWidth:</span>
<input type="text" v-model="minWidth" />
</p>
<p>
<span>minHeigth:</span>
<input type="text" v-model="minHeight" />
</p>
<p>
<span>png转jpeg阈值:</span>
<select v-model="convertSize">
<option :value="1024000">1mb</option>
<option :value="2048000">2mb</option>
<option :value="5120000">5mb</option>
<option :value="10240000">10mb</option>
<option :value="Infinity">Infinity</option>
</select>
</p>
<p>
<span>宽松模式:</span>
<select v-model="loose">
<option :value="true">是</option>
<option :value="false">否</option>
</select>
</p>
<p>
<span>矫正jpeg方向:</span>
<select v-model="redressOrientation">
<option :value="true">是</option>
<option :value="false">否</option>
</select>
</p>
<p>
<span>压缩率:</span>
<input
type="number"
max="1"
min="0.05"
step="0.05"
v-model="quality"
/>
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>grayscale demo</title>
<link rel="stylesheet" href="../index.css" />
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div
class="drop-content"
@drop="dropFile"
@dragenter="dragFileEnter"
@dragover="drageFileOver"
>
<input
id="file-elem"
type="file"
accept="image/*"
@change="inputChange"
/>
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>压缩率:</span>
<input
type="number"
max="1"
min="0.05"
step="0.05"
v-model="quality"
/>
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>simple demo</title>
<link rel="stylesheet" href="../index.css" />
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div
class="drop-content"
@drop="dropFile"
@dragenter="dragFileEnter"
@dragover="drageFileOver"
>
<input
id="file-elem"
type="file"
accept="image/*"
@change="inputChange"
/>
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>压缩率:</span>
<input
type="number"
max="1"
min="0.05"
step="0.05"
v-model="quality"
/>
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>watermark demo</title>
<link rel="stylesheet" href="../index.css" />
</head>
<body>
<div id="app">
<div class="row">
<div class="column upload">
<div
class="drop-content"
@drop="dropFile"
@dragenter="dragFileEnter"
@dragover="drageFileOver"
>
<input
id="file-elem"
type="file"
accept="image/*"
@change="inputChange"
/>
<span>拖入文件或</span><label for="file-elem">点击选择文件</label>
</div>
</div>
<div class="column">
<div>
<h3>设置参数</h3>
<p>
<span>压缩率:</span>
<input
type="number"
max="1"
min="0.05"
step="0.05"
v-model="quality"
/>
</p>
<p>
<span>水印:</span>
<input type="text" v-model="watermarkText" />
</p>
<p>
<button @click="submit">{{btnText}}</button>
</p>
</div>
</div>
</div>
<div class="row">
<div class="column origin">
<img :src="originImgUrl" />
</div>
<div class="column">
<h3>源图片信息</h3>
<div>
<span>mime类型:{{originMimeType}}</span>
</div>
<div>
<span>尺寸:{{originImgWidth}} x {{originImgHeight}}</span>
</div>
<div>
<span>大小:{{originSize}}</span>
</div>
</div>
</div>
<div class="row">
<div class="column output">
<img :src="outputImgUrl" />
</div>
<div class="column">
<h3>压缩图片信息</h3>
<div>
<span>mime类型:{{outputMimeType}}</span>
</div>
<div>
<span>尺寸:{{outputImgWidth}} x {{outputImgHeight}}</span>
</div>
<div>
<span>大小:{{outputSize}}</span>
</div>
<div>
<span>实际压缩率:{{compressRatio}}</span>
</div>
<div>
<a :href="outputImgUrl" :download="imgName">下载</a>
</div>
</div>
</div>
</div>
<script src="../vue.min.js"></script>
<script src="../../dist/image-compressor.js"></script>
<script src="./main.js"></script>
</body>
</html>
================================================
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\./,
}),
],
},
}
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
SYMBOL INDEX (3 symbols across 2 files)
FILE: dist/image-compressor.js
function __webpack_require__ (line 16) | function __webpack_require__(moduleId) {
function ImageCompressor (line 149) | function ImageCompressor(options) {
FILE: src/index.js
function ImageCompressor (line 47) | function ImageCompressor(options) {
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (117K chars).
[
{
"path": ".gitignore",
"chars": 208,
"preview": ".DS_Store\nnode_modules\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error"
},
{
"path": "README-CN.md",
"chars": 3588,
"preview": "## 简介\n\n`js-image-compressor` 是一个实现轻量级图片压缩的 `javascript` 库,压缩后仅有 `5kb`,在前端页面即可实现对图片的压缩。在提供基本图片压缩功能同时,还暴露出图片处理相关公用方法,以及进行边"
},
{
"path": "README.md",
"chars": 6836,
"preview": "## Introduction\n\n`js-image-compressor` is a `javascript` library that implements lightweight image compression. After co"
},
{
"path": "demo/default/index.html",
"chars": 4041,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "demo/default/main.js",
"chars": 3685,
"preview": "var BTN_OK = '确定';\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n"
},
{
"path": "demo/grayscale/index.html",
"chars": 2094,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "demo/grayscale/main.js",
"chars": 3181,
"preview": "var BTN_OK = '确定';\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n"
},
{
"path": "demo/index.css",
"chars": 803,
"preview": ".row {\n display: flex;\n border-bottom: 1px dashed #999;\n}\n\n.column {\n padding: 20px;\n}\n\n.upload,\n.origin,\n.output {\n "
},
{
"path": "demo/simple/index.html",
"chars": 2091,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "demo/simple/main.js",
"chars": 3005,
"preview": "var BTN_OK = '确定';\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n"
},
{
"path": "demo/watermark/index.html",
"chars": 2208,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "demo/watermark/main.js",
"chars": 3371,
"preview": "var BTN_OK = '确定';\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n"
},
{
"path": "dist/image-compressor.js",
"chars": 21669,
"preview": "(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object"
},
{
"path": "examples/default/index.html",
"chars": 4545,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "examples/default/main.js",
"chars": 3702,
"preview": "var BTN_OK = '确定'\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n "
},
{
"path": "examples/grayscale/index.html",
"chars": 2470,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "examples/grayscale/main.js",
"chars": 3197,
"preview": "var BTN_OK = '确定'\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n "
},
{
"path": "examples/index.css",
"chars": 803,
"preview": ".row {\n display: flex;\n border-bottom: 1px dashed #999;\n}\n\n.column {\n padding: 20px;\n}\n\n.upload,\n.origin,\n.output {\n "
},
{
"path": "examples/simple/index.html",
"chars": 2467,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "examples/simple/main.js",
"chars": 3024,
"preview": "var BTN_OK = '确定'\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n "
},
{
"path": "examples/watermark/index.html",
"chars": 2594,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "examples/watermark/main.js",
"chars": 3382,
"preview": "var BTN_OK = '确定'\n\nnew Vue({\n el: '#app',\n data: function () {\n return {\n file: null,\n btnText: BTN_OK,\n "
},
{
"path": "index.js",
"chars": 173,
"preview": "if (process.env.NODE_ENV === 'production') {\n module.exports = require('./dist/image-compressor.min.js')\n} else {\n mod"
},
{
"path": "package.json",
"chars": 613,
"preview": "{\n \"name\": \"js-image-compressor\",\n \"version\": \"2.0.0\",\n \"description\": \"A simple image compressor of javascript\",\n \""
},
{
"path": "src/index.js",
"chars": 17468,
"preview": "var WIN = window\nvar REGEXP_IMAGE_TYPE = /^image\\//\nvar REGEXP_EXTENSION = /\\.\\w+$/\nvar util = {}\nvar defaultOptions = {"
},
{
"path": "webpack.config.js",
"chars": 546,
"preview": "const TerserWebpackPlugin = require('terser-webpack-plugin')\nconst path = require('path')\n\nmodule.exports = {\n mode: 'n"
}
]
About this extraction
This page contains the full source code of the wuwhs/js-image-compressor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (99.4 KB), approximately 29.6k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.