├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── __tests__
├── __mocks__
│ ├── fileMock.js
│ ├── jsdomMock.js
│ └── styleMock.js
├── setup.js
└── tests
│ └── index.test.js
├── assetsImg
└── screenshot.png
├── example
├── README.md
├── demo.jpg
├── dist
│ ├── build.js
│ ├── demo.jpg
│ ├── index.html
│ └── watermark.png
├── example.css
├── example.js
├── example.less
├── index.html
├── watermark.png
└── webpack.config.js
├── index.d.ts
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.json
├── src
└── index.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "es2015",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "react",
10 | "stage-1"
11 | ],
12 | "plugins": [
13 | "transform-es2015-modules-commonjs",
14 | "transform-object-rest-spread",
15 | "transform-class-properties",
16 | [
17 | "transform-runtime",
18 | {
19 | "helpers": false,
20 | "polyfill": false,
21 | "regenerator": true,
22 | "moduleName": "babel-runtime"
23 | }
24 | ]
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parserOptions": {
3 | "ecmaVersion": 8,
4 | "sourceType": "module",
5 | "ecmaFeatures": {
6 | "jsx": true
7 | }
8 | },
9 | "parser": "babel-eslint",
10 | "plugins": [
11 | "babel",
12 | "react"
13 | ],
14 | "extends": "eslint:recommended",
15 | "env": {
16 | "es6": true,
17 | "browser": true,
18 | "commonjs": true,
19 | },
20 | "globals": {
21 | },
22 | "rules": {
23 | "object-shorthand": "error",
24 | "generator-star-spacing": ["error", "after"],
25 | "camelcase": ["error", {"properties": "never"}],
26 | "eqeqeq": ["error", "smart"],
27 | "linebreak-style": ["error", "unix"],
28 | "new-cap": "error",
29 | "no-array-constructor": "error",
30 | "no-lonely-if": "error",
31 | "no-loop-func": "error",
32 | "no-param-reassign": "error",
33 | "no-sequences": "error",
34 | "no-shadow-restricted-names": "error",
35 | "no-unneeded-ternary": "error",
36 | // "no-unused-expressions": "error",
37 | "no-unused-vars": ["error", {"args": "none"}],
38 | "no-use-before-define": ["error", "nofunc"],
39 | "no-var": "error",
40 | "prefer-arrow-callback": "error",
41 | "prefer-spread": "error",
42 | "prefer-template": "error",
43 | "wrap-iife": ["error", "inside"],
44 | "yoda": ["error", "never"],
45 | "react/jsx-uses-react": "error",
46 | "react/jsx-uses-vars": "error",
47 | "react/jsx-no-undef": ["error", {"allowGlobals": true}],
48 | "react/jsx-no-bind": ["error", {"allowArrowFunctions": true}],
49 | "react/jsx-key": "error",
50 | "react/no-unknown-property": "error",
51 | "react/no-string-refs": "error",
52 | "react/no-direct-mutation-state": "error",
53 | }
54 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | lib
3 | assets
4 | npm-debug.log
5 | .DS_Store
6 | .history
7 | npm-error.log
8 | yarn-error.log
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | yarn-error.log
4 | .DS_Store
5 | dist
6 | example
7 | src
8 | .history
9 | assetsImg
10 | index.html
11 | __tests__
12 | /.*
13 | postcss.config.json
14 | watermark.png
15 | demo.jpg
16 | yarn.lock
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2020 jinke.Li
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-image-process
2 |
3 | [](https://www.npmjs.com/package/react-image-process)
4 | [](https://badge.fury.io/js/react-image-process)
5 | [](https://github.com/facebook/jest)
6 |
7 | > :art: A image process component for react, like compressed image,clip image, add watermarking of image
8 |
9 | [normal version](https://github.com/lijinke666/photo-magician)
10 |
11 | ## Installation
12 |
13 | using `yarn` :
14 |
15 | ```
16 | yarn add react-image-process
17 | ```
18 |
19 | using `npm` :
20 |
21 | ```
22 | npm install react-image-process --save
23 | ```
24 |
25 | ## Screenshots
26 |
27 | 
28 |
29 | ## Example
30 |
31 | online example : [https://lijinke666.github.io/react-image-process/](https://lijinke666.github.io/react-image-process/)
32 |
33 | [Source Code](https://github.com/lijinke666/react-image-process/blob/master/example/example.js)
34 |
35 | ## Usage
36 |
37 | ```jsx
38 | import React from 'react';
39 | import ReactDOM from 'react-dom';
40 | import ReactImageProcess from 'react-image-process';
41 |
42 | const onComplete = data => {
43 | console.log('data:', data);
44 | };
45 |
46 | ReactDOM.render(
47 |
48 |
49 | ,
50 | document.getElementById('root')
51 | );
52 | ```
53 |
54 | Support multiple Images
55 |
56 | ```jsx
57 |
58 |
59 |
60 |
61 | ```
62 |
63 | > rotate
64 |
65 | ```jsx
66 |
67 |
68 |
69 | ```
70 |
71 | > get primary color
72 |
73 | ```jsx
74 | console.log(color)}>
75 |
76 |
77 | ```
78 |
79 | > waterMark
80 |
81 | ```jsx
82 |
91 |
92 |
93 | ```
94 |
95 | ```jsx
96 |
105 |
106 |
107 | ```
108 |
109 | > imageFilter
110 |
111 | ```jsx
112 |
113 |
114 |
115 | ```
116 |
117 | ## API
118 |
119 | | Property | Description | Type | Default |
120 | | ------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------- |
121 | | mode | can be set to `base64` `clip` `compress` `rotate` `waterMark` `filter` `primaryColor` | `string` | `base64` |
122 | | onComplete | The callback after trans complete conversion | function(base64Data){} | `-` |
123 | | outputType | image data output type of `blob` | `dataUrl` | `string` | `dataUrl` |
124 | | scale | When the mode is equal to 'clip', the zoom scale of the image. | `number` | `1.0` |
125 | | coordinate | When the mode is equal to 'clip', coordinate of the image. like `[[x1,y1],[x2,y2]]`, if mode equal to `waterMark` like `[x1,y1]` | `number[]` | `-` |
126 | | quality | When the mode is equal to 'compress', quality of the image. | `number` | `0.92` |
127 | | rotate | When the mode is equal to 'rotate', rotate deg of the image. | `number` | `-` |
128 | | waterMark | When the mode is equal to 'waterMark', can be set to `image` or `text` | `string|ReactNode` | `-` |
129 | | waterMarkType | When the mode is equal to 'waterMark', can be set to `image` or `text` | `string` | `text` |
130 | | fontBold | When the mode is equal to 'waterMark' and waterMark equal to `text` ,the font is bold. | `boolean` | `false` |
131 | | fontSize | When the mode is equal to 'waterMark' and waterMark equal to `text` ,the font size | `number` | `20` |
132 | | fontColor | When the mode is equal to 'waterMark' and waterMark equal to `text` ,the font color | `string` | `rgba(255,255,255,.5)` |
133 | | width | When the mode is equal to 'waterMark' and waterMark equal to `image` ,the water width | `number` | `50` |
134 | | height | When the mode is equal to 'waterMark' and waterMark equal to `image` ,the water height | `number` | `50` |
135 | | opacity | When the mode is equal to 'waterMark' and waterMark equal to `image` ,the water opacity range [0-1] | `number` | `0.5` |
136 | | filterType | When the mode is equal to 'filter', can be set to `vintage` `blackWhite` `relief` `blur` | `string` | `vintage` |
137 |
138 | ## Development
139 |
140 | ```
141 | git clone https://github.com/lijinke666/react-image-process.git
142 | npm install
143 | npm start
144 | ```
145 |
146 | ## Properties
147 |
148 | ```ts
149 | export type ReactImageProcessMode =
150 | | 'base64'
151 | | 'clip'
152 | | 'compress'
153 | | 'rotate'
154 | | 'waterMark'
155 | | 'filter'
156 | | 'primaryColor';
157 |
158 | export type ReactImageProcessWaterMarkType = 'image' | 'text';
159 | export type ReactImageProcessFilterType =
160 | | 'vintage'
161 | | 'blackWhite'
162 | | 'relief'
163 | | 'blur';
164 | export type ReactImageProcessOutputType = 'blob' | 'dataUrl';
165 |
166 | export interface ReactImageProcessProps {
167 | mode: ReactImageProcessMode;
168 | waterMarkType: ReactImageProcessWaterMarkType;
169 | filterType: ReactImageProcessFilterType;
170 | outputType: ReactImageProcessOutputType;
171 | waterMark: string;
172 | rotate: number;
173 | quality: number;
174 | coordinate: number[];
175 | width: number;
176 | height: number;
177 | opacity: number;
178 | fontColor: number;
179 | fontSize: number;
180 | fontBold: number;
181 | onComplete: (data: Blob | string) => void;
182 | }
183 | ```
184 |
185 | ## License
186 |
187 | [MIT](https://github.com/lijinke666/react-image-process/blob/master/LICENSE)
188 |
--------------------------------------------------------------------------------
/__tests__/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | export default "test-file-stub";
2 |
--------------------------------------------------------------------------------
/__tests__/__mocks__/jsdomMock.js:
--------------------------------------------------------------------------------
1 | /*eslint-disable no-console */
2 | const JSDOMEnvironment = require("jest-environment-jsdom");
3 |
4 | module.exports = class CustomizedJSDomEnvironment extends JSDOMEnvironment {
5 | constructor(config) {
6 | const _config = Object.assign(config, {
7 | testEnvironmentOptions: {
8 | beforeParse(window) {
9 | window.document.childNodes.length === 0;
10 | window.alert = msg => {
11 | console.log(msg);
12 | };
13 | window.matchMedia = () => ({
14 | addListener: () => {},
15 | removeListener: () => {}
16 | });
17 | window.scrollTo = () => {};
18 | }
19 | }
20 | });
21 | super(_config);
22 | this.global.jsdom = this.dom;
23 | }
24 |
25 | teardown() {
26 | this.global.jsdom = null;
27 | return super.teardown();
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/__tests__/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/__tests__/setup.js:
--------------------------------------------------------------------------------
1 | /*eslint-disable no-console */
2 | const Enzyme = require("enzyme");
3 | const Adapter = require("enzyme-adapter-react-16");
4 |
5 | const { JSDOM } = require("jsdom");
6 |
7 | const jsdom = new JSDOM("
");
8 | const { window } = jsdom;
9 |
10 | function copyProps(src, target) {
11 | const props = Object.getOwnPropertyNames(src)
12 | .filter(prop => typeof target[prop] === "undefined")
13 | .reduce(
14 | (result, prop) => ({
15 | ...result,
16 | [prop]: Object.getOwnPropertyDescriptor(src, prop)
17 | }),
18 | {}
19 | );
20 | Object.defineProperties(target, props);
21 | }
22 |
23 | global.window = window;
24 | global.document = window.document;
25 | global.navigator = {
26 | userAgent: "node.js"
27 | };
28 | copyProps(window, global);
29 |
30 | window.alert = msg => {
31 | console.log(msg);
32 | };
33 | window.matchMedia = () => ({});
34 | window.scrollTo = () => {};
35 |
36 | Enzyme.configure({ adapter: new Adapter() });
37 |
--------------------------------------------------------------------------------
/__tests__/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import assert from "power-assert";
3 | import { shallow, mount } from "enzyme";
4 | import ReactImageProcess from "../../src";
5 | import watermark from "../../example/watermark.png"
6 | import img from "../../example/demo.jpg"
7 |
8 | describe("ReactImageProcess", () => {
9 | it("should render a components", () => {
10 | const wrapper = mount(
11 |
12 | );
13 | assert(wrapper.find(".react-image-process-base64").length === 1);
14 | assert(wrapper.find(".text-class-name").length >= 1);
15 | });
16 | it("should render mode == base64", () => {
17 | const wrapper = mount();
18 | assert(wrapper.props().mode === "base64");
19 | wrapper.setProps({ mode: "clip" });
20 | assert(wrapper.props().mode === "clip");
21 | })
22 | });
23 |
--------------------------------------------------------------------------------
/assetsImg/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lijinke666/react-image-process/abf8db4b81a22cab2a12c2786718ce0029696401/assetsImg/screenshot.png
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # react-image-process
2 | ### a example
3 |
4 | ```
5 | $ npm run demo :)
6 | ```
7 |
--------------------------------------------------------------------------------
/example/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lijinke666/react-image-process/abf8db4b81a22cab2a12c2786718ce0029696401/example/demo.jpg
--------------------------------------------------------------------------------
/example/dist/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lijinke666/react-image-process/abf8db4b81a22cab2a12c2786718ce0029696401/example/dist/demo.jpg
--------------------------------------------------------------------------------
/example/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | react-image-process
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/dist/watermark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lijinke666/react-image-process/abf8db4b81a22cab2a12c2786718ce0029696401/example/dist/watermark.png
--------------------------------------------------------------------------------
/example/example.css:
--------------------------------------------------------------------------------
1 | #root,
2 | body,
3 | html {
4 | height: 100%;
5 | }
6 | body,
7 | html {
8 | margin: 0;
9 | padding: 0;
10 | }
11 | body {
12 | background: #f0f2f5;
13 | padding: 40px;
14 | }
15 | h1 {
16 | font-weight: 400;
17 | }
18 | h2 {
19 | font-weight: 300;
20 | margin-top: 30px;
21 | }
22 | @media (max-width: 768px) {
23 | h2 {
24 | text-align: center;
25 | }
26 | }
27 | img.example-img {
28 | display: block;
29 | max-width: 100%;
30 | margin: 10px 0;
31 | }
32 | @media (max-width: 768px) {
33 | img.example-img {
34 | margin: 10px auto;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/example.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import ReactImageProcess from '../src';
4 | import swal from 'sweetalert';
5 | import { name } from '../package.json';
6 |
7 | import demoImg from './demo.jpg';
8 | import waterMark from './watermark.png';
9 |
10 | import './example.less';
11 |
12 | const onComplete = data => {
13 | console.log('data:', data);
14 | };
15 |
16 | class Demo extends React.PureComponent {
17 | constructor(props) {
18 | super(props);
19 | }
20 | state = {
21 | primaryColor: 'transparent'
22 | };
23 | getPrimaryColorComplete = primaryColor => {
24 | console.log('primaryColor:', primaryColor);
25 | this.setState({ primaryColor });
26 | };
27 | render() {
28 | const { primaryColor } = this.state;
29 | return (
30 |
31 |
40 |
41 |
42 | Base Image
43 |
44 |
45 | base64
46 |
51 |
56 | swal({
57 | text: `
58 | {
59 | mode:'base64'
60 | }
61 | `
62 | })
63 | }
64 | />
65 |
66 |
67 | clip
68 |
73 |
78 | swal({
79 | text: `
80 | {
81 | mode:'clip',
82 | scale:1,
83 | coordinate:[[200, 200], [300, 300]]
84 | }
85 | `
86 | })
87 | }
88 | />
89 |
90 |
95 |
100 | swal({
101 | text: `
102 | {
103 | mode:'clip',
104 | scale:2,
105 | coordinate:[[200, 200], [600, 600]]}
106 | `
107 | })
108 | }
109 | />
110 |
111 |
112 | compress
113 |
114 |
119 | swal({
120 | text: `
121 | {
122 | mode:'compress',
123 | quality:0.1
124 | }
125 | `
126 | })
127 | }
128 | />
129 |
130 |
131 |
136 | swal({
137 | text: `
138 | {
139 | mode:'compress',
140 | quality:0.01
141 | }
142 | `
143 | })
144 | }
145 | />
146 |
147 |
148 | rotate
149 |
150 |
155 | swal({
156 | text: `
157 | {
158 | mode:'rotate',
159 | rotate:30
160 | }`
161 | })
162 | }
163 | />
164 |
165 |
166 | primaryColor
167 |
171 |
177 | swal({
178 | text: `
179 | {
180 | mode:'primaryColor'
181 | }`
182 | })
183 | }
184 | />
185 |
186 | {primaryColor}
187 |
188 | waterMark
189 |
198 |
203 | swal({
204 | text: `
205 | {
206 | mode:'waterMark',
207 | waterMarkType:'image'
208 | waterMark={waterMark}
209 | width:60
210 | height:60
211 | opacity:0.8
212 | coordinate:[330, 300]
213 | }
214 | `
215 | })
216 | }
217 | />
218 |
219 |
228 |
233 | swal({
234 | text: `
235 | {
236 | mode:'waterMark',
237 | waterMarkType:'text'.
238 | waterMark={${name}}.
239 | fontBold:false,
240 | fontSize:30
241 | fontColor:"#396"
242 | coordinate:[10,20]
243 | }
244 | `
245 | })
246 | }
247 | />
248 |
249 |
250 | imageFilter
251 |
252 |
257 | swal({
258 | text: `
259 | {
260 | mode:'filter',
261 | filterType:'vintage'
262 | }
263 | `
264 | })
265 | }
266 | />
267 |
268 |
269 |
274 | swal({
275 | text: `
276 | {
277 | mode:'filter',
278 | filterType:'blackWhite'
279 | }
280 | `
281 | })
282 | }
283 | />
284 |
285 |
286 |
291 | swal({
292 | text: `
293 | {
294 | mode:'filter',
295 | filterType:'relief'
296 | }
297 | `
298 | })
299 | }
300 | />
301 |
302 |
303 |
308 | swal({
309 | text: `
310 | {
311 | mode:'filter',
312 | filterType:'blur'
313 | }
314 | `
315 | })
316 | }
317 | />
318 |
319 |
320 | );
321 | }
322 | }
323 |
324 | ReactDOM.render(, document.getElementById('root'));
325 |
--------------------------------------------------------------------------------
/example/example.less:
--------------------------------------------------------------------------------
1 | #root,body,html{
2 | height:100%
3 | }
4 | body,html{
5 | margin:0;
6 | padding: 0;
7 | }
8 | body{
9 | background: #f0f2f5;
10 | padding: 40px
11 | }
12 | h1{
13 | font-weight: 400;
14 | }
15 | h2{
16 | font-weight: 300;
17 | margin-top:30px;
18 | @media (max-width:768px) {
19 | text-align: center;
20 | }
21 | }
22 | img.example-img{
23 | display: block;
24 | max-width:100%;
25 | margin:10px 0;
26 | @media (max-width:768px) {
27 | margin:10px auto;
28 | }
29 | }
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | react-image-process
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/watermark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lijinke666/react-image-process/abf8db4b81a22cab2a12c2786718ce0029696401/example/watermark.png
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | const PORT = 8083
6 |
7 | module.exports = env => {
8 | const mode = (env && env.mode) || 'development'
9 | const options = {
10 | mode,
11 | entry: path.join(__dirname, '../example/example.js'),
12 | output: {
13 | path: path.join(__dirname, '../example/dist'),
14 | filename: 'build.js',
15 | publicPath: mode === 'development' ? '' : './example/dist/'
16 | },
17 | //模块加载器
18 | module: {
19 | rules: [
20 | {
21 | test: /\.js[x]?$/,
22 | use: [
23 | {
24 | loader: 'babel-loader'
25 | }
26 | ],
27 | exclude: '/node_modules/'
28 | },
29 | {
30 | test: /\.less$/,
31 | use: [
32 | { loader: 'style-loader' },
33 | {
34 | loader: 'css-loader',
35 | options: { minimize: false, sourceMap: true }
36 | },
37 | { loader: 'less-loader', options: { sourceMap: true } }
38 | ]
39 | },
40 | {
41 | test: /\.css$/,
42 | use: [
43 | { loader: 'style-loader' }, //loader 倒序执行 先执行 less-laoder
44 | {
45 | loader: 'css-loader',
46 | options: { minimize: false, sourceMap: true }
47 | }
48 | ]
49 | },
50 | {
51 | test: /\.(jpg|jpeg|png|gif|cur|ico)$/,
52 | use: [
53 | {
54 | loader: 'file-loader',
55 | options: {
56 | name: '[name].[ext]' //遇到图片 生成一个images文件夹 名字.后缀的图片
57 | }
58 | }
59 | ]
60 | },
61 | {
62 | test: /\.(eot|ttf|svg|woff|woff2)$/,
63 | use: [
64 | {
65 | loader: 'file-loader',
66 | options: {
67 | name: 'fonts/[name][hash:8].[ext]'
68 | }
69 | }
70 | ]
71 | }
72 | ]
73 | },
74 | //自动补全后缀
75 | resolve: {
76 | enforceExtension: false,
77 | extensions: ['.js', '.jsx', '.json'],
78 | modules: [path.resolve('src'), path.resolve('.'), 'node_modules']
79 | },
80 | devServer: {
81 | contentBase: path.join(__dirname),
82 | inline: true,
83 | port: PORT,
84 | publicPath: '/dist/',
85 | historyApiFallback: true,
86 | stats: {
87 | color: true,
88 | errors: true,
89 | version: true,
90 | warnings: true,
91 | progress: true
92 | }
93 | },
94 | plugins: [
95 | new webpack.LoaderOptionsPlugin({
96 | options: {
97 | chunksSortMode: 'none'
98 | }
99 | }),
100 | new HtmlWebpackPlugin({
101 | title: 'demo',
102 | filename: 'index.html',
103 | template: path.resolve(__dirname, 'index.html'), //模板文件
104 | hash: true //添加hash码
105 | })
106 | ]
107 | }
108 | return options
109 | }
110 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export type ReactImageProcessMode =
4 | | 'base64'
5 | | 'clip'
6 | | 'compress'
7 | | 'rotate'
8 | | 'waterMark'
9 | | 'filter'
10 | | 'primaryColor';
11 |
12 | export type ReactImageProcessWaterMarkType = 'image' | 'text';
13 | export type ReactImageProcessFilterType =
14 | | 'vintage'
15 | | 'blackWhite'
16 | | 'relief'
17 | | 'blur';
18 | export type ReactImageProcessOutputType = 'blob' | 'dataUrl';
19 |
20 | export interface ReactImageProcessProps {
21 | mode?: ReactImageProcessMode;
22 | waterMarkType?: ReactImageProcessWaterMarkType;
23 | filterType?: ReactImageProcessFilterType;
24 | outputType?: ReactImageProcessOutputType;
25 | waterMark?: string;
26 | rotate?: number;
27 | quality?: number;
28 | coordinate?: number[];
29 | width?: number;
30 | height?: number;
31 | opacity?: number;
32 | fontColor?: number;
33 | fontSize?: number;
34 | fontBold?: number;
35 | onComplete?: (data: Blob | string) => void;
36 | }
37 |
38 | export default class ReactImageProcess extends React.PureComponent<
39 | ReactImageProcessProps,
40 | any
41 | > {}
42 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | react-image-process
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-image-process",
3 | "version": "0.2.4",
4 | "description": "a image process component for react",
5 | "main": "lib/index.js",
6 | "typing": "index.d.ts",
7 | "scripts": {
8 | "start": "yarn demo",
9 | "test": "jest --no-cache __tests__/tests",
10 | "clean": "rimraf lib && rimraf assets",
11 | "auto": "postcss -u autoprefixer -c postcss.config.json --no-map -o assets/index.css assets/index.css",
12 | "build:css": "lessc src/index.less assets/index.css && yarn auto",
13 | "build:js": "babel src -d lib",
14 | "build": "cross-env NODE_ENV=production && yarn run clean && yarn build:js",
15 | "build:demo": "cross-env NODE_ENV=production rimraf example/dist && webpack --env.mode=production --progress --config ./example/webpack.config.js",
16 | "demo": "cross-env NODE_ENV=development && webpack-dev-server --progress --inline --hot --config ./example/webpack.config.js",
17 | "prepare": "yarn build",
18 | "precommit": "lint-staged",
19 | "lint": "prettier --write \"src/**/*.js\" && eslint_d --fix src"
20 | },
21 | "pre-commit": "lint",
22 | "lint-staged": {
23 | "src/**/*.js": [
24 | "prettier --write",
25 | "eslint_d --fix",
26 | "git add"
27 | ]
28 | },
29 | "author": "Jinke.Li <1359518268@qq.com>",
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/lijinke666/react-image-process"
33 | },
34 | "homepage": "https://lijinke666.github.io/react-image-process",
35 | "bugs": {
36 | "url": "https://github.com/lijinke666/react-image-process/issues"
37 | },
38 | "license": "MIT",
39 | "keywords": [
40 | "react",
41 | "reactjs",
42 | "react-photo",
43 | "compress",
44 | "photo",
45 | "react-filter",
46 | "react-image",
47 | "react-image-process",
48 | "image-process",
49 | "photo-magician",
50 | "image-process",
51 | "photo",
52 | "filter",
53 | "clip",
54 | "crop",
55 | "component"
56 | ],
57 | "dependencies": {
58 | "classnames": "^2.2.5",
59 | "photo-magician": "^0.3.0",
60 | "prop-types": "^15.6.0",
61 | "babel-runtime": "^6.23.0"
62 | },
63 | "devDependencies": {
64 | "autoprefixer": "^6.7.2",
65 | "babel-cli": "^6.16.0",
66 | "babel-core": "6.x",
67 | "babel-eslint": "^8.2.3",
68 | "babel-jest": "^22.4.3",
69 | "babel-loader": "6.x",
70 | "babel-plugin-add-module-exports": "^0.2.1",
71 | "babel-plugin-dynamic-import-node": "^1.0.2",
72 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
73 | "babel-plugin-syntax-object-rest-spread": "^6.13.0",
74 | "babel-plugin-transform-async-to-generator": "^6.24.1",
75 | "babel-plugin-transform-class-properties": "^6.23.0",
76 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
77 | "babel-plugin-transform-object-assign": "^6.22.0",
78 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
79 | "babel-plugin-transform-runtime": "^6.23.0",
80 | "babel-preset-es2015": "^6.18.0",
81 | "babel-preset-react": "^6.16.0",
82 | "babel-preset-stage-0": "6.x",
83 | "babel-preset-stage-1": "^6.24.1",
84 | "cross-env": "^5.1.4",
85 | "css-loader": "~0.28.11",
86 | "enzyme": "^3.3.0",
87 | "enzyme-adapter-react-16": "^1.1.1",
88 | "enzyme-to-json": "^3.3.3",
89 | "eslint": "^4.19.1",
90 | "eslint-plugin-babel": "^5.1.0",
91 | "eslint-plugin-react": "^7.7.0",
92 | "eslint_d": "^5.3.0",
93 | "extract-text-webpack-plugin": "^2.0.0-beta.4",
94 | "file-loader": "^0.9.0",
95 | "html-webpack-plugin": "^3.2.0",
96 | "jest": "^22.4.3",
97 | "jest-environment-jsdom": "^22.4.3",
98 | "jsdom": "^11.9.0",
99 | "less": "^2.7.2",
100 | "less-loader": "^2.2.3",
101 | "lint-staged": "^7.0.5",
102 | "open-browser-webpack-plugin": "0.0.5",
103 | "optimize-css-assets-webpack-plugin": "^1.3.0",
104 | "postcss": "^6.0.12",
105 | "postcss-cli": "^4.1.1",
106 | "postcss-loader": "^1.2.2",
107 | "power-assert": "^1.5.0",
108 | "prettier": "^1.12.1",
109 | "react": "^16.3.2",
110 | "react-dom": "^16.3.2",
111 | "react-hot-loader": "^4.1.2",
112 | "react-loader": "^2.4.0",
113 | "regenerator-runtime": "^0.11.0",
114 | "rimraf": "^2.6.0",
115 | "style-loader": "~0.13.0",
116 | "sweetalert": "^2.1.0",
117 | "url-loader": "^0.5.8",
118 | "webpack": "^4.2.0",
119 | "webpack-cli": "^3.3.5",
120 | "webpack-dev-server": "^3.1.11"
121 | },
122 | "jest": {
123 | "moduleFileExtensions": [
124 | "js",
125 | "jsx",
126 | "json"
127 | ],
128 | "transformIgnorePatterns": [
129 | "/node_modules/"
130 | ],
131 | "modulePathIgnorePatterns": [
132 | "/.history/"
133 | ],
134 | "moduleDirectories": [
135 | "node_modules",
136 | ".",
137 | "src",
138 | "src/shared"
139 | ],
140 | "setupTestFrameworkScriptFile": "/__tests__/setup.js",
141 | "snapshotSerializers": [
142 | "enzyme-to-json/serializer"
143 | ],
144 | "collectCoverageFrom": [
145 | "src/**/*.{js,jsx}"
146 | ],
147 | "transform": {
148 | "^.+\\.jsx?$": "babel-jest"
149 | },
150 | "moduleNameMapper": {
151 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__tests__/__mocks__/fileMock.js",
152 | "\\.(css|less)$": "/__tests__/__mocks__/styleMock.js"
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/postcss.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "autoprefixer": {
3 | "browsers": [
4 | "last 6 versions",
5 | "Android >= 4.0",
6 | "Firefox ESR",
7 | "not ie < 9"
8 | ],
9 | "sourceMap":false
10 | }
11 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @name react-image-process
3 | * @version 0.2.0
4 | */
5 |
6 | import React, { PureComponent } from 'react'
7 | import PhotoMagician from 'photo-magician'
8 | import PropTypes from 'prop-types'
9 | import classnames from 'classnames'
10 |
11 | export const MODE_TYPE = {
12 | base64: 'base64',
13 | clip: 'clip',
14 | compress: 'compress',
15 | rotate: 'rotate',
16 | waterMark: 'waterMark',
17 | filter: 'filter',
18 | primaryColor: 'primaryColor'
19 | }
20 |
21 | export const WATER_MARK_TYPE = {
22 | image: 'image',
23 | text: 'text'
24 | }
25 |
26 | export const FILTER_TYPE = {
27 | vintage: 'vintage',
28 | blackWhite: 'blackWhite',
29 | relief: 'relief',
30 | blur: 'blur'
31 | }
32 |
33 | export const OUTPUT_TYPE = {
34 | blob: 'blob',
35 | dataUrl: 'dataUrl'
36 | }
37 |
38 | const MODE = Object.values(MODE_TYPE)
39 | const WATER_MARK = Object.values(WATER_MARK_TYPE)
40 | const FILTER = Object.values(FILTER_TYPE)
41 | const OUTPUT = Object.values(OUTPUT_TYPE)
42 |
43 | const mainPrefix = 'react-image-process'
44 |
45 | export default class ReactImageProcess extends PureComponent {
46 | constructor(props) {
47 | super(props)
48 | }
49 | static defaultProps = {
50 | mode: MODE_TYPE['base64'],
51 | waterMarkType: WATER_MARK_TYPE['text'],
52 | filterType: FILTER_TYPE['vintage'],
53 | outputType: OUTPUT_TYPE['dataUrl'],
54 | waterMark: mainPrefix,
55 | rotate: 0,
56 | quality: 1.0,
57 | coordinate: [0, 0],
58 | width: 50,
59 | height: 50,
60 | opacity: 0.5,
61 | fontColor: 'rgba(255,255,255,.5)',
62 | fontSize: 20,
63 | fontBold: true,
64 | onComplete: () => {}
65 | }
66 | static propTypes = {
67 | mode: PropTypes.oneOf(MODE),
68 | waterMarkType: PropTypes.oneOf(WATER_MARK),
69 | filterType: PropTypes.oneOf(FILTER),
70 | outputType: PropTypes.oneOf(OUTPUT),
71 | waterMark: PropTypes.oneOfType([
72 | PropTypes.string,
73 | PropTypes.element,
74 | PropTypes.node
75 | ]),
76 | scale: PropTypes.number,
77 | rotate: PropTypes.number,
78 | quality: PropTypes.number,
79 | width: PropTypes.number,
80 | height: PropTypes.number,
81 | fontColor: PropTypes.string,
82 | fontSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
83 | fontBold: PropTypes.bool,
84 | coordinate: PropTypes.array,
85 | onComplete: PropTypes.func
86 | }
87 | render() {
88 | const { mode, children, style, className } = this.props
89 |
90 | const _className = `${mainPrefix}-${mode}`
91 |
92 | return (
93 | (this.node = node)}
96 | {...style}
97 | >
98 | {children}
99 |
100 | )
101 | }
102 | /**
103 | * @description get base64 data of the image
104 | * @param {Object} options
105 | * @param {String | Object} options.cover cover url | image element node The next cover parameter is the same as this.
106 | * @return base64 data
107 | */
108 | base64Handler = async (cover, params) => {
109 | return await this.photoMagician.toBase64Url({ cover, ...params })
110 | }
111 |
112 | /**
113 | * @description cut clip of the image
114 | * @param {object} Options
115 | * @param {String | Object} options.cover
116 | * @param {Number} options.scale Image zooming default '1.0'
117 | * @param {Array} options.coordinate [[x1,y1],[x2,y2]]
118 | * @return image node
119 | */
120 | clipHandler = async (cover, params) => {
121 | return await this.photoMagician.clipImage({ cover, ...params })
122 | }
123 | /**
124 | * @description compress of the image
125 | * @param {Object} options
126 | * @param {String | Object} options.cover
127 | * @param {Number} options.quality range(0-1) default '0.92'
128 | * @return base64 data
129 | */
130 | compressHandler = async (cover, params) => {
131 | return await this.photoMagician.compressImage({ cover, ...params })
132 | }
133 | /**
134 | * @description Rotate the image
135 | * @param {String | Object} cover 图片地址或节点
136 | * @param {Number} rotate 旋转比例 (0 -360 ) °
137 | */
138 | rotateHandler = async (cover, params) => {
139 | return await this.photoMagician.rotateImage({ cover, ...params })
140 | }
141 | /**
142 | * @param {Object} options
143 | * @param {String | Object} options.cover
144 | * @param {String} options.mode filter name "vintage" | "blackWhite" | "relief" | "blur"
145 | */
146 | filterHandler = async (cover, { filterType, ...params }) => {
147 | return await this.photoMagician.addImageFilter({
148 | cover,
149 | ...params,
150 | mode: filterType
151 | })
152 | }
153 | waterMarkHandler = async (cover, { waterMarkType, ...params }) => {
154 | return await this.photoMagician.addWaterMark({
155 | cover,
156 | ...params,
157 | mode: waterMarkType
158 | })
159 | }
160 | /**
161 | * @description get primary color of the image
162 | * @param {Object} options
163 | * @param {String | Object} options.cover
164 | * @return primaryColor
165 | */
166 | primaryColorHandler = async cover => {
167 | return await this.photoMagician.getPrimaryColor({ cover })
168 | }
169 | baseHandler = async (mode, options) => {
170 | try {
171 | const { onComplete } = this.props
172 | const { children, ...params } = options
173 | const images = Array.isArray(children)
174 | ? [...options.children]
175 | : [options.children]
176 |
177 | for (let [
178 | i,
179 | {
180 | props: { src }
181 | }
182 | ] of images.entries()) {
183 | if (!src) continue
184 | const data = await this[`${mode}Handler`](src, params)
185 | if (mode !== MODE_TYPE['primaryColor']) {
186 | if (params.outputType === OUTPUT_TYPE.dataUrl) {
187 | return (this.currentImgNodes[i].src = data)
188 | }
189 | this.currentImgNodes[i].src = URL.createObjectURL(data)
190 | this.currentImgNodes[i].onload = () => {
191 | URL.revokeObjectURL(data)
192 | }
193 | }
194 | if (onComplete && onComplete instanceof Function) {
195 | onComplete(data)
196 | }
197 | }
198 | } catch (err) {
199 | /*eslint-disable no-console */
200 | console.error(`[${mode}Handler-error]:`, err)
201 | }
202 | }
203 | //图片处理
204 | imageHandle = async ({ mode, ...options }) => {
205 | await this.baseHandler(MODE_TYPE[mode], options)
206 | }
207 | componentWillUnmount() {
208 | this.photoMagician = undefined
209 | this.currentImgNodes = undefined
210 | this.node = undefined
211 | }
212 | componentDidMount() {
213 | this.currentImgNodes = this.node.querySelectorAll('img')
214 | this.photoMagician = new PhotoMagician()
215 | this.imageHandle(this.props)
216 | }
217 |
218 | componentWillReceiveProps(nextProps) {
219 | if (
220 | Object.keys(nextProps).some(
221 | key => !Object.is(nextProps[key], this.props[key])
222 | )
223 | ) {
224 | this.imageHandle(nextProps)
225 | }
226 | }
227 | }
228 |
--------------------------------------------------------------------------------