├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README-ZH.md ├── README.md ├── __sites_ ├── .gitignore ├── .travis.yml ├── README.md ├── build │ └── bundle.js ├── index.html ├── package.json ├── server.js ├── src │ ├── app.js │ ├── components │ │ ├── doc │ │ │ ├── cn │ │ │ │ ├── compress-image.js │ │ │ │ ├── crop-image.js │ │ │ │ ├── custom-component.js │ │ │ │ ├── events.js │ │ │ │ ├── get-started.js │ │ │ │ ├── home.js │ │ │ │ ├── multiple-file.js │ │ │ │ ├── others.js │ │ │ │ ├── post-data.js │ │ │ │ ├── props.js │ │ │ │ └── resize-image.js │ │ │ └── en │ │ │ │ ├── compress-image.js │ │ │ │ ├── crop-image.js │ │ │ │ ├── custom-component.js │ │ │ │ ├── events.js │ │ │ │ ├── get-started.js │ │ │ │ ├── home.js │ │ │ │ ├── multiple-file.js │ │ │ │ ├── others.js │ │ │ │ ├── post-data.js │ │ │ │ ├── props.js │ │ │ │ └── resize-image.js │ │ ├── ft.js │ │ ├── hd.js │ │ └── nav-list.js │ ├── index.js │ ├── less │ │ ├── config.less │ │ ├── modules │ │ │ ├── m-button.less │ │ │ ├── m-icon.less │ │ │ ├── m-layout.less │ │ │ ├── m-pages.less │ │ │ ├── m-pagination.less │ │ │ ├── m-table.less │ │ │ ├── m-type.less │ │ │ └── reset.less │ │ ├── plugins │ │ │ ├── angular.progress.less │ │ │ └── hljs.theme.less │ │ └── vtui.less │ └── lib │ │ ├── constants.js │ │ └── vendor.js ├── webpack.config.js └── yarn.lock ├── karma.conf.js ├── package.json ├── react-core-image-upload.js ├── server.js ├── shots ├── react-core-image-upload.jpg └── vuedba0ed377b88fc84d51026310efcb255b.png ├── src ├── components │ ├── crop.js │ └── resize-bar.js ├── index.js ├── lib │ ├── canvas-helper.js │ ├── drag.js │ ├── error-code.js │ ├── helper.js │ ├── loading-gif.js │ ├── resize.js │ └── xhr.js ├── propTypes.js ├── props.js ├── react-core-image-upload.js └── style.css ├── tests └── react-core-image-upload.test.js ├── webpack.config.build.js └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "import", 5 | "react" 6 | ], 7 | "env": { 8 | "browser": 1 9 | }, 10 | "rules": { 11 | "prefer-template": 0, 12 | "no-console": 0 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 6, 16 | "sourceType": "module", 17 | "ecmaFeatures": { 18 | "jsx": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | Icon 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | Thumbs.db 9 | ehthumbs.db 10 | Desktop.ini 11 | $RECYCLE.BIN/ 12 | node_modules/ 13 | npm-debug.log 14 | .idea/ 15 | test/temp-test 16 | coverage/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __site__ 2 | shots 3 | tests 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 5.0 4 | - 6.0 5 | - 7.0 6 | script: node_modules/karma/bin/karma start ./karma.conf.js --singleRun 7 | before_install: 8 | - export CHROME_BIN=chromium-browser 9 | - export DISPLAY=:99.0 10 | - sh -e /etc/init.d/xvfb start -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Vanthink-UED 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. -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | # react-core-image-upload 2 | 3 | [![npm](https://img.shields.io/npm/v/react-core-image-upload.svg?maxAge=2592000)]() 4 | ![Node Version](https://img.shields.io/node/v/react-core-image-upload.svg "Node Version") 5 | [![Build Status](https://travis-ci.org/Vanthink-UED/react-core-image-upload.svg?branch=master)](https://travis-ci.org/Vanthink-UED/react-core-image-upload) 6 | 7 | 8 | 9 | 一款轻量级的图片上传裁剪组件 10 | 11 | [English Doc]('./README.md') 12 | 13 | 14 | ### 快速开始 15 | 使用 npm 16 | ```bash 17 | npm install react-core-image-upload --save 18 | ``` 19 | 20 | 使用 yarn 21 | ``` bash 22 | yarn add react-core-image-upload 23 | ``` 24 | 25 | ### 使用ES6 进行开发 26 | ``` js 27 | import React from 'react'; 28 | import ReactCoreImageUpload from 'react-core-image-upload'; 29 | let App = React.createClass({ 30 | //... 31 | 32 | render() { 33 | return( 34 |
35 | 41 | 42 |
43 | ); 44 | }, 45 | 46 | handleRes(res) { 47 | this.setState({ 48 | // handle response 49 | }) 50 | } 51 | }) 52 | 53 | ``` 54 | 55 | 56 | 57 | ### 运行DEMO 58 | ``` bash 59 | yarn run start 60 | ``` 61 | [http://localhost:9000/webpack-dev-server/demo/index.html](http://localhost:9000/webpack-dev-server/demo/index.html) 62 | 63 | [Demo Online](http://vanthink-ued.github.io/react-core-image-upload/upload.html) 64 | 65 | ### 配置属性 66 | 67 | | Props | Type | Example | Description | 68 | | ------------- |:----------| ---------|--------------| 69 | | url | String | '/crop.php' | 服务端上传的地址 | 70 | | text | String | 'Upload Image' | 你需要显示按钮的文本 | 71 | | inputOfFile | String | 'file' | 上传服务端对应表单 name | 72 | | extensions | String | 'png,jpg,gif' | 限制的图片类型 | 73 | | crop | Boolean | true | 是否需要裁剪 | 74 | | cropRatio | String | '1:1' | 限制裁剪的形状| 75 | | cropBtn | Object | {ok:'Save','cancel':'Give Up'} |按钮文本| 76 | | maxFileSize | Number | 10485760(10M) | 文件大小限制| 77 | | maxWidth | Number | 150 | 限制裁剪图片的最大宽度| 78 | | maxheight | Number | 150 | 限制裁剪图片的最大高度| 79 | | inputAccept | string | 'image/*' / 'image/jpg,image/jpeg,image/png' | 赋予上传file的接受类型| 80 | | isXhr | Boolean | true | 是否需要调用系统内自己的上传功能 81 | | headers | Object | {auth: xxxxx} | 设置xhr上传 的header 82 | 83 | ### image uploading callback 84 | 85 | + `imageUploaded`: 当图片上传成功后的响应处理 86 | + `imageChanged`: 当选择图片后 87 | + `imageUploading` 图片上传过程中 88 | + `errorHandle`图片上传中的异常处理 89 | 90 | 91 | [Demo](http://vanthink-ued.github.io/react-core-image-upload/upload.html) 92 | 93 | [Demo Source](https://github.com/Vanthink-UED/react-core-image-upload/blob/master/src/components/contents.js) 94 | 95 | 96 | ### 发给服务端的裁剪参数 97 | 98 | If you crop a image , your crop will send a request to your server with some crop arguments; 99 | 100 | 101 | 102 | 103 | 参数如上图。 104 | 105 | 如果你需要自定义裁剪弹窗的的样式,你可以自己写css进行覆盖 106 | 107 | ### MIT Liscense -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-core-image-upload 2 | 3 | [![npm](https://img.shields.io/npm/v/react-core-image-upload.svg?maxAge=2592000)]() 4 | ![Node Version](https://img.shields.io/node/v/react-core-image-upload.svg "Node Version") 5 | [![Build Status](https://travis-ci.org/Vanthink-UED/react-core-image-upload.svg?branch=master)](https://travis-ci.org/Vanthink-UED/react-core-image-upload) 6 | 7 | 8 | 9 | A component for image to upload and crop 10 | 11 | [Document](http://vanthink-ued.github.io/react-core-image-upload/index.html#/en/home) 12 | 13 | [中文文档](http://vanthink-ued.github.io/react-core-image-upload/index.html#/cn/home) 14 | 15 | 16 | ### Install 17 | Use Npm 18 | ```bash 19 | npm install react-core-image-upload --save 20 | ``` 21 | 22 | Use yarn 23 | ``` bash 24 | yarn add react-core-image-upload 25 | ``` 26 | 27 | ### How to use 28 | ``` js 29 | import React from 'react'; 30 | import ReactCoreImageUpload from 'react-core-image-upload'; 31 | let App = React.createClass({ 32 | //... 33 | 34 | render() { 35 | return( 36 |
37 | 43 | 44 |
45 | ); 46 | }, 47 | 48 | handleRes(res) { 49 | this.setState({ 50 | // handle response 51 | }) 52 | } 53 | }) 54 | 55 | ``` 56 | 57 | 58 | ### Run demo 59 | ``` bash 60 | cd __sites_ && npm run start 61 | ``` 62 | 63 | [http://localhost:9000/webpack-dev-server/demo/index.html](http://localhost:9000/webpack-dev-server/demo/index.html) 64 | 65 | [Demos](http://vanthink-ued.github.io/react-core-image-upload/index.html) 66 | 67 | ### Props 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |
PropsData TypeExampleDetails
urlString'/crop.php'Your server api
textString'Upload Image'The text of your uploading button
inputOfFileString      'file'Yout input[file] name
extensionsString'png,jpg,gif'Limit the image type
cropBoolean'server'Crop image option
cropRatioString'1:1'The cropped image shape(set 'auto' not limit the crop shape)
cropBtnObject{ok:'Save','cancel':'Give Up'}The Text of cropping button text
maxFileSizeNumber10485760(10M)Limit the size of the file
maxWidthNumber150The maximum width of cropped image
maxheightNumber150限制图片的最大高度
inputAcceptstring'image/*' / 'image/jpg,image/jpeg,image/png'the input[file] accept
compressNumber50Set the quality of compressed image
isXhrBooleantrueIF cancel ajax uploading
headersObject{auth: xxxxx}Set customed header when ajax uploading
dataObject{auth: xxxxx}Set customed data when ajax posting server
170 | 171 | ### Events 172 | 173 | + `imageUploaded`: when you finish your image uploading 174 | + `imageChanged`: when the input file has changed 175 | + `imageUploading` when your image is uploading 176 | + `errorHandle` when image uploading meet some error 177 | 178 | 179 | [Demo](http://vanthink-ued.github.io/react-core-image-upload/upload.html) 180 | 181 | [Demo Source](https://github.com/Vanthink-UED/react-core-image-upload/blob/master/src/components/contents.js) 182 | 183 | 184 | ### Contributions 185 | 186 | Your contributions and suggestions are welcome 😄😄😄💐💐💐 187 | -------------------------------------------------------------------------------- /__sites_/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | Icon 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | Thumbs.db 9 | ehthumbs.db 10 | Desktop.ini 11 | $RECYCLE.BIN/ 12 | node_modules/ 13 | npm-debug.log 14 | .idea/ 15 | test/temp-test 16 | coverage/ -------------------------------------------------------------------------------- /__sites_/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | - '5' 5 | before_install: 6 | - 'npm i yarn -g' 7 | after_success: 'npm run coverage' -------------------------------------------------------------------------------- /__sites_/README.md: -------------------------------------------------------------------------------- 1 | # yarn-react-webpack-seed 2 | [![npm](https://img.shields.io/npm/v/yarn-react-webpack-seed.svg?maxAge=2592000)]() 3 | ![Node Version](https://img.shields.io/node/v/yarn-react-webpack-seed.svg "Node Version") 4 | 5 | 6 | 7 | a seed for a react app(yarn + webpack + react + react-router) 8 | 9 | ### Start 10 | 11 | ```bash 12 | git clone https://github.com/JackPu/yarn-react-webpack-seed 13 | yarn install 14 | ``` 15 | [How to Use Yarn](https://yarnpkg.com/) 16 | 17 | Of course you can use `npm install`.It also works well. 18 | 19 | [Demo](http://events.jackpu.com/yarn-react-webpack-seed/#/start?_k=uyqxva) 20 | 21 | ### Contribute 22 | 23 | Please contribute to the project if you think this can be done better in anyway even for this project(😊😄💐) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /__sites_/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-core-image-upload a react.js plugin for image upload and crop(一款轻量级react.js图片上传插件) 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /__sites_/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yarn-react-webpack-seed", 3 | "version": "0.9.4", 4 | "description": "a react seed (router + webpack + react + yarn)", 5 | "main": "index.js", 6 | "repository": { 7 | "url": "https://github.com/JackPu/yarn-react-webpack-seed.git", 8 | "type": "git" 9 | }, 10 | "scripts": { 11 | "start": "webpack-dev-server --port 9000", 12 | "build": "webpack --progress --colors" 13 | }, 14 | "author": "JackPu ", 15 | "license": "MIT", 16 | "dependencies": { 17 | "core-image-xhr": "^1.0.1", 18 | "less-loader": "^2.2.3", 19 | "prop-types": "^15.5.10", 20 | "react": "^15.5.4", 21 | "react-core-image-upload": "^2.2.1", 22 | "react-dom": "^15.5.4", 23 | "react-highlight": "^0.10.0", 24 | "react-highlight.js": "^1.0.5", 25 | "react-router": "^2.8.1" 26 | }, 27 | "devDependencies": { 28 | "babel": "^6.5.2", 29 | "babel-core": "^6.17.0", 30 | "babel-loader": "^6.2.5", 31 | "babel-preset-es2015": "^6.16.0", 32 | "babel-preset-react": "^6.16.0", 33 | "history": "^4.3.0", 34 | "less": "^3.8.1", 35 | "react-hot-loader": "^3.0.0-beta.6", 36 | "webpack": "^1.13.2", 37 | "webpack-dev-server": "^1.16.2" 38 | }, 39 | "engines": { 40 | "node": ">=4.0.0", 41 | "npm": ">=2.5.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /__sites_/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true 9 | }).listen(9000, 'localhost', function (err, result) { 10 | if (err) { 11 | return console.log(err); 12 | } 13 | 14 | console.log('Listening at http://localhost:9000/'); 15 | }); -------------------------------------------------------------------------------- /__sites_/src/app.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Router, Route, hashHistory } from 'react-router'; 4 | import Ft from './components/ft'; 5 | import Hd from './components/hd'; 6 | import Navlist from './components/nav-list'; 7 | //pages 8 | import EnHome from './components/doc/en/home'; 9 | import CnHome from './components/doc/cn/home'; 10 | import CngetStarted from './components/doc/cn/get-started'; 11 | import EngetStarted from './components/doc/en/get-started'; 12 | import CnProps from './components/doc/cn/props'; 13 | import EnProps from './components/doc/en/props'; 14 | import CnEvents from './components/doc/cn/events'; 15 | import EnEvents from './components/doc/en/events'; 16 | import CnCustomComponent from './components/doc/cn/custom-component'; 17 | import EnCustomComponent from './components/doc/en/custom-component'; 18 | import CnCropImage from './components/doc/cn/crop-image'; 19 | import EnCropImage from './components/doc/en/crop-image'; 20 | import CnResizeImage from './components/doc/cn/resize-image'; 21 | import EnResizeImage from './components/doc/en/resize-image'; 22 | import CnCompressImage from './components/doc/cn/compress-image'; 23 | import EnCompressImage from './components/doc/en/compress-image'; 24 | import CnMultipleFile from './components/doc/cn/multiple-file'; 25 | import EnMultipleFile from './components/doc/en/multiple-file'; 26 | import CnPostData from './components/doc/cn/post-data'; 27 | import EnPostData from './components/doc/en/post-data'; 28 | import CnOthers from './components/doc/cn/others'; 29 | import EnOthers from './components/doc/en/others'; 30 | 31 | 32 | class App extends Component { 33 | render() { 34 | return ( 35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 | 67 |
68 | 69 | ); 70 | } 71 | } 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/compress-image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnMultipleFile extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued7553a09a5d5209ebd00a48264394b7f3.png', 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | render() { 15 | return ( 16 |
17 |

压缩图片

18 |

设置compress的数值,你可以在上传之前进行图片的本地压缩。其中 compress 为 0 表示不压缩,数据越大,图片的质量越差,且最大值不能大于100。

19 |
20 |

21 | 29 | 30 |
31 |

代码示例

32 | {` 40 | `} 41 | 42 |
43 | ); 44 | } 45 | 46 | imageUploded(res) { 47 | if (res.errcode === 0) { 48 | this.setState({ 49 | src: res.data.src, 50 | }); 51 | } 52 | } 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/crop-image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnCropImage extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued7553a09a5d5209ebd00a48264394b7f3.png', 11 | cropSrc: 'http://img1.vued.vanthink.cn/vued7553a09a5d5209ebd00a48264394b7f3.png', 12 | cropArgs: { 13 | toCropImgH: '?', 14 | toCropImgW: '?', 15 | toCropImgX: '?', 16 | toCropImgY: '?', 17 | }, 18 | }; 19 | this.cropLocalImageUploaded = this.cropLocalImageUploaded.bind(this); 20 | this.crpoServerImageUploaded = this.crpoServerImageUploaded.bind(this); 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |

裁剪图片

27 |

你可以通过设置 crop,来实现图片的裁剪。你可以指定图片裁剪的宽高,以及它的最大宽度和高度这些参数。

28 |

设置 cropRatio来限制裁剪图片的形状,需要字符串的格式(1:1 或者2:3这种比例形式),当然你可以设置为 auto 则不限制裁剪框的形状。

29 |

设置图片裁剪后,批量上传将不再生效。

30 |

图片裁剪完有两种选择,选择本地裁剪local或者服务端裁剪 server

31 |

本地裁剪

32 |

你可以将 crop 设置为 local 来实现本地裁剪。本地裁剪完成后发送给服务端接口的图片便是已经裁剪好的图片。

33 |
34 |
35 | 36 |
37 | 44 | 45 |
46 |

服务端裁剪

47 |

服务端裁剪是指将原图片和裁剪的参数一起发给后端,方便服务端保存原图,以及对原图的其他操作,而服务端能够接收到post的参数如下:

48 | 49 |

每个字段具体说明如下:

50 | 58 |

裁剪区域的样式,你可以自行复写样式进行覆盖

59 |

服务端裁剪DEMO

60 |

上传图片后可以看到裁剪的参数

61 |
62 |
63 | 64 |
65 | 71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
HWXY
{this.state.cropArgs.toCropImgH}{this.state.cropArgs.toCropImgW}{this.state.cropArgs.toCropImgX}{this.state.cropArgs.toCropImgY}
91 |

Code Example

92 | 93 |

设置resize="server"同理,会上传原图片,只是会在服务端的参数自动添加裁剪的比例 imgChangeRatio

94 | View Code Source 95 |
96 | ); 97 | } 98 | 99 | cropLocalImageUploaded(res) { 100 | this.src = res.data.src; 101 | } 102 | 103 | crpoServerImageUploaded(res) { 104 | if (res.errcode === 0) { 105 | this.setState({ 106 | cropArgs: { 107 | toCropImgH: parseInt(res.data.post.toCropImgH), 108 | toCropImgW: parseInt(res.data.post.toCropImgW), 109 | toCropImgX: parseInt(res.data.post.toCropImgX), 110 | toCropImgY: parseInt(res.data.post.toCropImgY) 111 | }, 112 | cropSrc: 'http://img1.vued.vanthink.cn/vued41b900045d6d44f3b32e06049621b415.png', 113 | }); 114 | 115 | } 116 | } 117 | 118 | }; 119 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/custom-component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CustomComponent extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

自定义组件样式

19 |

你可以设置组件的class 以及自己编写子组件的形式来控制组件的显示的样子。

20 |

Demo

21 |

下面是一个图片按钮。

22 |

23 | avatar 24 |

25 |
26 | 33 | 34 | 35 |
36 |

Code Example

37 | 38 | {` 45 | 46 | `} 47 | 48 |
49 | ); 50 | } 51 | 52 | imageUploded(res) { 53 | if (res.errcode === 0) { 54 | this.setState({ 55 | fileList: res.data, 56 | }); 57 | } 58 | } 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/events.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactCoreImageUpload from '../../../../../src/index'; 3 | import Highlight from 'react-highlight'; 4 | 5 | export default class Events extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 10 | step: 0, 11 | }; 12 | this.imagechanged = this.imagechanged.bind(this); 13 | this.imageuploading = this.imageuploading.bind(this); 14 | this.imageuploaded = this.imageuploaded.bind(this); 15 | } 16 | 17 | render() { 18 | return ( 19 |
20 |

响应事件

21 |

我们在上传的不同阶段指定了不同的派发事件,你可以绑定每个事件的响应方法,实现对于流程的控制。

22 |
imageuploaded
23 |

当图片上传完,会调用该事件绑定的函数,并且用户可以获取到服务端返回的数据。

24 |
imagechanged
25 |

当input框改变选择图片时候触发,会返回input的获取的图片数据

26 |
imageuploading
27 |

当图片上传过程中触发,你可以自定义你需要处理的内容比如显示加载动画等。

28 |
errorhandle
29 |

当图片上传发生错误的时候触发,会返回错误状态信息

30 |
Code Example
31 |
32 | 33 |
34 |
35 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 64 |
图片选中图片上传图片完成
58 | 0 ? 'circle-bar active': 'circle-bar'}> 59 | 1 ? 'circle-bar active': 'circle-bar'}> 2 ? 'circle-bar active': 'circle-bar'}>
65 |
66 |

上面的演示,表示了上传自定义事件的执行状况,参考代码如下:

67 | 68 | {``} 76 | 77 | 完整代码 78 |
79 | ); 80 | } 81 | 82 | imagechanged() { 83 | this.plus(); 84 | } 85 | 86 | imageuploading() { 87 | this.plus(); 88 | } 89 | 90 | imageuploaded() { 91 | this.plus(); 92 | } 93 | 94 | plus() { 95 | this.setState({ 96 | step: this.state.step += 1, 97 | }); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/get-started.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactCoreImageUpload from '../../../../../src/index'; 3 | import Highlight from 'react-highlight'; 4 | 5 | export default class GetStarted extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 10 | }; 11 | this.imageuploaded = this.imageuploaded.bind(this); 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 |

快速开始

18 |

使用 npm 安装依赖

19 |
npm install react-core-image-upload --save
20 |

安装完成后,编辑源码

21 | 22 | {`import React from 'react'; 23 | import ReactCoreImageUpload from 'react-core-image-upload'; 24 | 25 | export default class GetStarted extends React.Component { 26 | constructor(props) { 27 | super(props); 28 | this.state = { 29 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 30 | }; 31 | this.imageuploaded = this.imageuploaded.bind(this); 32 | } 33 | 34 | redner() { 35 | return( 36 |
37 |

38 | 39 |

40 |
41 | 47 | 48 |
49 |
50 | ); 51 | } 52 | 53 | imageuploaded(res) { 54 | if (res.errcode == 0) { 55 | this.setState({ 56 | src: res.data.src, 57 | }); 58 | } 59 | } 60 | }` 61 | } 62 |
63 |
Code Example
64 |

65 | 66 |

67 |
68 | 74 | 75 |
76 |

如果我们要使用上传插件,我们首先需要引入我们的组件然后并在components中声明。 77 | 实现上传,我们需要定义我们上传的服务器地址url,然后我们需要指定上传完成后触发的方法,也就是imageUploaded,这样我们才能获取上传完后的数据,从而进行下一步的操作。 78 |

79 |

查看详细文档

80 |
81 | ); 82 | } 83 | 84 | imageuploaded(res) { 85 | if (res.errcode == 0) { 86 | this.setState({ 87 | src: res.data.src, 88 | }); 89 | } 90 | } 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | 4 | export default class CnHome extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 |

13 | react-core-image-upload 14 |

15 |

16 | 快速开始 17 |

18 |

English Document

19 |

react-core-image-upload 是一款轻量级的 react.js 上传插件,它可以支持的图片的上传,裁剪,压缩。它同样也支持在移动端的图片处理,它定义了诸多上传周期,你可以自由的进行流程控制。

20 |
21 | ); 22 | } 23 | 24 | goToEnglishDoc() { 25 | vendor.setLocalData('lan', 'en'); 26 | window.lan = 'en'; 27 | location.href = './index.html#/en/home'; 28 | location.reload(); 29 | } 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/multiple-file.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnMultipleFile extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | fileList: [], 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | 15 | render() { 16 | const trs = []; 17 | console.log(this.state.fileList); 18 | for(let i = 0; i < this.state.fileList.length; i++) { 19 | const item = this.state.fileList[i]; 20 | 21 | trs.push( 22 | 23 | {item.name} 24 | {item.size} 25 | 26 | ); 27 | } 28 | 29 | return ( 30 |
31 |

上传多个文件

32 |

multiple

33 |

你可以使用 multiple 属性设置为true来实现多文件的上传。需要注意的是,你设置了该属性后,服务端收到文件上传的字段数据会是一个数组。

34 |

multiple-size

35 |

你可以使用multiple-size来限制图片的数量,你可以限制上传文件的数量。

36 |

演示

37 |
38 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {trs} 56 | 57 |
文件名称文件大小
58 |

点击上传按钮,可以选择多张图片,然后在表格中可以看到上传的图片名称和图片大小。

59 |

Code Example

60 | {` 68 | `} 69 | 70 | 查看完整源码 71 |
72 | ); 73 | } 74 | 75 | imageUploded(res) { 76 | if (res.errcode === 0) { 77 | this.setState({ 78 | fileList: res.data, 79 | }); 80 | } 81 | } 82 | 83 | }; 84 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/others.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Others extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | render() { 9 | return ( 10 |
11 |

其他与反馈

12 |

你可以在 Github issue中搜索你遇见的问题,如果没有对应的情况,请直接提交你遇到的问题。

13 |

如果你觉得问题比较复杂,难以描述,你可以直接联系我,我的邮箱是: kakashjack#gmail.com(# 替换成 @符号)

14 |

微信:

15 | wechat 16 |

Facebook Messager:

17 | Facebook Messager 18 |
19 | ); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/post-data.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactCoreImageUpload from '../../../../../src/index'; 3 | import Highlight from 'react-highlight'; 4 | 5 | export default class PostData extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | data: { 10 | text: '' 11 | }, 12 | }; 13 | this.change = this.change.bind(this); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 |

向服务端发送数据

20 |

你可以设置data 来将一些附带的数据发送给服务端。 21 | 当然你也可以将一些数据包含在 header 中传递过去,你只需要绑定到 header即可。

22 |

Exmaple:

23 |

下面会将输入框的内容一并发送过去

24 |
25 | 26 |

27 | 34 | 35 |
36 |

上传的过程中我们可以打开devtool查看请求,可以看到发送数据中带上了一个新的 text 字段,也就是文本框内容。

37 |

38 |

Code Example

39 |

你可以设置 isXhr 来取消向服务端上传。

40 | 查看源码 41 |
42 | ); 43 | } 44 | 45 | imageuploaded(res) { 46 | if (res.errcode == 0) { 47 | this.setState({ 48 | src: res.data.src, 49 | }); 50 | } 51 | } 52 | 53 | change(e) { 54 | var val = e.target.value; 55 | if (val) { 56 | this.setState({ 57 | data: { 58 | text: val, 59 | } 60 | }) 61 | } 62 | } 63 | 64 | }; 65 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/props.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class Props extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | render() { 11 | return ( 12 |
13 |

基本属性

14 |

Vue-core-image-upload 提供了很多可配置的选项,从而希望尽可能的满足开发者的需求。

15 |

Code Example

16 | { 17 | ` 23 | ` 24 | } 25 |

其中 className-file-size均是插件支持的属性,你可以具体查看这张表格:

26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
属性数据类型示例描述
urlString'/crop.php'服务端上传的地址
textString'Upload Image'你需要显示按钮的文本
inputOfFileString      'file'上传服务端对应表单 name
extensionsString'png,jpg,gif'限制的图片类型
cropBooleantrue是否需要裁剪
cropRatioString'1:1'限制裁剪的形状(设置为auto表示不限制裁剪框形状)
cropBtnObject{"{ok:'Save','cancel':'Give Up'}"}按钮文本
maxFileSizeNumber10485760(10M)文件大小限制
maxWidthNumber150限制图片的最大宽度
maxheightNumber150限制图片的最大高度
inputAcceptstring'image/*' / 'image/jpg,image/jpeg,image/png'赋予上传file的接受类型
compressNumber50设置本地图片压缩的质量
isXhrBooleantrue是否需要调用系统内自己的上传功能
headersObject{"{auth: xxxxx}"}设置xhr上传 的header
dataObject{"{name: xxxxx}"}设置附带发送给服务端的数据
129 |
130 |

后文会对一些重要的属性进行详细的描述,以及一些其他 Demos 你可以参考左边的导航。

131 |

url

132 |

url 表示我们指定的服务端配置接口地址,插件会将文件使用 Ajax 的形式上传到到你指定的地址服务,然后你可以通过imageuploaded的回调取得服务器的响应的数据。

133 |

inputOfFile

134 |

如果你的服务端端需要指定上传表单file name 的字段,你可以通过inputOfFile设置,只能是字符串如果没有设置,默认发给服务端的字段是files。如果你是使用多图片上传的话,字段会变成files[]

135 |

extensions

136 |

限制图片的上传类型,你可以传递一组CSV数据进去比如 'png,jpg,gif',从而只允许png,jpg,gif的类型图片上传到服务器。

137 |

maxFileSize

138 |

虽然很多时候服务端限制了图片的大小,但是本地同样可以限制上传图片的大小,你可以设置 maxFileSize的值来实现,它的基本单位为字节比如(10485760B = 10M)。

139 |

inputAccept

140 |

赋予上传file的接受类型,它可以帮助你再上传图片选择时,禁用掉不需要的文件,默认值为 image/jpg,image/jpeg,image/png

141 |

isXhr

142 |

我们同样接受取消掉默认的上传,你可以设置isXhr为 false ,自行处理我们获取到文件对象。

143 |

关于没有涉及到的属性,左边的导航会给出详细说明

144 |
145 | ); 146 | } 147 | 148 | imageUploded(res) { 149 | if (res.errcode === 0) { 150 | this.setState({ 151 | src: res.data.src, 152 | }); 153 | } 154 | } 155 | 156 | }; 157 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/cn/resize-image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnResizeImage extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

调整图片

19 |

你可以设置 resize 来进行图片的缩放。

20 |

设置resize="local" 意味着图片的缩放将在本地进行。发给服务端的将会是大小调整完毕的后的图片。

21 |
22 |
23 | 24 |
25 | 31 | 32 |
33 |

Code Example

34 | 35 |

设置resize="server"同理,会上传原图片,只是会在服务端的参数自动添加裁剪的比例 imgChangeRatio

36 | View Code Source 37 |
38 | ); 39 | } 40 | 41 | imageUploded(res) { 42 | if (res.errcode === 0) { 43 | this.setState({ 44 | src: res.data.src, 45 | }); 46 | } 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/compress-image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnMultipleFile extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued7553a09a5d5209ebd00a48264394b7f3.png', 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | render() { 15 | return ( 16 |
17 |

Compress Image

18 |

Props compress means the quality of the image you want to compress via browser 19 | and then send the compressed image to the server.

20 |
21 |

22 | 30 | 31 |
32 |

代码示例

33 | {` 41 | `} 42 | 43 |
44 | ); 45 | } 46 | 47 | imageUploded(res) { 48 | if (res.errcode === 0) { 49 | this.setState({ 50 | src: res.data.src, 51 | }); 52 | } 53 | } 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/crop-image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnCropImage extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued7553a09a5d5209ebd00a48264394b7f3.png', 11 | cropSrc: 'http://img1.vued.vanthink.cn/vued7553a09a5d5209ebd00a48264394b7f3.png', 12 | cropArgs: { 13 | toCropImgH: '?', 14 | toCropImgW: '?', 15 | toCropImgX: '?', 16 | toCropImgY: '?', 17 | }, 18 | }; 19 | this.cropLocalImageUploaded = this.cropLocalImageUploaded.bind(this); 20 | this.crpoServerImageUploaded = this.crpoServerImageUploaded.bind(this); 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |

Crop Image

27 |

Set crop value to help you crop the image.

28 |

if you setted the crop props, you can not upload multiple files.

29 |

cropRatio can be setted for diffrent crop shape.But it must be a string like '2:3' or '1:1'. If you set it to 'auto', users can crop any shape images.

30 |

You have two values to select,local crop:localorserver-side crop: server.

31 |

Local Crop

32 |

crop="local" The Browser will crop the image via canvas API and send the cropped image to the server.

33 |
34 |
35 | 36 |
37 | 44 | 45 |
46 |

Server-side crop

47 |

crop="server" means the bwowser will send the original image to the server and post the cropped data below to the server:

48 | 49 |

Each filed introduce:

50 | 58 |

Code example

59 |

Click button to upload and you can view some post params.

60 |
61 |
62 | avatar 63 |
64 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
HWXY
{this.state.cropArgs.toCropImgH}{this.state.cropArgs.toCropImgW}{this.state.cropArgs.toCropImgX}{this.state.cropArgs.toCropImgY}
90 |

Code Example

91 | {` 97 | `} 98 | View Code Source 99 |
100 | ); 101 | } 102 | 103 | cropLocalImageUploaded(res) { 104 | this.src = res.data.src; 105 | } 106 | 107 | crpoServerImageUploaded(res) { 108 | if (res.errcode === 0) { 109 | this.setState({ 110 | cropArgs: { 111 | toCropImgH: parseInt(res.data.post.toCropImgH), 112 | toCropImgW: parseInt(res.data.post.toCropImgW), 113 | toCropImgX: parseInt(res.data.post.toCropImgX), 114 | toCropImgY: parseInt(res.data.post.toCropImgY) 115 | }, 116 | cropSrc: 'http://img1.vued.vanthink.cn/vued41b900045d6d44f3b32e06049621b415.png', 117 | }); 118 | 119 | } 120 | } 121 | 122 | }; 123 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/custom-component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CustomComponent extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

Custom Component

19 |

class will be bind to the component and you can include any child component.

20 |

Demo

21 |

This is a image upload button.

22 |

23 | avatar 24 |

25 |
26 | 33 | 34 | 35 |
36 |

Code Example

37 | 38 | {` 45 | 46 | `} 47 | 48 |
49 | ); 50 | } 51 | 52 | imageUploded(res) { 53 | if (res.errcode === 0) { 54 | this.setState({ 55 | fileList: res.data, 56 | }); 57 | } 58 | } 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/events.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactCoreImageUpload from '../../../../../src/index'; 3 | import Highlight from 'react-highlight'; 4 | 5 | export default class Events extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 10 | step: 0, 11 | }; 12 | this.imagechanged = this.imagechanged.bind(this); 13 | this.imageuploading = this.imageuploading.bind(this); 14 | this.imageuploaded = this.imageuploaded.bind(this); 15 | } 16 | 17 | render() { 18 | return ( 19 |
20 |

Custom Events

21 |

It defines different custom event in different upload progress. You can control the files flow via binding some functions.

22 |
imageuploaded
23 |

When image has been uploaded, it call the function you bind. You could recive response from server as an param

24 |
imagechanged
25 |

When input[file] velue has been changed, it call the function you bind. You could recive an file source param

26 |
imageuploading
27 |

When the image is uploading.

28 |
errorhandle
29 |

Whne you meet some error like network error or file size error.

30 |
Code Example
31 |
32 | 33 |
34 |
35 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 64 |
SelectedUplaodingFinished
58 | 0 ? 'circle-bar active': 'circle-bar'}> 59 | 1 ? 'circle-bar active': 'circle-bar'}> 2 ? 'circle-bar active': 'circle-bar'}>
65 |
66 |

Code:

67 | 68 | {``} 76 | 77 | View Code 78 |
79 | ); 80 | } 81 | 82 | imagechanged() { 83 | this.plus(); 84 | } 85 | 86 | imageuploading() { 87 | this.plus(); 88 | } 89 | 90 | imageuploaded() { 91 | this.plus(); 92 | } 93 | 94 | plus() { 95 | this.setState({ 96 | step: this.state.step += 1, 97 | }); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/get-started.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactCoreImageUpload from '../../../../../src/index'; 3 | import Highlight from 'react-highlight'; 4 | 5 | export default class GetStarted extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 10 | }; 11 | this.imageuploaded = this.imageuploaded.bind(this); 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 |

快速开始

18 |

使用 npm 安装依赖

19 |
npm install vue-core-image-upload --save
20 |

安装完成后,编辑源码

21 | 22 | {` 34 | 35 | ` 56 | } 57 | 58 |
Code Example
59 |

60 | 61 |

62 |
63 | 69 | 70 |
71 |

如果我们要使用上传插件,我们首先需要引入我们的组件然后并在components中声明。 72 | 实现上传,我们需要定义我们上传的服务器地址url,然后我们需要指定上传完成后触发的方法,也就是@imageuploaded,这样我们才能获取上传完后的数据,从而进行下一步的操作。 73 |

74 |

查看详细文档

75 |
76 | ); 77 | } 78 | 79 | imageuploaded(res) { 80 | if (res.errcode == 0) { 81 | this.src = res.data.src; 82 | } 83 | } 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | 4 | export default class EnHome extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 |

13 | react core image upload 14 |

15 |

16 | Get Started 17 |

18 |

Chinese Document

19 |

react-core-image-upload is a lightweight plugin for developers to upload and crop images. There is also a good experience on mobile devices. We has define different type events for developers and they can control the file flow and do more thing they want.

20 | 21 |
22 | ); 23 | } 24 | 25 | goToChineseDoc() { 26 | vendor.setLocalData('lan', 'cn'); 27 | window.lan = 'cn'; 28 | location.href = './index.html#/cn/home'; 29 | location.reload(); 30 | } 31 | 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/multiple-file.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnMultipleFile extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | fileList: [], 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | 15 | render() { 16 | const trs = []; 17 | console.log(this.state.fileList); 18 | for(let i = 0; i < this.state.fileList.length; i++) { 19 | const item = this.state.fileList[i]; 20 | 21 | trs.push( 22 | 23 | {item.name} 24 | {item.size} 25 | 26 | ); 27 | } 28 | 29 | return ( 30 |
31 |

上传多个文件

32 |

multiple

33 |

你可以使用 multiple 属性设置为true来实现多文件的上传。需要注意的是,你设置了该属性后,服务端收到文件上传的字段数据会是一个数组。

34 |

multiple-size

35 |

你可以使用multiple-size来限制图片的数量,你可以限制上传文件的数量。

36 |

演示

37 |
38 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {trs} 56 | 57 |
文件名称文件大小
58 |

点击上传按钮,可以选择多张图片,然后在表格中可以看到上传的图片名称和图片大小。

59 |

Code Example

60 | {` 68 | `} 69 | 70 | 查看完整源码 71 |
72 | ); 73 | } 74 | 75 | imageUploded(res) { 76 | if (res.errcode === 0) { 77 | this.setState({ 78 | fileList: res.data, 79 | }); 80 | } 81 | } 82 | 83 | }; 84 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/others.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Others extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | render() { 9 | return ( 10 |
11 |

Feedback

12 |

You could find some solution from Github issue.

13 |

If you need help when you meet some trouble, it is free to contac with me via email:kakashjack@gmail.com

14 |

WeChat:

15 | wechat 16 |

Facebook Messager:

17 | Facebook Messager 18 |
19 | ); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/post-data.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactCoreImageUpload from '../../../../../src/index'; 3 | import Highlight from 'react-highlight'; 4 | 5 | export default class PostData extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | data: { 10 | text: '' 11 | }, 12 | }; 13 | this.change = this.change.bind(this); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 |

Send Data to Server

20 |

Setting data attribute will send some data you bind to the server via ajax. 21 | Of course you could pass data to server via header, just pass data to header.

22 |

Exmaple:

23 |

You could type some text here and its text will be sended to the server when you upload your image.

24 |
25 | 26 |

27 | 34 | 35 |
36 |

We could open chrome devtool to view http status when uploading image. Below is the result after uploading image successfully.

37 |

send server shoot

38 |

Code Example

39 | { 40 | ` 47 | ` 48 | } 49 |

Set isXhr equal false to cancel default ajax uploading.

50 | View Code 51 |
52 | ); 53 | } 54 | 55 | imageuploaded(res) { 56 | if (res.errcode == 0) { 57 | this.setState({ 58 | src: res.data.src, 59 | }); 60 | } 61 | } 62 | 63 | change(e) { 64 | var val = e.target.value; 65 | if (val) { 66 | this.setState({ 67 | data: { 68 | text: val, 69 | } 70 | }) 71 | } 72 | } 73 | 74 | }; 75 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/props.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class Props extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | render() { 11 | return ( 12 |
13 |

Props

14 |

Vue-core-image-upload supports many props to meet developers needs.

15 |

Code Example

16 | { 17 | ` 23 | ` 24 | } 25 |

class, url and max-file-size are different props. And the table below simply introduces how it work.

26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
PropsData TypeExampleDetails
urlString'/crop.php'Your server api
textString'Upload Image'The text of your uploading button
inputOfFileString      'file'Yout input[file] name
extensionsString'png,jpg,gif'Limit the image type
cropBoolean'server'Crop image option
cropRatioString'1:1'The cropped image shape(set 'auto' not limit the crop shape)
cropBtnObject{"{ok:'Save','cancel':'Give Up'}"}The Text of cropping button text
maxFileSizeNumber10485760(10M)Limit the size of the file
maxWidthNumber150The maximum width of cropped image
maxheightNumber150限制图片的最大高度
inputAcceptstring'image/*' / 'image/jpg,image/jpeg,image/png'the input[file] accept
compressNumber50Set the quality of compressed image
isXhrBooleantrueIF cancel ajax uploading
headersObject{"{auth: xxxxx}"}Set customed header when ajax uploading
dataObject{"{name: xxxxx}"}Set customed data when ajax posting server
129 |
130 |

url

131 |

url means a http url that ajax will send,and you need bind a function on @imageuploaded to handle the server response

132 |

inputOfFile

133 |

You can set input[file] name which the server must use via inputOfFile. The default value is files。 134 | If you upload multiple images the name will be files[]

135 |

extensions

136 |

If you want only one or two image types to upload to server, extensions is the right way when you pass an CSV string to it.

137 |

maxFileSize

138 |

Even though the server would check the file size , you cloud also limit the file size locally for thrifting the traffic of network.

139 |

isXhr

140 |

Set isXhr equal false, the uploading will be canceld and you can do something by yourself.

141 |

The naviagtion will show other props documents and demos

142 |
143 | ); 144 | } 145 | 146 | imageUploded(res) { 147 | if (res.errcode === 0) { 148 | this.setState({ 149 | src: res.data.src, 150 | }); 151 | } 152 | } 153 | 154 | }; 155 | -------------------------------------------------------------------------------- /__sites_/src/components/doc/en/resize-image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../../../lib/vendor'; 3 | import ReactCoreImageUpload from '../../../../../src/index'; 4 | import Highlight from 'react-highlight.js'; 5 | 6 | export default class CnResizeImage extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | src: 'http://img1.vued.vanthink.cn/vued0a233185b6027244f9d43e653227439a.png', 11 | }; 12 | this.imageUploded = this.imageUploded.bind(this); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |

Resize

19 |

Set resize props to help you to resize the imagey you want to upload.

20 |

resize="local" means you can resize image in local browser via canvas and it will send server the resized image.

21 |
22 |
23 | 24 |
25 | 31 | 32 |
33 |

Code Example

34 | 35 |

resize="server" means it will send the original image you upload,and it will send the server with data params imgChangeRatio.

36 | View Code Source 37 |
38 | ); 39 | } 40 | 41 | imageUploded(res) { 42 | if (res.errcode === 0) { 43 | this.setState({ 44 | src: res.data.src, 45 | }); 46 | } 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /__sites_/src/components/ft.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../lib/vendor' 3 | 4 | export default class Ft extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | const lan = vendor.getLocalData('lan') || 'cn'; 8 | this.state = { 9 | lan, 10 | }; 11 | this.changeCn = this.changeCn.bind(this); 12 | this.changeEn = this.changeEn.bind(this); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |

All Rights Reserved By Vanthink-UED

33 |

34 | 🇨🇳 Chinese 35 | 🇺🇸 English 36 |

37 |
38 | ); 39 | } 40 | 41 | changeCn() { 42 | vendor.setLocalData('lan', 'cn'); 43 | location.href="./index.html#/cn/home"; 44 | location.reload(); 45 | } 46 | 47 | changeEn() { 48 | vendor.setLocalData('lan', 'en'); 49 | location.href="./index.html#/en/home"; 50 | location.reload(); 51 | } 52 | 53 | 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /__sites_/src/components/hd.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import vendor from '../lib/vendor'; 3 | 4 | let language = vendor.getLocalData('data'); 5 | if (!language) { 6 | language = { 7 | label: '🇨🇳 Chinese', 8 | value: 'cn' 9 | } 10 | } else { 11 | language = language['lan'] 12 | } 13 | 14 | export default class Hd extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | language, 19 | options: [ 20 | { 21 | label: '🇨🇳 Chinese', 22 | value: 'cn', 23 | }, 24 | { 25 | label: '🇺🇸 English', 26 | value: 'en', 27 | }, 28 | ], 29 | }; 30 | } 31 | 32 | render() { 33 | return ( 34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 | Issues 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 | ); 53 | 54 | } 55 | 56 | toggleMenu() { 57 | const $aside = document.querySelector('aside'); 58 | if ($aside.className.indexOf('active') > -1) { 59 | $aside.className = ''; 60 | } else { 61 | $aside.className = 'active'; 62 | } 63 | } 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /__sites_/src/components/nav-list.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { routers } from '../lib/constants'; 3 | import vendor from '../lib/vendor'; 4 | 5 | export default class NavList extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | const lan = vendor.getLocalData('lan') || 'cn'; 9 | for (var item of routers) { 10 | item.url = '#/' + lan + '/' + item.url; 11 | if (lan !== 'en') { 12 | item.name = item.cn_name; 13 | } 14 | } 15 | this.state = { 16 | url: location.hash.replace(/\?.*/, ''), 17 | list: routers, 18 | }; 19 | this.setUrl = this.setUrl.bind(this); 20 | } 21 | 22 | setUrl(e) { 23 | const url = /\#(.*)/.exec(e.target.href)[0]; 24 | this.setState({ 25 | url, 26 | }); 27 | const $aside = document.querySelector('aside'); 28 | if ($aside) { 29 | $aside.classList.remove('active'); 30 | } 31 | 32 | } 33 | 34 | render() { 35 | const lis = []; 36 | for (let i = 0; i < this.state.list.length; i++) { 37 | const classname = this.state.list[i].url === this.state.url ? 'active' : ''; 38 | const item = this.state.list[i]; 39 | lis.push( 40 |
  • 41 | {item.name} 42 |
  • 43 | ); 44 | } 45 | return ( 46 | 52 | ); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /__sites_/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app'; 4 | 5 | require('./less/vtui.less'); 6 | 7 | ReactDOM.render(, document.getElementById('app')); 8 | -------------------------------------------------------------------------------- /__sites_/src/less/config.less: -------------------------------------------------------------------------------- 1 | /**config **/ 2 | 3 | @bgcolor: #f1f1f1; 4 | 5 | 6 | 7 | /***flat ui color**/ 8 | 9 | @turquoise: #1abc9c; 10 | @green-sea: #16a085; 11 | 12 | @emerald: #2ecc71; 13 | @nephritis: #27ae60; 14 | 15 | @peter-river: #3498db; 16 | @belize-hole: #2980b9; 17 | 18 | @amethyst: #9b59b6; 19 | @wisteria: #8e44ad; 20 | 21 | @wet-asphalt: #34495e; 22 | @midnight-blue: #2c3e50; 23 | 24 | @sun-flower: #f1c40f; 25 | @orange: #f39c12; 26 | 27 | @carrot: #e67e22; 28 | @pumpkin: #d35400; 29 | 30 | @alizarin: #e74c3c; 31 | @pomegranate: #c0392b; 32 | 33 | @clouds: #ecf0f1; 34 | @silver: #bdc3c7; 35 | 36 | @concrete: #95a5a6; 37 | @asbestos: #7f8c8d; 38 | 39 | /****/ 40 | 41 | @gray: @concrete; 42 | @gray-light: @silver; 43 | @inverse: white; 44 | 45 | // Brand colors 46 | @brand-primary: @wet-asphalt; 47 | @brand-secondary: @turquoise; 48 | @brand-success: @emerald; 49 | @brand-warning: @sun-flower; 50 | @brand-danger: @alizarin; 51 | @brand-info: @peter-river; 52 | 53 | @body-bg: #fff; 54 | @text-color: @brand-primary; 55 | 56 | //** Global textual link color. 57 | @link-color: @green-sea; 58 | @link-hover-color: @turquoise; 59 | //** Link hover decoration. 60 | @link-hover-decoration: none; 61 | 62 | 63 | //== Typography 64 | // 65 | //## Font, line-height for body text, headings, and more. 66 | 67 | @font-family-base: '\5FAE\8F6F\96C5\9ED1','\9ED1\4F53',arial,sans-serif; 68 | @font-family-demo: "Helvetica Neue", Helvetica, Arial, sans-serif; 69 | @font-family-monospace: Monaco, Menlo, Consolas, "Courier New", monospace; 70 | @font-size-base: 14px; 71 | 72 | @local-font-path: "../fonts/lato/"; 73 | @local-font-name: "lato-regular"; 74 | @local-font-svg-id: "latoregular"; 75 | @local-font-name-light: "lato-light"; 76 | @local-font-svg-id-light: "latolight"; 77 | @local-font-name-black: "lato-black"; 78 | @local-font-svg-id-black: "latoblack"; 79 | @local-font-name-bold: "lato-bold"; 80 | @local-font-svg-id-bold: "latobold"; 81 | @local-font-name-italic: "lato-italic"; 82 | @local-font-svg-id-italic: "latoitalic"; 83 | @local-font-name-bold-italic: "lato-bolditalic"; 84 | @local-font-svg-id-bold-italic: "latobold-italic"; 85 | 86 | @font-size-h1: floor((@font-size-base * 3.444)); // ~62px 87 | @font-size-h2: ceil((@font-size-base * 2.889)); // ~52px 88 | @font-size-h3: ceil((@font-size-base * 2.222)); // ~40px 89 | @font-size-h4: ceil((@font-size-base * 1.611)); // ~29px 90 | @font-size-h5: floor((@font-size-base * 1.556)); // ~28px 91 | @font-size-h6: ceil((@font-size-base * 1.333)); // ~24px 92 | 93 | @line-height-base: 1.72222; // 31/18 94 | @line-height-computed: floor((@font-size-base * @line-height-base)); // ~31px 95 | 96 | @headings-font-family: inherit; 97 | @headings-font-weight: 700; 98 | @headings-line-height: 1.1; 99 | @headings-color: inherit; 100 | 101 | 102 | //== Iconography 103 | // 104 | //## Specify custom locations of the include Glyphicons icon font. 105 | 106 | @icon-font-path: "../fonts/glyphicons/"; 107 | @icon-font-name: "flat-ui-icons-regular"; 108 | @icon-font-svg-id: "flat-ui-icons-regular"; 109 | 110 | //** Icon sizes for use in components 111 | @icon-normal: 16px; 112 | @icon-medium: 18px; 113 | @icon-large: 32px; 114 | 115 | 116 | //== Components 117 | // 118 | //## Define common padding and border radius sizes and more. 119 | 120 | //** Default font-size in components 121 | @component-font-size-base: ceil((@font-size-base * 0.833)); // ~15px 122 | 123 | // Border-radius 124 | @border-radius-base: 4px; 125 | @border-radius-large: 6px; 126 | @border-radius-small: 3px; 127 | 128 | //** Width of the `border` for generating carets that indicator dropdowns. 129 | @caret-width-base: 6px; 130 | @caret-width-base-vertical: (@caret-width-base + 2); 131 | 132 | @caret-width-xs: 4px; 133 | @caret-width-xs-vertical: (@caret-width-xs + 2); 134 | 135 | //== Buttons 136 | // 137 | //## For each of Flat UI's buttons, define text, background, font size and height. 138 | 139 | @btn-font-size-base: @component-font-size-base; 140 | @btn-font-size-xs: ceil((@component-font-size-base * 0.8)); // ~12px 141 | @btn-font-size-sm: floor((@component-font-size-base * 0.867)); // ~13px 142 | @btn-font-size-lg: ceil((@component-font-size-base * 1.133)); // ~17px 143 | @btn-font-size-hg: floor((@component-font-size-base * 1.467)); // ~22px 144 | 145 | @btn-line-height-base: 1.4; // ~21px 146 | @btn-line-height-hg: 1.227; // ~27px 147 | @btn-line-height-lg: 1.471; // ~25px 148 | @btn-line-height-sm: 1.385; // ~16px 149 | @btn-line-height-xs: 1.083; // ~13px 150 | 151 | @btn-social-font-size-base: floor((@component-font-size-base * 0.867)); // ~13px 152 | @btn-social-line-height-base: 1.077; // ~14px 153 | 154 | @btn-font-weight: normal; 155 | 156 | @btn-default-color: @inverse; 157 | @btn-default-bg: @gray-light; 158 | @btn-hover-bg: mix(@gray-light, white, 80%); 159 | @btn-active-bg: mix(@gray-light, black, 85%); 160 | 161 | @btn-primary-hover-bg: mix(@brand-secondary, white, 80%); 162 | @btn-primary-active-bg: mix(@brand-secondary, black, 85%); 163 | 164 | @btn-info-hover-bg: mix(@brand-info, white, 80%); 165 | @btn-info-active-bg: mix(@brand-info, black, 85%); 166 | 167 | @btn-success-hover-bg: mix(@brand-success, white, 80%); 168 | @btn-success-active-bg: mix(@brand-success, black, 85%); 169 | 170 | @btn-danger-hover-bg: mix(@brand-danger, white, 80%); 171 | @btn-danger-active-bg: mix(@brand-danger, black, 85%); 172 | 173 | @btn-warning-hover-bg: overlay(@brand-warning, darken(white, 37.5%)); 174 | @btn-warning-active-bg: mix(@brand-warning, black, 85%); 175 | 176 | @btn-inverse-hover-bg: overlay(@brand-primary, darken(white, 37.5%)); 177 | @btn-inverse-active-bg: mix(@brand-primary, black, 85%); 178 | 179 | @btn-link-disabled-color: @gray-light; 180 | 181 | 182 | //== Forms 183 | // 184 | //## 185 | 186 | @input-font-size-base: @component-font-size-base; 187 | @input-font-size-sm: floor((@component-font-size-base * 0.867)); // ~13px 188 | @input-font-size-lg: ceil((@component-font-size-base * 1.133)); // ~17px 189 | @input-font-size-hg: floor((@component-font-size-base * 1.467)); // ~22px 190 | 191 | @input-line-height-base: 1.467; // ~22px 192 | @input-line-height-sm: 1.462; // ~19px 193 | @input-line-height-lg: 1.235; // ~21px 194 | @input-line-height-hg: 1.318; // ~29px 195 | 196 | @input-icon-font-size: ceil((@component-font-size-base * 1.067)); // ~16px 197 | @input-icon-font-size-lg: ceil((@component-font-size-base * 1.2)); // ~18px 198 | @input-icon-font-size-hg: ceil((@component-font-size-base * 1.333)); // ~20px 199 | 200 | @input-bg: @inverse; 201 | @input-bg-disabled: mix(@gray, white, 10%); 202 | 203 | @input-height-sm: 35px; 204 | @input-height-base: 41px; 205 | @input-height-lg: 45px; 206 | @input-height-hg: 53px; 207 | 208 | @input-border-radius: @border-radius-large; 209 | 210 | //** Disabled cursor for form controls and buttons. 211 | @cursor-disabled: not-allowed; 212 | 213 | @legend-color: inherit; 214 | 215 | 216 | 217 | 218 | 219 | 220 | //== Tags Input 221 | // 222 | //## 223 | 224 | @tagsinput-container-bg: @inverse; 225 | @tagsinput-container-border-color: mix(@inverse, @brand-primary, 90%); 226 | @tagsinput-container-border-radius: @border-radius-large; 227 | 228 | @tagsinput-input-color: @brand-primary; 229 | 230 | @tagsinput-tag-bg: mix(@inverse, @brand-primary, 90%); 231 | @tagsinput-tag-color: mix(@brand-primary, @inverse, 65%); 232 | @tagsinput-tag-hover-bg: mix(@brand-secondary, black, 85%); 233 | @tagsinput-tag-hover-color: @inverse; 234 | @tagsinput-tag-icon-color: @inverse; 235 | @tagsinput-tag-border-radius: @border-radius-base; 236 | 237 | @tagsinput-primary-container-border-color: @brand-secondary; 238 | @tagsinput-primary-tag-bg: @brand-secondary; 239 | @tagsinput-primary-tag-color: @inverse; 240 | @tagsinput-primary-tag-hover-bg: mix(@brand-secondary, black, 85%); 241 | @tagsinput-primary-tag-hover-color: @inverse; 242 | 243 | 244 | //== Selects 245 | // 246 | //## For each of Flat UI's selects, define text, background, font size and height. 247 | 248 | @select-font-size-base: @btn-font-size-base; 249 | @select-font-size-sm: @btn-font-size-sm; 250 | @select-font-size-lg: @btn-font-size-lg; 251 | @select-font-size-hg: @btn-font-size-hg; 252 | 253 | @select-line-height-base: @btn-line-height-base; 254 | @select-line-height-hg: @btn-line-height-hg; 255 | @select-line-height-lg: @btn-line-height-lg; 256 | @select-line-height-sm: @btn-line-height-sm; 257 | 258 | @select-font-weight: @btn-font-weight; 259 | 260 | @select-disabled-opacity: 0.7; 261 | 262 | @select-default-color: @btn-default-color; 263 | @select-default-bg: @btn-default-bg; 264 | @select-default-hover-bg: @btn-hover-bg; 265 | @select-default-active-bg: @btn-active-bg; 266 | 267 | @select-primary-hover-bg: @btn-primary-hover-bg; 268 | @select-primary-active-bg: @btn-primary-active-bg; 269 | 270 | @select-info-hover-bg: @btn-info-hover-bg; 271 | @select-info-active-bg: @btn-info-active-bg; 272 | 273 | @select-success-hover-bg: @btn-success-hover-bg; 274 | @select-success-active-bg: @btn-success-active-bg; 275 | 276 | @select-danger-hover-bg: @btn-danger-hover-bg; 277 | @select-danger-active-bg: @btn-danger-active-bg; 278 | 279 | @select-warning-hover-bg: @btn-warning-hover-bg; 280 | @select-warning-active-bg: @btn-warning-active-bg; 281 | 282 | @select-inverse-hover-bg: @btn-inverse-hover-bg; 283 | @select-inverse-active-bg: @btn-inverse-active-bg; 284 | 285 | @select-link-disabled-color: @btn-link-disabled-color; 286 | @select-arrow-color: @brand-primary; 287 | 288 | // Select dropdowns 289 | @select-dropdown-border-radius: @border-radius-base; 290 | 291 | @select-dropdown-item-color: fade(@brand-primary, 85%); 292 | @select-dropdown-item-hover-color: inherit; 293 | @select-dropdown-item-hover-bg: mix(@inverse, @brand-primary, 85%); 294 | 295 | @select-dropdown-disabled-item-color: fade(@brand-primary, 95%); 296 | @select-dropdown-disabled-item-opacity: 0.4; 297 | 298 | @select-dropdown-highlighted-item-bg: @brand-secondary; 299 | @select-dropdown-highlighted-item-color: @inverse; 300 | 301 | @select-dropdown-optgroup-color: fade(@brand-primary, 60%); 302 | 303 | // Multiselect 304 | @multiselect-container-bg: @tagsinput-container-bg; 305 | @multiselect-container-border-color: @tagsinput-container-border-color; 306 | @multiselect-container-border-radius: @tagsinput-container-border-radius; 307 | 308 | @multiselect-tag-border-radius: @tagsinput-tag-border-radius; 309 | @multiselect-tag-color: @inverse; 310 | @multiselect-tag-hover-color: @tagsinput-tag-hover-color; 311 | @multiselect-tag-icon-color: @tagsinput-tag-icon-color; 312 | 313 | @multiselect-dropdown-border-radius: @border-radius-large; 314 | @multiselect-dropdown-item-border-radius: @border-radius-base; 315 | 316 | @multiselect-input-color: @tagsinput-input-color; 317 | 318 | 319 | //== Pagination 320 | // 321 | //## 322 | 323 | @pagination-bg: mix(@brand-primary, white, 20%); 324 | @pagination-hover-bg: @brand-secondary; 325 | @pagination-color: @inverse; 326 | @pagination-border-radius: @border-radius-large; 327 | 328 | 329 | //== Pager 330 | // 331 | //## 332 | 333 | @pager-padding: 9px 15px 10px; 334 | @pager-bg: @brand-primary; 335 | @pager-hover-bg: mix(@brand-primary, black, 85%); 336 | @pager-active-bg: @pager-hover-bg; 337 | @pager-border-radius: @border-radius-large; 338 | @pager-color: @inverse; 339 | 340 | 341 | 342 | 343 | //== Navbar 344 | // 345 | //## 346 | 347 | // Basics of a navbar 348 | @zindex-navbar: 1000; 349 | @zindex-navbar-fixed: 1030; 350 | @navbar-height-base: 53px; 351 | @navbar-height-large: 76px; 352 | @navbar-input-line-height: 1.4; // ~21px 353 | @navbar-margin-bottom: @line-height-computed; 354 | @navbar-border-radius: @border-radius-large; 355 | 356 | @navbar-default-bg: saturate(spin(tint(@brand-primary, 91%), -18), 2%); 357 | 358 | // Navbar links 359 | @navbar-default-link-color: @brand-primary; 360 | @navbar-default-link-hover-color: @brand-secondary; 361 | @navbar-default-link-hover-bg: transparent; 362 | @navbar-default-link-active-color: @brand-secondary; 363 | @navbar-default-link-active-bg: transparent; 364 | @navbar-default-link-disabled-color: #ccc; 365 | @navbar-default-link-disabled-bg: transparent; 366 | 367 | // Navbar nav carets 368 | @navbar-default-caret-color: @navbar-default-link-color; 369 | @navbar-default-caret-hover-color: @navbar-default-link-hover-color; 370 | @navbar-default-caret-active-color: @navbar-default-link-active-color; 371 | 372 | // Navbar brand label 373 | @navbar-default-brand-color: @navbar-default-link-color; 374 | @navbar-default-brand-hover-color: @navbar-default-link-hover-color; 375 | @navbar-default-brand-hover-bg: transparent; 376 | 377 | // Navbar toggle 378 | @navbar-default-toggle-color: @navbar-default-link-color; 379 | @navbar-default-toggle-hover-color: @navbar-default-link-hover-color; 380 | 381 | // Navbar form 382 | @navbar-default-form-placeholder: spin(tint(@brand-primary, 60%), 2); 383 | @navbar-default-form-icon: desaturate(tint(@brand-primary, 45%), 2%); 384 | @navbar-default-form-border: shade(@navbar-default-bg, 3%); 385 | 386 | 387 | // Inverted navbar 388 | // Reset inverted navbar basics 389 | @navbar-inverse-divider: darken(@brand-primary, 3%); 390 | 391 | // Reset inverted navbar basics 392 | @navbar-inverse-color: @inverse; 393 | @navbar-inverse-bg: @brand-primary; 394 | @navbar-inverse-border: darken(@navbar-inverse-bg, 10%); 395 | 396 | // Inverted navbar links 397 | @navbar-inverse-link-color: @inverse; 398 | @navbar-inverse-link-hover-color: @brand-secondary; 399 | @navbar-inverse-link-hover-bg: transparent; 400 | @navbar-inverse-link-active-color: @navbar-inverse-link-color; 401 | @navbar-inverse-link-active-bg: @brand-secondary; 402 | @navbar-inverse-link-disabled-color: #444; 403 | @navbar-inverse-link-disabled-bg: transparent; 404 | 405 | // Navbar nav carets 406 | @navbar-inverse-caret-color: lighten(desaturate(@brand-primary, 7%), 9%); 407 | @navbar-inverse-caret-hover-color: @navbar-inverse-link-hover-color; 408 | @navbar-inverse-caret-active-color: @navbar-inverse-link-active-color; 409 | 410 | // Inverted navbar brand label 411 | @navbar-inverse-brand-color: @navbar-inverse-link-color; 412 | @navbar-inverse-brand-hover-color: @navbar-inverse-link-hover-color; 413 | @navbar-inverse-brand-hover-bg: transparent; 414 | 415 | // Inverted navbar toggle 416 | @navbar-inverse-toggle-color: @navbar-inverse-link-color; 417 | @navbar-inverse-toggle-hover-color: @navbar-inverse-link-hover-color; 418 | 419 | // Navbar form 420 | @navbar-inverse-form-bg: darken(@brand-primary, 6%); 421 | @navbar-inverse-form-placeholder: desaturate(lighten(@brand-primary, 13%), 7%); 422 | @navbar-inverse-form-icon: desaturate(lighten(@brand-primary, 13%), 6%); 423 | @navbar-inverse-form-border: @navbar-inverse-divider; 424 | 425 | // Navbar dropdowns 426 | @navbar-inverse-dropdown-bg: @navbar-inverse-bg; 427 | @navbar-inverse-dropdown-link-color: mix(@navbar-inverse-bg, @navbar-inverse-color, 15%); 428 | @navbar-inverse-dropdown-link-hover-color: @inverse; 429 | @navbar-inverse-dropdown-link-hover-bg: @brand-secondary; 430 | 431 | //== Dropdowns 432 | // 433 | //## Dropdown menu container and contents. 434 | 435 | 436 | @zindex-dropdown: 1000; 437 | @dropdown-border-radius: @border-radius-base; 438 | 439 | //** Background for the dropdown menu. 440 | @dropdown-bg: desaturate(lighten(@brand-primary, 67%), 20%); 441 | 442 | //** Dropdown link text color. 443 | @dropdown-link-color: mix(darken(@brand-primary, 5%), @inverse, 75%); 444 | //** Hover color for dropdown links. 445 | @dropdown-link-hover-color: darken(@dropdown-link-color, 5%); 446 | //** Hover background for dropdown links. 447 | @dropdown-link-hover-bg: fade(desaturate(lighten(@brand-primary, 52%), 21%), 50%); 448 | 449 | //** Active dropdown menu item text color. 450 | @dropdown-link-active-color: @inverse; 451 | //** Active dropdown menu item background color. 452 | @dropdown-link-active-bg: @brand-secondary; 453 | 454 | //** Disabled dropdown menu item background color. 455 | @dropdown-link-disabled-color: @gray-light; 456 | 457 | //** Divider color for between dropdown items. 458 | @dropdown-divider-bg: fade(@dropdown-link-hover-bg, 50%); 459 | 460 | //** Text color for headers within dropdown menus. 461 | @dropdown-header-color: fade(@brand-primary, 60%); 462 | 463 | 464 | // Inverted dropdown 465 | // 466 | 467 | @dropdown-inverse-bg: @brand-primary; 468 | 469 | 470 | //** Dropdown link text color. 471 | @dropdown-inverse-link-color: fade(@inverse, 85%); 472 | //** Hover color for dropdown links. 473 | @dropdown-inverse-link-hover-color: fade(@inverse, 85%); 474 | //** Hover background for dropdown links. 475 | @dropdown-inverse-link-hover-bg: fade(darken(@brand-primary, 5%), 50%); 476 | 477 | //** Active dropdown menu item text color. 478 | @dropdown-inverse-link-active-color: fade(@inverse, 85%); 479 | //** Active dropdown menu item background color. 480 | @dropdown-inverse-link-active-bg: @brand-secondary; 481 | 482 | //** Disabled dropdown menu item background color. 483 | @dropdown-inverse-link-disabled-color: fade(@dropdown-inverse-link-color, 50%); 484 | 485 | //** Divider color for between dropdown items. 486 | @dropdown-inverse-divider-bg: @dropdown-inverse-link-hover-bg; 487 | 488 | //** Text color for headers within dropdown menus. 489 | @dropdown-inverse-header-color: fade(@inverse, 40%); 490 | 491 | 492 | //== Progress bars 493 | // 494 | //## 495 | 496 | @progress-height: 12px; 497 | 498 | 499 | //== Slider 500 | // 501 | //## 502 | 503 | @slider-height: 12px; 504 | @slider-value-font-size: floor((@component-font-size-base * 0.867)); // ~13px; 505 | 506 | @slider-handle-bg: mix(@brand-secondary, black, 85%); 507 | @slider-handle-hover-bg: mix(@brand-secondary, white, 80%); 508 | @slider-handle-active-bg: mix(@brand-secondary, black, 85%); 509 | 510 | @slider-range-bg: @brand-secondary; 511 | 512 | @slider-segment-bg: mix(desaturate(@brand-primary, 15%), white, 20%); 513 | 514 | 515 | //== Switch 516 | // 517 | //## 518 | 519 | @switch-name: bootstrap-switch; 520 | @switch-border-radius: 30px; 521 | @switch-width: 80px; 522 | @switch-height: 29px; 523 | 524 | 525 | 526 | //== Video player 527 | // 528 | //## 529 | 530 | @vplayer-border-radius: @border-radius-large; 531 | @vplayer-fullscreen-bg: #000; 532 | @vplayer-fullscreen-zindex: 10000; 533 | 534 | @vplayer-control-bar-color: @inverse; 535 | @vplayer-control-bar-bg: @midnight-blue; 536 | 537 | @vplayer-preloader-primary-bg: #e74c3c; 538 | @vplayer-preloader-secondary-bg: #ebedee; 539 | 540 | @vplayer-text-track-bg: rgba(0,0,0,.5); 541 | 542 | @vplaver-play-control-color: @brand-secondary; 543 | @vplaver-play-control-hover-color: mix(@brand-secondary, black, 85%); 544 | 545 | @vplaver-second-controls-color: desaturate(lighten(@midnight-blue, 12%), 6%); 546 | @vplaver-second-controls-hover-color: desaturate(lighten(@midnight-blue, 20%), 6%); 547 | 548 | @vplaver-progress-bg: mix(@brand-primary, @inverse, 93%); 549 | @vplaver-play-progress-bg: @brand-secondary; 550 | @vplaver-load-progress-bg: mix(@brand-primary, @inverse, 20%); 551 | 552 | @vplayer-seek-handle-bg: mix(@brand-secondary, black, 85%); 553 | @vplayer-seek-handle-hover-bg: mix(@brand-secondary, black, 75%); 554 | @vplayer-seek-handle-active-bg: mix(@brand-secondary, black, 65%); 555 | 556 | @vplayer-time-divider-color: mix(@brand-primary, @inverse, 80%); 557 | @vplayer-duration-color: mix(@brand-primary, @inverse, 80%); 558 | 559 | 560 | 561 | 562 | //== Todo list 563 | // 564 | //## 565 | 566 | @todo-bg: @brand-primary; 567 | @todo-bg-active: mix(@brand-primary, black, 85%); 568 | @todo-search-bg: @brand-secondary; 569 | @todo-search-color: @brand-primary; 570 | @todo-color: mix(@brand-primary, @inverse, 66%); 571 | @todo-name-color: @inverse; 572 | @todo-color-active: @brand-secondary; 573 | @todo-border-radius: @border-radius-large; 574 | 575 | 576 | //== Thumbnails 577 | // 578 | //## 579 | 580 | //** Padding around the thumbnail image 581 | @thumbnail-padding: 4px; 582 | //** Thumbnail background color 583 | @thumbnail-bg: @body-bg; 584 | //** Thumbnail border color 585 | @thumbnail-border: @gray-light; 586 | //** Thumbnail border radius 587 | @thumbnail-border-radius: @border-radius-large; 588 | 589 | //** Custom text color for thumbnail captions 590 | @thumbnail-caption-color: @text-color; 591 | //** Padding around the thumbnail caption 592 | @thumbnail-caption-padding: 9px; 593 | 594 | 595 | //== Tiles 596 | // 597 | //## 598 | 599 | @tiles-bg: mix(@brand-primary, @inverse, 8%); 600 | @tiles-border-radius: @border-radius-large; 601 | 602 | 603 | 604 | //== Media queries breakpoints 605 | // 606 | //## Define the breakpoints at which your layout will change, adapting to different screen sizes. 607 | 608 | // Extra small screen / phone 609 | @screen-xs-min: 480px; 610 | 611 | // Small screen / tablet 612 | @screen-sm-min: 768px; 613 | 614 | // Medium screen / desktop 615 | @screen-md-min: 992px; 616 | 617 | // Large screen / wide desktop 618 | @screen-lg-min: 1200px; 619 | 620 | // So media queries don't overlap when required, provide a maximum 621 | @screen-xs-max: (@screen-sm-min - 1); 622 | @screen-sm-max: (@screen-md-min - 1); 623 | @screen-md-max: (@screen-lg-min - 1); 624 | 625 | 626 | //== Grid system 627 | // 628 | //## Define your custom responsive grid. 629 | 630 | //** Number of columns in the grid. 631 | @grid-columns: 12; 632 | //** Padding between columns. Gets divided in half for the left and right. 633 | @grid-gutter-width: 30px; 634 | // Navbar collapse 635 | //** Point at which the navbar becomes uncollapsed. 636 | @grid-float-breakpoint: @screen-sm-min; 637 | //** Point at which the navbar begins collapsing. 638 | @grid-float-breakpoint-max: (@grid-float-breakpoint - 1); 639 | 640 | 641 | //== Form states and alerts 642 | // 643 | //## Define colors for form feedback states and, by default, alerts. 644 | 645 | @state-success-text: @brand-success; 646 | @state-success-bg: #dff0d8; 647 | @state-success-border: darken(spin(@state-success-bg, -10), 5%); 648 | 649 | @state-info-text: @brand-info; 650 | @state-info-bg: #d9edf7; 651 | @state-info-border: darken(spin(@state-info-bg, -10), 7%); 652 | 653 | @state-warning-text: @brand-warning; 654 | @state-warning-bg: #fcf8e3; 655 | @state-warning-border: darken(spin(@state-warning-bg, -10), 5%); 656 | 657 | @state-danger-text: @brand-danger; 658 | @state-danger-bg: #f2dede; 659 | @state-danger-border: darken(spin(@state-danger-bg, -10), 5%); 660 | 661 | 662 | //== Tooltips 663 | // 664 | //## 665 | 666 | //** Tooltip max width 667 | @tooltip-max-width: 183px; 668 | //** Tooltip text color 669 | @tooltip-color: @inverse; 670 | //** Tooltip background color 671 | @tooltip-bg: @brand-primary; 672 | @tooltip-opacity: 1; 673 | //** Tooltip zIndex 674 | @zindex-tooltip: 1070; 675 | 676 | //** Tooltip inverse text color 677 | @tooltip-inverse-color: @brand-primary; 678 | //** Tooltip inverse background color 679 | @tooltip-inverse-bg: mix(@brand-primary, white, 9%); 680 | 681 | //** Tooltip arrow width 682 | @tooltip-arrow-width: 9px; 683 | //** Tooltip arrow color 684 | @tooltip-arrow-color: @tooltip-bg; 685 | //** Tooltip inverse arrow color 686 | @tooltip-inverse-arrow-color: @tooltip-inverse-bg; 687 | 688 | 689 | 690 | 691 | //== Code 692 | // 693 | //## 694 | 695 | @code-color: #c7254e; 696 | @code-bg: #f9f2f4; 697 | 698 | @kbd-color: @inverse; 699 | @kbd-bg: @brand-primary; 700 | 701 | @pre-bg: @inverse; 702 | @pre-color: inherit; 703 | @pre-border-color: mix(@brand-primary, @inverse, 12%); 704 | @pre-scrollable-max-height: 340px; 705 | @pre-border-radius: @border-radius-large; 706 | 707 | 708 | //== Form states and alerts 709 | // 710 | //## 711 | 712 | //** Text muted color 713 | @text-muted: @gray-light; 714 | //** Abbreviations and acronyms border color 715 | @abbr-border-color: @gray-light; 716 | //** Headings small color 717 | @headings-small-color: mix(@brand-primary, @inverse, 12%); 718 | //** Blockquote small color 719 | @blockquote-small-color: inherit; 720 | //** Blockquote border color 721 | @blockquote-border-color: mix(@brand-primary, @inverse, 12%); 722 | //** Page header border color 723 | @page-header-border-color: mix(@brand-primary, @inverse, 12%); 724 | //** Width of horizontal description list titles 725 | @dl-horizontal-offset: @component-offset-horizontal; 726 | 727 | 728 | //== Miscellaneous 729 | // 730 | //## 731 | 732 | //** Hr border color 733 | @hr-border: mix(@brand-primary, @inverse, 63%); 734 | 735 | //** Horizontal forms & lists 736 | @component-offset-horizontal: 180px; 737 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-button.less: -------------------------------------------------------------------------------- 1 | /** just for button **/ 2 | .btn { 3 | display: inline-block; 4 | line-height: 30px; 5 | padding: 0 15px; 6 | border-radius: 2px; 7 | background: #fff; 8 | border: 1px solid #e7eaec; 9 | min-width: 46px; 10 | color:#323c48; 11 | text-align: center; 12 | transition: all .15s ease; 13 | font-size: 13px; 14 | cursor: pointer; 15 | outline: none !important; 16 | box-shadow: 0 1px 2px -1px rgba(255,255,255,0.1); 17 | transition: all .25s ease; 18 | } 19 | .btn-sl{ 20 | padding: 0 8px; 21 | line-height: 22px; 22 | font-size: 12px; 23 | } 24 | .btn:hover { 25 | border-color: #d2d2d2; 26 | box-shadow: 0 2px 4px -2px rgba(255,255,255,0.2); 27 | } 28 | .btn:disabled, 29 | .btn:disabled:hover{ 30 | background: #b4b4b4 !important; 31 | color:#fff; 32 | border-color: #ccc; 33 | box-shadow: none; 34 | cursor: not-allowed; 35 | } 36 | 37 | .submit-btn { 38 | margin-right: 6px; 39 | } 40 | .btn-primary, 41 | .submit-btn, 42 | .btn.active{ 43 | color:#fff; 44 | background: @peter-river; 45 | border-color: @peter-river; 46 | } 47 | .btn-primary:focus, 48 | .btn-primary:active, 49 | .submit-btn:focus, 50 | .submit-btn:active, 51 | .btn.active:focuss, 52 | .btn.active:active{ 53 | border-color:@belize-hole; 54 | } 55 | .btn-primary:hover, 56 | .submit-btn:hover, 57 | .btn.active:hover{ 58 | background: @belize-hole; 59 | border-color: @belize-hole; 60 | color: #fff; 61 | } 62 | .btn-info{ 63 | color:@peter-river; 64 | background: #fff; 65 | border-color:@peter-river; 66 | } 67 | .btn-info:focus, 68 | .btn-info:active{ 69 | background: @belize-hole; 70 | border-color: @belize-hole; 71 | color:#fff; 72 | } 73 | .btn-info:hover{ 74 | background: @belize-hole; 75 | border-color:@belize-hole; 76 | color:#fff; 77 | } 78 | a.btn-info:hover { 79 | color: #fff; 80 | } 81 | 82 | 83 | .btn-warning{ 84 | color:#fff; 85 | background: #faa732; 86 | border-color:#faa732; 87 | } 88 | .btn-warning:focus, 89 | .btn-warning:active{ 90 | border-color:#dd942e; 91 | } 92 | .btn-warning:hover{ 93 | background: #dd942e; 94 | border-color: #dd942e; 95 | } 96 | 97 | .btn-danger{ 98 | color:#fff; 99 | background: #ea6153; 100 | border-color:#d14233; 101 | } 102 | .btn-danger:focus, 103 | .btn-danger:active{ 104 | border-color:#d14233; 105 | } 106 | .btn-danger:hover{ 107 | background: #dc5b4e; 108 | border-color: #bd2b41; 109 | } 110 | .btn-link{ 111 | line-height: 28px; 112 | padding: 0 15px; 113 | background-color: transparent; 114 | border:none; 115 | color:#00a8e6; 116 | } 117 | .btn-link:focus, 118 | .btn-link:active{ 119 | color:#005598; 120 | } 121 | .btn-link:hover{ 122 | color:#0077dd; 123 | border:0px; 124 | } 125 | .btn-lg{ 126 | line-height: 36px; 127 | padding: 0 30px; 128 | } 129 | .btn-sm{ 130 | line-height: 28px; 131 | padding: 0 15px; 132 | } 133 | 134 | .btn-icon{ 135 | position: relative; 136 | width: 32px; 137 | min-width: 32px; 138 | height: 32px; 139 | padding: 0; 140 | margin-right: 15px; 141 | text-align: center; 142 | } 143 | .btn.btn-icon .icon{ 144 | margin: 0; 145 | font-size: 0.9rem; 146 | } 147 | .btn-icon:hover .icon-tips-box{ 148 | display: inline-block; 149 | } 150 | .icon-tips-box{ 151 | z-index: 5; 152 | position: absolute; 153 | display: none; 154 | left:100%; 155 | top:50%; 156 | margin-top: -15px; 157 | min-width: 56px; 158 | height: 30px; 159 | line-height: 30px; 160 | margin-left: 10px; 161 | padding: 0 15px; 162 | border-radius: 3px; 163 | color:#f1f1f1; 164 | background: rgba(0,0,0,.8); 165 | background-color: #333; 166 | font-size: 12px; 167 | } 168 | .icon-tips-box:before{ 169 | content: ''; 170 | position: absolute; 171 | display: inline-block; 172 | top:50%; 173 | margin-top: -6px; 174 | left:-12px; 175 | border-width:6px; 176 | border-style: solid; 177 | border-color: transparent rgba(0,0,0,.8) transparent transparent ; 178 | } 179 | .btn-icon.left .icon-tips-box{ 180 | left:inherit; 181 | right: 100%; 182 | margin-right: 5px; 183 | } 184 | .btn-icon.left .icon-tips-box:before{ 185 | left:inherit; 186 | right:-12px; 187 | border-color: transparent transparent transparent rgba(0,0,0,.8) ; 188 | 189 | } 190 | .btn-group .btn, 191 | .btn-group .text{ 192 | float: left; 193 | margin-left: 0p; 194 | margin-right: -1px; 195 | } 196 | .btn-group .btn.active{ 197 | background: #00a8e6; 198 | border-color: #00a8e6; 199 | color:#fff; 200 | } 201 | .btn-group .btn.active:hover{ 202 | background: #009cd6; 203 | } 204 | .btn-group.hover-group{ 205 | padding-right: 10px; 206 | } 207 | .btn-group.hover-group .btn-primary{ 208 | opacity: 0; 209 | } 210 | .btn-group.hover-group:hover .btn-primary{ 211 | opacity: 1; 212 | } 213 | 214 | 215 | .btn .icon{ 216 | font-size: 16px; 217 | margin-right: 6px; 218 | } 219 | .btn-noborder { 220 | background: none; 221 | border: none 0; 222 | padding: 0 15px; 223 | } 224 | .btn-noborder:hover{ 225 | color:#333; 226 | } 227 | 228 | 229 | /*.btn.btn-default { 230 | background: #fff; 231 | color: #636365; 232 | border: 1px solid #D8DCE1; 233 | border-radius: 2px; 234 | line-height: 30px; 235 | padding: 0 10px; 236 | } 237 | 238 | 239 | .btn-noborder i { 240 | font-size: 0.85714285714286rem; 241 | border: 1px solid #707070; 242 | color: #707070; 243 | border-radius: 2px; 244 | padding: 1px 2px; 245 | background: #fff; 246 | } 247 | .btn-noborder:hover i { 248 | border: 1px solid #61bbf4; 249 | color: #61bbf4; 250 | } 251 | .btn-default:hover, 252 | .btn-default:focus, 253 | .btn-default:active, 254 | .btn-default.active, 255 | .open .dropdown-toggle.btn-default { 256 | color: #333333; 257 | background-color: #ebebeb; 258 | border-color: #adadad; 259 | } 260 | 261 | .btn-default:active, 262 | .btn-default.active, 263 | .open .dropdown-toggle.btn-default { 264 | background-image: none; 265 | } 266 | 267 | .btn-default.disabled, 268 | .btn-default[disabled], 269 | fieldset[disabled] .btn-default, 270 | .btn-default.disabled:hover, 271 | .btn-default[disabled]:hover, 272 | fieldset[disabled] .btn-default:hover, 273 | .btn-default.disabled:focus, 274 | .btn-default[disabled]:focus, 275 | fieldset[disabled] .btn-default:focus, 276 | .btn-default.disabled:active, 277 | .btn-default[disabled]:active, 278 | fieldset[disabled] .btn-default:active, 279 | .btn-default.disabled.active, 280 | .btn-default[disabled].active, 281 | fieldset[disabled] .btn-default.active { 282 | background-color: #ffffff; 283 | border-color: #cccccc; 284 | }*/ 285 | .btn-group, 286 | .btn-group-vertical { 287 | position: relative; 288 | display: inline-block; 289 | vertical-align: middle; 290 | } 291 | .btn-group > .btn, 292 | .btn-group-vertical > .btn { 293 | position: relative; 294 | float: left; 295 | } 296 | .btn-group > .btn:focus, 297 | .btn-group-vertical > .btn:focus { 298 | outline: none; 299 | } 300 | .btn-group .btn + .btn, 301 | .btn-group .btn + .btn-group, 302 | .btn-group .btn-group + .btn, 303 | .btn-group .btn-group + .btn-group { 304 | margin-left: -1px; 305 | } 306 | .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { 307 | border-radius: 0; 308 | } 309 | .btn-group > .btn:first-child { 310 | margin-left: 0; 311 | } 312 | .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { 313 | border-top-right-radius: 0; 314 | border-bottom-right-radius: 0; 315 | } 316 | .btn-group > .btn:last-child:not(:first-child), 317 | .btn-group > .dropdown-toggle:not(:first-child) { 318 | border-top-left-radius: 0; 319 | border-bottom-left-radius: 0; 320 | } 321 | .btn-group > .btn-group { 322 | float: left; 323 | } 324 | .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { 325 | border-radius: 0; 326 | } 327 | .btn-group > .btn-group:first-child > .btn:last-child, 328 | .btn-group > .btn-group:first-child > .dropdown-toggle { 329 | border-top-right-radius: 0; 330 | border-bottom-right-radius: 0; 331 | } 332 | .btn-group > .btn-group:last-child > .btn:first-child { 333 | border-top-left-radius: 0; 334 | border-bottom-left-radius: 0; 335 | } 336 | .btn-group .dropdown-toggle:active, 337 | .btn-group.open .dropdown-toggle { 338 | outline: 0; 339 | } 340 | .btn-group > .btn + .dropdown-toggle { 341 | padding-right: 8px; 342 | padding-left: 8px; 343 | } 344 | .btn-group > .btn-lg + .dropdown-toggle { 345 | padding-right: 12px; 346 | padding-left: 12px; 347 | } 348 | .btn-group.open .dropdown-toggle { 349 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 350 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 351 | } 352 | .btn-group.open .dropdown-toggle.btn-link { 353 | -webkit-box-shadow: none; 354 | box-shadow: none; 355 | } 356 | 357 | .btn-circle{ 358 | position: relative; 359 | width: 60px; 360 | height: 60px; 361 | background: #fff; 362 | border-radius: 30px; 363 | box-shadow: 0px 2px 5px rgba(0,0,0,.05); 364 | line-height: 60px; 365 | font-size: 18px; 366 | color:#999; 367 | } 368 | .btn-circle:hover{ 369 | box-shadow: 0px 5px 12px rgba(0,0,0,.1); 370 | } 371 | .btn-circle:hover:after{ 372 | content: attr(data-tooltip); 373 | position: absolute; 374 | top:70px; 375 | width: 80px; 376 | left:-10px; 377 | background: #333; 378 | background: rgba(0,0,0,.8); 379 | border-radius: 2px; 380 | color: #fff; 381 | padding: 0 10px; 382 | font-size: 12px; 383 | line-height: 25px; 384 | } 385 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-icon.less: -------------------------------------------------------------------------------- 1 | .svg-icon{ 2 | width: 1.8em; 3 | height: 1.8em; 4 | fill: #222; 5 | } 6 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-layout.less: -------------------------------------------------------------------------------- 1 | /** layout **/ 2 | .container { 3 | position: relative; 4 | max-width: 1140px; 5 | margin: 0 auto; 6 | } 7 | 8 | .nav { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | right: 0; 13 | bottom: 0; 14 | z-index: 5; 15 | height: 70px; 16 | background: #fff; 17 | margin-bottom: 0; 18 | font-size: 1.5rem; 19 | line-height: 70px; 20 | box-shadow: 0px 1px 3px -1px rgba(0, 0, 0, 0.1); 21 | opacity: .95; 22 | } 23 | 24 | .nav-logo { 25 | display: inline-block; 26 | width: 45px; 27 | height: 45px; 28 | background-image: url(http://img1.vued.vanthink.cn/vued7cecc028b8b640e58157bf4f2dd17184.png); 29 | background-size: cover; 30 | vertical-align: middle; 31 | } 32 | 33 | .nav-title { 34 | position: absolute; 35 | top: 45px; 36 | left: 30px; 37 | font-size: 16px; 38 | font-weight: 100; 39 | text-transform: uppercase; 40 | color: #fff; 41 | } 42 | 43 | .menu{ 44 | display: none; 45 | } 46 | .nav-list { 47 | float: right; 48 | z-index: 100; 49 | height: 40px; 50 | line-height: 40px; 51 | margin-top: 15px; 52 | margin-left: 2rem; 53 | transition: all .25s ease; 54 | text-align: right; 55 | } 56 | 57 | 58 | 59 | .nav-list a { 60 | text-decoration: none; 61 | font-size: 14px; 62 | display: inline-block; 63 | padding: 0 1rem; 64 | overflow: hidden; 65 | white-space: nowrap; 66 | text-overflow: ellipsis; 67 | transition: all .25s ease; 68 | color: #444; 69 | } 70 | 71 | .nav-list:hover a { 72 | opacity: 1; 73 | color:#111; 74 | } 75 | .nav-list a.active{ 76 | color:#000; 77 | } 78 | .nav-list .icon-github{ 79 | margin-top:2px; 80 | transform:translateY(4px) 81 | } 82 | aside{ 83 | float: left; 84 | position: relative; 85 | width: 220px; 86 | padding: 100px 15px 40px 20px; 87 | h4{ 88 | margin: 0; 89 | background-color: #bbb; 90 | color: #fff; 91 | padding: 10px; 92 | font-size: 15px; 93 | } 94 | ul{ 95 | background-color: #e1e1e1; 96 | padding-bottom: 20px; 97 | } 98 | li{ 99 | margin-bottom: 5px; 100 | margin-top: 0; 101 | } 102 | a{ 103 | display: inline-block; 104 | line-height: 30px; 105 | color: #777; 106 | padding-left: 15px; 107 | &.active{ 108 | color: @peter-river; 109 | } 110 | } 111 | 112 | } 113 | 114 | 115 | 116 | .main{ 117 | margin-left: 240px; 118 | max-width: 900px; 119 | min-height: 400px; 120 | padding: 100px 20px; 121 | 122 | .components { 123 | text-align: left; 124 | 125 | .center{ 126 | text-align: center; 127 | padding-bottom: 20px; 128 | } 129 | } 130 | } 131 | 132 | .ft{ 133 | background: #fff; 134 | padding-top: 120px; 135 | padding-bottom: 40px; 136 | margin-top: 40px; 137 | text-align: center; 138 | } 139 | .ft .icon-list a{ 140 | position: relative; 141 | display: inline-block; 142 | width: 75px; 143 | height: 75px; 144 | margin: 20px; 145 | } 146 | 147 | .ft .other-info { 148 | font-size: 13px; 149 | color:#888; 150 | } 151 | 152 | .ft .lang-list a { 153 | margin: 0 10px; 154 | color: #999; 155 | 156 | &.active { 157 | color: @emerald; 158 | } 159 | } 160 | 161 | @media all and (max-width: 764px) { 162 | .nav{ 163 | height: 40px; 164 | line-height: 40px; 165 | 166 | } 167 | .menu { 168 | margin-left: 10px; 169 | margin-top: 4px; 170 | display: inline-block; 171 | .svg-icon{ 172 | width: 24px; 173 | height: 24px; 174 | } 175 | } 176 | 177 | .nav-logo { 178 | display: none; 179 | } 180 | .nav-list{ 181 | margin-top: 0; 182 | } 183 | 184 | 185 | aside{ 186 | z-index: 9; 187 | position: fixed; 188 | left: -250px; 189 | top: 40px; 190 | bottom: 0; 191 | padding: 10px; 192 | background-color: #fff; 193 | box-shadow: 3px 4px 5px rgba(0,0,0,.25); 194 | transition: left ease .25s; 195 | &.active{ 196 | left: 0; 197 | } 198 | h4, ul { 199 | background-color: #fff; 200 | } 201 | 202 | } 203 | .main { 204 | margin-left: 10px; 205 | padding-top: 60px; 206 | } 207 | 208 | .ft .icon-list a{ 209 | width: 40px; 210 | margin: 15px; 211 | img{ 212 | width: 100%; 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-pages.less: -------------------------------------------------------------------------------- 1 | .c-home { 2 | text-align: center; 3 | } 4 | .c-home img { 5 | width: 360px; 6 | } 7 | .c-home .btn-go-started{ 8 | border-radius: 25px; 9 | padding: 5px 40px; 10 | } 11 | .circle-bar{ 12 | display: inline-block; 13 | width: 20px; 14 | height: 20px; 15 | border-radius: 10px; 16 | background-color: #999; 17 | } 18 | .circle-bar.active{ 19 | background-color: #2ecc71; 20 | } 21 | 22 | .avatar { 23 | width: 150px; 24 | height: 150px; 25 | margin-bottom: 20px; 26 | border-radius: 50%; 27 | border: 2px solid rgba(0,0,0,.05); 28 | } 29 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-pagination.less: -------------------------------------------------------------------------------- 1 | .m-pagination{ 2 | position: relative; 3 | text-align: center; 4 | margin-top: 20px; 5 | } 6 | 7 | .m-pagination li,.m-pagination li a{ 8 | display: inline-block; 9 | width:34px; 10 | height: 34px; 11 | line-height: 32px; 12 | background: #fff; 13 | color:#313c48; 14 | 15 | } 16 | .m-pagination li{ 17 | margin-left: 5px; 18 | } 19 | .m-pagination li a{ 20 | border:1px solid #e6eaed; 21 | border-radius: 2px; 22 | } 23 | .m-pagination li a:hover{ 24 | border-color: #00a8e6; 25 | color:#00a8e6; 26 | } 27 | .m-pagination li a.active, 28 | .m-pagination li.active a{ 29 | background:#00a8e6; 30 | border-color: #00a8e6; 31 | color:#fff; 32 | } 33 | 34 | 35 | .m-pagination li a.disabled,.m-pagination li.disabled a{ 36 | background: #b4b4b4; 37 | border-color: #b4b4b4; 38 | color: #fff; 39 | cursor: not-allowed; 40 | } 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-table.less: -------------------------------------------------------------------------------- 1 | .m-table-label { 2 | margin-bottom: 24px; 3 | border-bottom:1px solid #ddd; 4 | line-height: 49px; 5 | color:#313c48; 6 | } 7 | .m-table thead th,.m-table tbody td { 8 | padding:10px 14px; 9 | border-bottom: 1px solid #d9dde3; 10 | border-collapse: collapse; 11 | } 12 | .m-table { 13 | width:100%; 14 | text-align: left; 15 | line-height: 100%; 16 | thead { 17 | background: #fff; 18 | border-bottom: 2px solid #d9dde3; 19 | th { 20 | border-width: 0px; 21 | text-align: left; 22 | } 23 | } 24 | tbody { 25 | tr.disabled td{ 26 | background: #e3e3e3 !important; 27 | cursor: not-allowed; 28 | } 29 | td.rowspan-td { 30 | border-right: 1px solid #d9dde3; 31 | border-left: 1px solid #d9dde3; 32 | padding-left:48px; 33 | text-align: left; 34 | } 35 | td.rowspan-right { 36 | border-right: 1px solid #d9dde3; 37 | } 38 | tr:hover { 39 | td { 40 | background-color: #fff; 41 | } 42 | td.rowspan-td { 43 | background: none; 44 | } 45 | } 46 | tr.error { 47 | background: #faf2ca !important; 48 | } 49 | tr { 50 | 51 | 52 | td { 53 | .icon-btn { 54 | color: #707070; 55 | border: none; 56 | background: transparent; 57 | } 58 | .icon-btn:hover { 59 | color: #61bbf4; 60 | } 61 | .icon-btn[disabled] { 62 | color: #ccc; 63 | } 64 | 65 | } 66 | 67 | &.selected{ 68 | 69 | border-left:3px solid @peter-river; 70 | 71 | } 72 | } 73 | td.inner:only-child { 74 | padding-right: 48px; 75 | padding-top: 10px; 76 | padding-bottom: 10px; 77 | } 78 | td.inner:only-child:hover { 79 | background-color: transparent; 80 | } 81 | td.inner { 82 | th { 83 | text-align: center; 84 | } 85 | th:first-child { 86 | text-align: left; 87 | } 88 | td { 89 | background-color: transparent; 90 | } 91 | tr:hover { 92 | td { 93 | background-color: #f8f8f8; 94 | } 95 | } 96 | } 97 | tr.end-noborder td{ 98 | border-bottom-width: 0px; 99 | } 100 | 101 | } 102 | input[type="checkbox"] { 103 | margin-right:10px; 104 | } 105 | 106 | .text-link { 107 | color: #61bbf4; 108 | } 109 | .text-link:hover { 110 | color: #707070; 111 | } 112 | td.inner { 113 | & > table { 114 | width: 100%; 115 | thead { 116 | background: #d9dde3; 117 | border-color: #d9dde3; 118 | color: #707070; 119 | } 120 | } 121 | } 122 | } 123 | 124 | .disable { 125 | tbody { 126 | tr:hover { 127 | td { 128 | background: #f8f8f8; 129 | } 130 | } 131 | } 132 | } 133 | .no-bottom-border{ 134 | tbody tr td { 135 | border-bottom-width: 0px; 136 | } 137 | } 138 | .bordered { 139 | thead { 140 | tr th{ 141 | border:1px solid #d9dde3; 142 | } 143 | } 144 | tbody { 145 | td:first-child { 146 | border-left: 1px solid #d9dde3; 147 | } 148 | td:last-child { 149 | border-right: 1px solid #d9dde3; 150 | } 151 | tr:hover { 152 | td { 153 | background: #fff; 154 | } 155 | } 156 | } 157 | thead { 158 | background: #f5f5f7; 159 | } 160 | } 161 | .bordered thead,.bordered tbody tr td { 162 | border:1px solid #d9dde3; 163 | } 164 | .centered { 165 | text-align: center; 166 | } 167 | .m-table.dark { 168 | thead { 169 | border-color: #e5eef5; 170 | border:1px solid #d9dde3; 171 | border-bottom: none; 172 | tr { 173 | background: #e5eef5; 174 | height: 30px; 175 | th { 176 | color:#687178; 177 | } 178 | } 179 | } 180 | } 181 | .dark { 182 | tbody { 183 | tr:nth-child(even) { 184 | background: #fff; 185 | } 186 | tr { 187 | td:first-child { 188 | border-left:1px solid #d9dde3; 189 | } 190 | td:last-child { 191 | border-right:1px solid #d9dde3; 192 | } 193 | } 194 | } 195 | } 196 | .tbl-form { 197 | tbody { 198 | tr { 199 | td { 200 | padding-top: 10px; 201 | border-bottom: 0px; 202 | } 203 | } 204 | } 205 | } 206 | 207 | .hover{ 208 | tbody tr:hover td{ 209 | background: #f3f3f3; 210 | cursor: pointer; 211 | } 212 | } 213 | 214 | .no-bordered tbody tr td, 215 | .no-bordered thead, 216 | .no-bordered thead tr th 217 | { 218 | border-width: 0px !important; 219 | } 220 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/m-type.less: -------------------------------------------------------------------------------- 1 | // 2 | // Typography 3 | // -------------------------------------------------- 4 | // Headings 5 | // ------------------------- 6 | a { 7 | text-decoration: none; 8 | color: @peter-river; 9 | } 10 | a:hover, 11 | a:active{ 12 | color: @belize-hole; 13 | } 14 | h1, 15 | h2, 16 | h3, 17 | h4, 18 | h5, 19 | h6, 20 | .h1, 21 | .h2, 22 | .h3, 23 | .h4, 24 | .h5, 25 | .h6 { 26 | font-family: @headings-font-family; 27 | font-weight: @headings-font-weight; 28 | line-height: @headings-line-height; 29 | color: @headings-color; 30 | small { 31 | color: @headings-small-color; 32 | } 33 | } 34 | 35 | h1, 36 | h2, 37 | h3 { 38 | margin-top: @line-height-computed; 39 | margin-bottom: (@line-height-computed / 2); 40 | padding-bottom: (@line-height-computed / 2); 41 | } 42 | 43 | h3 { 44 | border-bottom: 1px solid #ddd; 45 | } 46 | 47 | h4, 48 | h5, 49 | h6 { 50 | margin-top: (@line-height-computed / 2); 51 | margin-bottom: (@line-height-computed / 2); 52 | } 53 | 54 | h6 { 55 | font-weight: normal; 56 | } 57 | 58 | h1, 59 | .h1 { 60 | font-size: @font-size-h1; 61 | } // ~62px 62 | h2, 63 | .h2 { 64 | font-size: @font-size-h2; 65 | } // ~52px 66 | h3, 67 | .h3 { 68 | font-size: @font-size-h3; 69 | } // ~40px 70 | h4, 71 | .h4 { 72 | font-size: @font-size-h4; 73 | } // ~29px 74 | h5, 75 | .h5 { 76 | font-size: @font-size-h5; 77 | } // ~28px 78 | h6, 79 | .h6 { 80 | font-size: @font-size-h6; 81 | } // ~24px 82 | // Body text 83 | // ------------------------- 84 | p { 85 | font-size: @font-size-base; 86 | line-height: @line-height-base; 87 | margin: 0 0 (@line-height-computed / 2); 88 | } 89 | 90 | .lead { 91 | margin-bottom: @line-height-computed; 92 | font-size: floor((@font-size-base * 1.556)); // ~28px 93 | line-height: 1.46428571; // ~41px 94 | font-weight: 300; 95 | @media (min-width: @screen-sm-min) { 96 | font-size: (@font-size-base * 1.667); // ~30px 97 | } 98 | } 99 | 100 | // Emphasis & misc 101 | // ------------------------- 102 | // Ex: 18px base font * 83% = about 15px 103 | small, 104 | .small { 105 | font-size: 83%; // ~15px 106 | line-height: 2.067; // ~31px 107 | } 108 | 109 | // Contextual emphasis 110 | .text-muted { 111 | color: @text-muted; 112 | } 113 | 114 | .text-inverse { 115 | color: @inverse; 116 | } 117 | 118 | // Page header 119 | // ------------------------- 120 | .page-header { 121 | padding-bottom: ((@line-height-computed / 2) - 1); 122 | margin: (@line-height-computed * 2) 0 @line-height-computed; 123 | border-bottom: 2px solid @page-header-border-color; 124 | } 125 | 126 | // Lists 127 | // -------------------------------------------------- 128 | // Unordered and Ordered lists 129 | ul, 130 | ol { 131 | margin-bottom: (@line-height-computed / 2); 132 | } 133 | li{ 134 | margin-top: (@line-height-computed / 2); 135 | } 136 | // Description Lists 137 | dl { 138 | margin-bottom: @line-height-computed; 139 | } 140 | 141 | dt, 142 | dd { 143 | line-height: @line-height-base; 144 | } 145 | 146 | // Horizontal description lists 147 | // 148 | // Defaults to being stacked without any of the below styles applied, until the 149 | // grid breakpoint is reached (default of ~768px). 150 | @media (min-width: @grid-float-breakpoint) { 151 | .dl-horizontal { 152 | dt { 153 | width: (@dl-horizontal-offset - 20); 154 | } 155 | dd { 156 | margin-left: @dl-horizontal-offset; 157 | } 158 | } 159 | } 160 | 161 | // MISC 162 | // ---- 163 | // Abbreviations and acronyms 164 | abbr[title], 165 | abbr[data-original-title] { 166 | border-bottom: 1px dotted @abbr-border-color; 167 | } 168 | 169 | // Blockquotes 170 | blockquote { 171 | border-left: 3px solid @blockquote-border-color; 172 | padding: 0 0 0 16px; 173 | margin: 0 0 @line-height-computed; 174 | p { 175 | font-size: ceil((@font-size-base * 1.111)); // ~20px 176 | line-height: 1.55; // ~31px 177 | font-weight: normal; 178 | margin-bottom: .4em; 179 | } 180 | small, 181 | .small { 182 | font-size: @font-size-base; 183 | line-height: @line-height-base; 184 | font-style: italic; 185 | color: @blockquote-small-color; 186 | &:before { 187 | content: ""; 188 | } 189 | } 190 | // Float right with text-align: right 191 | &.pull-right { 192 | padding-right: 16px; 193 | padding-left: 0; 194 | border-right: 3px solid @blockquote-border-color; 195 | border-left: 0; 196 | small:after { 197 | content: ""; 198 | } 199 | } 200 | } 201 | 202 | // Addresses 203 | address { 204 | margin-bottom: @line-height-computed; 205 | line-height: @line-height-base; 206 | } 207 | 208 | // Sup and Sub 209 | sub, 210 | sup { 211 | font-size: 70%; 212 | } 213 | 214 | hr { 215 | display: block; 216 | height: 1px; 217 | border: 0; 218 | border-top: #EFEFEF 1px solid; 219 | margin: 3.2em 0; 220 | padding: 0; 221 | } 222 | 223 | blockquote { 224 | -moz-box-sizing: border-box; 225 | box-sizing: border-box; 226 | margin: 1.75em 0 1.75em -2.2em; 227 | padding: 0 0 0 1.75em; 228 | border-left: #3498db 0.4em solid; 229 | } 230 | 231 | blockquote p { 232 | margin: 0.8em 0; 233 | font-style: italic; 234 | } 235 | 236 | blockquote small { 237 | display: inline-block; 238 | margin: 0.8em 0 0.8em 1.5em; 239 | font-size: 0.9em; 240 | color: #CCC; 241 | } 242 | 243 | blockquote small:before { 244 | content: "\2014 \00A0"; 245 | } 246 | 247 | blockquote cite { 248 | font-weight: 700; 249 | } 250 | 251 | blockquote cite a { 252 | font-weight: normal; 253 | } 254 | 255 | mark { 256 | background-color: #fdffb6; 257 | } 258 | 259 | code, 260 | tt { 261 | padding: 1px 3px; 262 | border: #f8f8f8 1px solid; 263 | background: #f8f8f8; 264 | border-radius: 2px; 265 | white-space: pre-wrap; 266 | color: #e67e22; 267 | } 268 | 269 | pre { 270 | -moz-box-sizing: border-box; 271 | box-sizing: border-box; 272 | margin: 0 0 1.75em 0; 273 | width: 100%; 274 | padding: 10px; 275 | font-size: 0.9em; 276 | white-space: pre; 277 | overflow: auto; 278 | background: #f7f7f7; 279 | border-radius: 3px; 280 | } 281 | 282 | pre code, 283 | pre tt { 284 | font-size: inherit; 285 | white-space: pre-wrap; 286 | background: transparent; 287 | border: none; 288 | padding: 0; 289 | } 290 | 291 | kbd { 292 | display: inline-block; 293 | margin-bottom: 0.4em; 294 | padding: 1px 8px; 295 | border: #CCC 1px solid; 296 | color: #666; 297 | text-shadow: #FFF 0 1px 0; 298 | font-size: 0.9em; 299 | font-weight: 700; 300 | background: #F4F4F4; 301 | border-radius: 4px; 302 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 1px 0 0 #ffffff inset; 303 | } 304 | 305 | table { 306 | -moz-box-sizing: border-box; 307 | box-sizing: border-box; 308 | margin: 1.75em 0; 309 | width: 100%; 310 | max-width: 100%; 311 | background-color: transparent; 312 | } 313 | 314 | table th, 315 | table td { 316 | padding: 8px; 317 | line-height: 20px; 318 | text-align: left; 319 | vertical-align: top; 320 | border-top: #EFEFEF 1px solid; 321 | } 322 | 323 | table th { 324 | color: #000; 325 | } 326 | 327 | table caption + thead tr:first-child th, 328 | table caption + thead tr:first-child td, 329 | table colgroup + thead tr:first-child th, 330 | table colgroup + thead tr:first-child td, 331 | table thead:first-child tr:first-child th, 332 | table thead:first-child tr:first-child td { 333 | border-top: 0; 334 | } 335 | 336 | table tbody + tbody { 337 | border-top: #EFEFEF 2px solid; 338 | } 339 | 340 | table table table { 341 | background-color: #FFF; 342 | } 343 | 344 | table tbody > tr:nth-child(odd) > td, 345 | table tbody > tr:nth-child(odd) > th { 346 | background-color: #F6F6F6; 347 | } 348 | 349 | table.plain tbody > tr:nth-child(odd) > td, 350 | table.plain tbody > tr:nth-child(odd) > th { 351 | background: transparent; 352 | } 353 | 354 | iframe, 355 | .fluid-width-video-wrapper { 356 | display: block; 357 | margin: 1.75em 0; 358 | } 359 | /* When a video is inside the fitvids wrapper, drop the 360 | margin on the iframe, cause it breaks stuff. */ 361 | 362 | .fluid-width-video-wrapper iframe { 363 | margin: 0; 364 | } 365 | .warnning{ 366 | border-left: 3px solid #f39c12; 367 | padding-left: 15px; 368 | padding-top: 10px; 369 | padding-bottom: 10px; 370 | background-color: #ddd; 371 | } 372 | -------------------------------------------------------------------------------- /__sites_/src/less/modules/reset.less: -------------------------------------------------------------------------------- 1 | /**reset css**/ 2 | html,body,div,span, 3 | applet,object,iframe, 4 | h1,h2,h3,h4,h5,h6,p,blockquote,pre, 5 | a,abbr,acronym,address,big,cite,code, 6 | del,dfn,em,font,img,ins,kbd,q,s,samp, 7 | small,strike,strong,sub,sup,tt,var, 8 | dd,dl,dt,li,ol,ul, 9 | fieldset,form,label,legend, 10 | table,caption,tbody,tfoot,thead,tr,th,td { 11 | margin: 0; 12 | padding: 0; 13 | border: 0; 14 | } 15 | table { 16 | border-collapse: collapse; 17 | border-spacing: 0; 18 | } 19 | ol,ul { 20 | list-style: none; 21 | } 22 | 23 | q:before,q:after, 24 | blockquote:before,blockquote:after { 25 | content: ""; 26 | } 27 | /**base style**/ 28 | body{ 29 | font-family: '\5FAE\8F6F\96C5\9ED1','\9ED1\4F53',arial,sans-serif; 30 | font-size: @font-size-base; 31 | background-color: @bgcolor; 32 | } 33 | -------------------------------------------------------------------------------- /__sites_/src/less/plugins/angular.progress.less: -------------------------------------------------------------------------------- 1 | /* Styling for the ngProgress itself */ 2 | #ngProgress { 3 | margin: 0; 4 | padding: 0; 5 | z-index: 99998; 6 | background-color: @nephritis !important; 7 | color: @nephritis; 8 | box-shadow:none; /* Inherits the font color */ 9 | height: 2px; 10 | opacity: 0; 11 | 12 | /* Add CSS3 styles for transition smoothing */ 13 | -webkit-transition: all 0.5s ease-in-out; 14 | -moz-transition: all 0.5s ease-in-out; 15 | -o-transition: all 0.5s ease-in-out; 16 | transition: all 0.5s ease-in-out; 17 | } 18 | 19 | /* Styling for the ngProgress-container */ 20 | #ngProgress-container { 21 | position: fixed; 22 | margin: 0; 23 | padding: 0; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | z-index: 99999; 28 | } -------------------------------------------------------------------------------- /__sites_/src/less/plugins/hljs.theme.less: -------------------------------------------------------------------------------- 1 | /** view doc https://highlightjs.org **/ 2 | .hljs { 3 | display: block; 4 | overflow-x: auto; 5 | padding: 0.5em; 6 | background: #f7f7f7; 7 | line-height: 1.65; 8 | } 9 | .hljs, 10 | .hljs-subst { 11 | color: #2c3e50; 12 | } 13 | .hljs-comment { 14 | color: #999; 15 | } 16 | .hljs-keyword, 17 | .hljs-attribute, 18 | .hljs-selector-tag, 19 | .hljs-meta-keyword, 20 | .hljs-doctag, 21 | .hljs-name { 22 | font-weight: bold; 23 | } 24 | .hljs-type, 25 | .hljs-string, 26 | .hljs-number, 27 | .hljs-selector-id, 28 | .hljs-selector-class, 29 | .hljs-quote, 30 | .hljs-template-tag, 31 | .hljs-deletion { 32 | color: #e74c3c; 33 | } 34 | .hljs-title, 35 | .hljs-section { 36 | color: #e74c3c; 37 | font-weight: bold; 38 | } 39 | .hljs-regexp, 40 | .hljs-symbol, 41 | .hljs-variable, 42 | .hljs-template-variable, 43 | .hljs-link, 44 | .hljs-selector-attr, 45 | .hljs-selector-pseudo { 46 | color: #bc6060; 47 | } 48 | .hljs-literal { 49 | color: #78a960; 50 | } 51 | .hljs-built_in, 52 | .hljs-bullet, 53 | .hljs-code, 54 | .hljs-addition { 55 | color: #16a085; 56 | } 57 | .hljs-meta { 58 | color: #2980b9; 59 | } 60 | .hljs-meta-string { 61 | color: #2980b9; 62 | } 63 | .hljs-emphasis { 64 | font-style: italic; 65 | } 66 | .hljs-strong { 67 | font-weight: bold; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /__sites_/src/less/vtui.less: -------------------------------------------------------------------------------- 1 | /** vtui 2 | ** https://github.com/Vanthink-UED/VTUI @Vanthink-UED 3 | **/ 4 | 5 | @import "./config.less"; 6 | @import "modules/reset.less"; 7 | @import "modules/m-icon.less"; 8 | @import "modules/m-layout.less"; 9 | @import "modules/m-type.less"; 10 | @import "modules/m-button.less"; 11 | @import "modules/m-table.less"; 12 | @import "modules/m-pagination.less"; 13 | @import "modules/m-pages.less"; 14 | 15 | 16 | /**the plugins**/ 17 | 18 | @import "plugins/angular.progress.less"; 19 | @import "plugins/hljs.theme.less"; 20 | -------------------------------------------------------------------------------- /__sites_/src/lib/constants.js: -------------------------------------------------------------------------------- 1 | // all routers 2 | let routers = [ 3 | { 4 | name: 'Home', 5 | cn_name: '首页', 6 | }, 7 | { 8 | name: 'Get Started', 9 | cn_name: '快速开始', 10 | }, 11 | { 12 | name: 'Props', 13 | cn_name: '基本属性', 14 | }, 15 | { 16 | name: 'Events', 17 | cn_name: '响应事件', 18 | }, 19 | { 20 | name: 'Custom Component', 21 | cn_name: '自定义组件' 22 | }, 23 | { 24 | name: 'Crop Image', 25 | cn_name: '裁剪图片' 26 | }, 27 | { 28 | name: 'Resize Image', 29 | cn_name: '调整图片' 30 | }, 31 | { 32 | name: 'Multiple File', 33 | cn_name: '上传多文件' 34 | }, 35 | { 36 | name: 'Compress Image', 37 | cn_name: '压缩图片', 38 | }, 39 | { 40 | name: 'Post Data', 41 | cn_name: '向服务端发送数据', 42 | }, 43 | { 44 | name: 'Others', 45 | cn_name: '其他 & 问题反馈', 46 | } 47 | ] 48 | 49 | for (const item of routers) { 50 | item.url = item['name'].toLowerCase().replace(/\s+/g, '-'); 51 | } 52 | export { 53 | routers 54 | } 55 | -------------------------------------------------------------------------------- /__sites_/src/lib/vendor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | getLocalData(key) { 3 | let data = ''; 4 | try { 5 | data = localStorage.getItem(key); 6 | } catch (ew) { 7 | console.log('Warnning: cannot get data') 8 | } 9 | return data; 10 | }, 11 | 12 | setLocalData(key, data) { 13 | localStorage.setItem(key, data); 14 | } 15 | 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /__sites_/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: [ 5 | "webpack-dev-server/client?http://localhost:9000", 6 | 'webpack/hot/only-dev-server', 7 | "./src/index" 8 | ], 9 | output: { 10 | path: __dirname + '/build', 11 | filename: "bundle.js", 12 | publicPath: '/build/', 13 | }, 14 | devtool: 'inline-source-map', 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.js?$/, 19 | loaders: ["react-hot-loader/webpack",'babel-loader?presets[]=react,presets[]=es2015'], 20 | exclude: /node_modules/ 21 | }, 22 | { 23 | test: /\.less$/, 24 | loader: "style!css!less" 25 | } 26 | ] 27 | }, 28 | plugins: [ 29 | new webpack.NoErrorsPlugin(), 30 | new webpack.HotModuleReplacementPlugin() 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var browsers = ['Chrome']; 4 | // trvis env 5 | 6 | if (process.env.TRAVIS) { 7 | browsers = ['Chrome_travis_ci']; 8 | } 9 | 10 | 11 | module.exports = function(config) { 12 | config.set({ 13 | basePath: '', 14 | frameworks: ['jasmine'], 15 | files: [ 16 | './tests/**/*.js' 17 | ], 18 | 19 | preprocessors: { 20 | // add webpack as preprocessor 21 | 'src/**/*.js': ['webpack', 'sourcemap'], 22 | 'tests/**/*.test.js': ['webpack', 'sourcemap'] 23 | }, 24 | // webpack file 25 | webpack: { 26 | devtool: 'inline-source-map', //just do inline source maps instead of the default 27 | module: { 28 | loaders: [ 29 | { 30 | test: /\.js$/, 31 | loader: 'babel', 32 | exclude: path.resolve(__dirname, 'node_modules'), 33 | query: { 34 | presets: ['airbnb'] 35 | } 36 | }, 37 | { 38 | test: /\.json$/, 39 | loader: 'json', 40 | }, 41 | ] 42 | }, 43 | externals: { 44 | 'react/addons': true, 45 | 'react/lib/ExecutionEnvironment': true, 46 | 'react/lib/ReactContext': true 47 | } 48 | }, 49 | 50 | webpackServer: { 51 | noInfo: true //please don't spam the console when running in karma! 52 | }, 53 | 54 | plugins: [ 55 | 'karma-webpack', 56 | 'karma-jasmine', 57 | 'karma-sourcemap-loader', 58 | 'karma-chrome-launcher', 59 | ], 60 | 61 | babelPreprocessor: { 62 | options: { 63 | presets: ['airbnb'] 64 | } 65 | }, 66 | reporters: ['progress'], 67 | // custom launchers 68 | customLaunchers: { 69 | Chrome_travis_ci: { 70 | base: 'Chrome', 71 | flags: ['--no-sandbox'] 72 | } 73 | }, 74 | // port: 9002, 75 | logLevel: config.LOG_INFO, 76 | browsers: browsers, 77 | singleRun: false 78 | }) 79 | 80 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-core-image-upload", 3 | "version": "2.2.2", 4 | "description": "a component for image to upload and crop", 5 | "keywords": [ 6 | "react", 7 | "iamge", 8 | "upload", 9 | "crop", 10 | "component", 11 | "core" 12 | ], 13 | "main": "./react-core-image-upload.js", 14 | "repository": { 15 | "url": "https://github.com/Vanthink-UED/react-core-image-upload.git", 16 | "type": "git" 17 | }, 18 | "scripts": { 19 | "start": "webpack-dev-server --port 9000", 20 | "build": "webpack --config ./webpack.config.build.js --progress --colors", 21 | "test": "karma start karma.conf.js" 22 | }, 23 | "author": "JackPu ", 24 | "license": "MIT", 25 | "dependencies": { 26 | "core-image-xhr": "^1.0.3", 27 | "prop-types": "^15.5.10" 28 | }, 29 | "peerDependencies": { 30 | "react": "^15.3.2" 31 | }, 32 | "devDependencies": { 33 | "babel": "^6.5.2", 34 | "babel-core": "^6.17.0", 35 | "babel-loader": "^6.2.5", 36 | "babel-preset-airbnb": "^2.1.1", 37 | "babel-preset-es2015": "^6.16.0", 38 | "babel-preset-react": "^6.16.0", 39 | "browserify": "^13.1.0", 40 | "css-loader": "^0.26.2", 41 | "enzyme": "^2.5.1", 42 | "eslint": "^3.17.0", 43 | "eslint-config-airbnb": "^14.1.0", 44 | "eslint-plugin-import": "^2.2.0", 45 | "eslint-plugin-jsx-a11y": "^4.0.0", 46 | "eslint-plugin-react": "^6.10.0", 47 | "expect": "^1.20.2", 48 | "history": "^4.3.0", 49 | "jasmine-core": "^2.5.2", 50 | "json-loader": "^0.5.4", 51 | "karma": "^1.3.0", 52 | "karma-browserify": "^5.1.0", 53 | "karma-chrome-launcher": "^2.0.0", 54 | "karma-jasmine": "^1.0.2", 55 | "karma-mocha": "^1.2.0", 56 | "karma-sourcemap-loader": "^0.3.7", 57 | "karma-webpack": "^1.8.0", 58 | "mocha": "^3.1.2", 59 | "react-addons-test-utils": "^15.3.2", 60 | "react": "^15.3.2", 61 | "react-dom": "^15.3.2", 62 | "react-hot-loader": "^3.0.0-beta.6", 63 | "style-loader": "*", 64 | "watchify": "^3.7.0", 65 | "webpack": "^1.13.2", 66 | "webpack-dev-server": "^1.16.2" 67 | }, 68 | "engines": { 69 | "node": ">=4.0.0", 70 | "npm": "~3.3.6" 71 | } 72 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true 9 | }).listen(9000, 'localhost', function (err, result) { 10 | if (err) { 11 | return console.log(err); 12 | } 13 | 14 | console.log('Listening at http://localhost:9000/'); 15 | }); -------------------------------------------------------------------------------- /shots/react-core-image-upload.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanthink-UED/react-core-image-upload/8423d7e609009c93864fc4838898f73371639e1c/shots/react-core-image-upload.jpg -------------------------------------------------------------------------------- /shots/vuedba0ed377b88fc84d51026310efcb255b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vanthink-UED/react-core-image-upload/8423d7e609009c93864fc4838898f73371639e1c/shots/vuedba0ed377b88fc84d51026310efcb255b.png -------------------------------------------------------------------------------- /src/components/crop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ResizeBar from './resize-bar'; 4 | import helper from '../lib/helper'; 5 | import resize from '../lib/resize'; 6 | import drag from '../lib/drag'; 7 | import GIF_LOADING_SRC from '../lib/loading-gif'; 8 | 9 | const CROPBOX_PERCENT = 75; 10 | const isMobile = helper.isMobile; 11 | const areaWidth = window.innerWidth - 60; 12 | const areaHeight = window.innerHeight - 110; 13 | 14 | export default class Crop extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | left: 0, 19 | top: 0, 20 | width: 0, 21 | height: 0, 22 | cropCSS: { 23 | left: 0, 24 | top: 0, 25 | width: 0, 26 | height: 0, 27 | }, 28 | src: GIF_LOADING_SRC, 29 | }; 30 | this.drag = this.drag.bind(this); 31 | this.resize = this.resize.bind(this); 32 | this.resizeImage = this.resizeImage.bind(this); 33 | } 34 | 35 | render() { 36 | return ( 37 |
    38 |
    39 |
    43 |
    53 | 57 |
    58 | {!this.props.isResize ? 59 |
    60 |
    68 |
    69 |
    77 |
    78 |
    86 |
    87 |
    95 |
    96 |
    97 | : null} 98 | { !this.props.isResize ? 99 |
    108 |
    109 |
    110 | 114 | 115 |
    116 | : ''} 117 |
    118 | 119 |
    120 |
    121 | ); 122 | } 123 | 124 | setImage(src, w, h) { 125 | this.setState({ 126 | src, 127 | }) 128 | if (this.props.ratio.indexOf(':') > 0) { 129 | this.ratioW = this.props.ratio.split(':')[0]; 130 | this.ratioH = this.props.ratio.split(':')[1]; 131 | this.ratioVal = this.ratioW / this.ratioH; 132 | } else { 133 | this.ratioW = 1; 134 | this.ratioH = 1; 135 | this.ratioVal = this.ratio; 136 | } 137 | this.natrualWidth = w; 138 | this.natrualHeight = h; 139 | this.setLayout(w, h); 140 | const resizeBar = this.refs.resizeBar; 141 | if (this.props.isResize) { 142 | resizeBar.setProgress(100); 143 | } else { 144 | resizeBar.setProgress(0); 145 | } 146 | return this.imgChangeRatio; 147 | } 148 | 149 | resizeImage(progress) { 150 | let w, 151 | h; 152 | if (this.props.isResize) { 153 | w = this.natrualWidth * this.imgChangeRatio * progress; 154 | h = this.natrualHeight * this.imgChangeRatio * progress; 155 | } else { 156 | w = this.initWidth + (progress * (this.natrualWidth - this.initWidth)); 157 | h = this.initHeight + (progress * (this.natrualHeight - this.initHeight)); 158 | } 159 | if (w <= this.props.minWidth || h < this.props.minHeight) { 160 | return; 161 | } 162 | this.setState({ 163 | left: this.state.left + ((this.state.width - w) / 2), 164 | top: this.state.top + ((this.state.height - h) / 2), 165 | width: w, 166 | height: h, 167 | }); 168 | this.imgChangeRatio = this.width / this.natrualWidth; 169 | } 170 | 171 | setLayout(w, h) { 172 | let H = areaHeight, 173 | W = areaWidth, 174 | width = w, 175 | height = h, 176 | marginLeft = 0, 177 | marginTop = 0; 178 | // caculate the image ratio 179 | let R = width / height; 180 | let Rs = W / H; 181 | if (R > Rs) { 182 | height = H; 183 | width = H * R; 184 | marginLeft = (W - (H * R)) / 2; 185 | } else { 186 | width = H * R, 187 | height = H; 188 | marginLeft = (W - (H * R)) / 2; 189 | } 190 | this._setStyle(width, height, marginLeft, marginTop, R, true); 191 | } 192 | 193 | _setStyle(w, h, ml, mt, r, isInit) { 194 | const $container = this.__find('.g-crop-image-principal'); 195 | if(!isInit) { 196 | this.marginLeft = this.marginLeft + (this.width - w) / 2; 197 | this.marginTop = this.marginTop + (this.height - h) / 2; 198 | } 199 | $container.style.cssText = 'width:' + w + 'px;height:' + h + 'px;margin-left:' 200 | + ml + 'px;' + 'margin-top:' + mt + 'px'; 201 | const cropCSS = this.setCropBox(w, h); 202 | let width; 203 | let height; 204 | if (this.props.isResize) { 205 | this.setState({ 206 | width: w, 207 | height: h, 208 | }); 209 | } else { 210 | if (r >= 1) { 211 | height = cropCSS.height; 212 | width = height * r; 213 | } else { 214 | width = cropCSS.width; 215 | height = width / r; 216 | } 217 | this.initWidth = width; 218 | this.initHeight = height; 219 | const left = (w - width) / 2; 220 | const top = (h - height) / 2; 221 | this.setState({ 222 | width, 223 | height, 224 | left, 225 | top, 226 | }); 227 | } 228 | this.imgChangeRatio = width / this.natrualWidth; 229 | } 230 | 231 | setCropBox(w, h, r) { 232 | if (this.props.isResize) { 233 | return; 234 | } 235 | let $selectCropBox = this.refs['cropbox']; 236 | let $wrap = this.refs['container']; 237 | let imageWidth = w, 238 | imageHeight = h, 239 | ratioW = this.ratioW, 240 | ratioH = this.ratioH; 241 | let cropWidth = imageWidth; 242 | if (areaWidth < w) { 243 | cropWidth = areaWidth; 244 | } 245 | const baseCropWidth = (cropWidth / 100) * CROPBOX_PERCENT; 246 | const CSSObj = { 247 | width: baseCropWidth, 248 | height: (baseCropWidth / ratioW) * ratioH, 249 | } 250 | CSSObj.left = (imageWidth - baseCropWidth) / 2; 251 | CSSObj.top = (imageHeight - CSSObj.height) / 2; 252 | $selectCropBox.style.cssText = helper.setCssText(CSSObj); 253 | if (CSSObj.height > imageHeight) { 254 | const baseCropHeight = (imageHeight / 100) * CROPBOX_PERCENT 255 | CSSObj.height = baseCropHeight; 256 | CSSObj.width = (CSSObj.height * ratioW) / ratioH; 257 | CSSObj.left = (imageWidth - CSSObj.width) / 2; 258 | CSSObj.top = (imageHeight - CSSObj.height) / 2; 259 | $selectCropBox.style.cssText = helper.setCssText(CSSObj); 260 | } 261 | this.setState({ 262 | cropCSS: CSSObj, 263 | }); 264 | return CSSObj; 265 | } 266 | 267 | getCropData() { 268 | // keep compatible with old api 269 | if (this.props.isResize) { 270 | return { 271 | imgChangeRatio: this.imgChangeRatio, 272 | toCropImgX: 0, 273 | toCropImgY: 0, 274 | toCropImgW: this.natrualWidth, 275 | toCropImgH: this.natrualHeight, 276 | }; 277 | } 278 | return { 279 | toCropImgX: (this.state.cropCSS.left - this.state.left) / this.imgChangeRatio, 280 | toCropImgY: (this.state.cropCSS.top - this.state.top) / this.imgChangeRatio, 281 | toCropImgW: this.state.cropCSS.width / this.imgChangeRatio, 282 | toCropImgH: this.state.cropCSS.height / this.imgChangeRatio, 283 | }; 284 | } 285 | 286 | getCropImage() { 287 | return this.refs['crop-image']; 288 | } 289 | 290 | __find(str) { 291 | let dq = this.refs['container']; 292 | return dq.querySelector(str); 293 | } 294 | 295 | resize(e) { 296 | e.stopPropagation(); 297 | if (!this.props.ratio.indexOf(':')) { 298 | return; 299 | } 300 | let $el = e.target.parentElement; 301 | let $container = this.__find('.g-crop-image-principal'); 302 | if (this._$container) { 303 | this._$container = container; 304 | } 305 | const self = this; 306 | const coor = { 307 | x: helper.isMobile ? e.touches[0].clientX : e.clientX, 308 | y: helper.isMobile ? e.touches[0].clientY : e.clientY, 309 | w: $el.offsetWidth, 310 | h: $el.offsetHeight, 311 | }; 312 | this.el = $el; 313 | this.container = $container; 314 | const move = function (ev) { 315 | const newCropStyle = resize(ev, self.el, $container, coor, self.ratioVal); 316 | if (newCropStyle) { 317 | self.setState({ 318 | cropCSS:{ 319 | width: newCropStyle.width, 320 | height: newCropStyle.height, 321 | } 322 | }); 323 | } 324 | }; 325 | const end = function (ev) { 326 | this.el = null; 327 | if (helper.isMobile) { 328 | document.removeEventListener('touchmove', move, false); 329 | document.removeEventListener('touchend', end, false); 330 | } 331 | document.removeEventListener('mousemove', move, false); 332 | document.removeEventListener('mouseup', end, false); 333 | }; 334 | if (helper.isMobile) { 335 | document.addEventListener('touchmove', move, false); 336 | document.addEventListener('touchend', end, false); 337 | } 338 | document.addEventListener('mousemove', move, false); 339 | document.addEventListener('mouseup', end, false); 340 | } 341 | 342 | drag(e) { 343 | e.preventDefault(); 344 | const $el = this.__find('.image-wrap'); 345 | this.el = $el; 346 | const $cropBox = this.__find('.crop-box'); 347 | const $container = e.currentTarget; 348 | const self = this; 349 | const isMobile = helper.isMobile; 350 | const coor = { 351 | x: (isMobile ? e.touches[0]['clientX'] : e.clientX) - $el.offsetLeft, 352 | y: (isMobile ? e.touches[0]['clientY'] : e.clientY) - $el.offsetTop, 353 | maxLeft: $cropBox.offsetLeft, 354 | maxTop: $cropBox.offsetTop, 355 | minLeft: ($cropBox.offsetWidth + $cropBox.offsetLeft) - $el.offsetWidth, 356 | minTop: ($cropBox.offsetHeight + $cropBox.offsetTop) - $el.offsetHeight, 357 | }; 358 | const move = function (ev) { 359 | const newCropStyle = drag(ev, self.el, coor); 360 | if (newCropStyle) { 361 | self.setState({ 362 | left: newCropStyle.left, 363 | top: newCropStyle.top, 364 | }) 365 | } 366 | }; 367 | const stopMove = function (ev) { 368 | self.el = null; 369 | if (isMobile) { 370 | document.removeEventListener('touchmove', move, false); 371 | document.removeEventListener('touchend', stopMove, false); 372 | return; 373 | } 374 | document.removeEventListener('mousemove', move, false); 375 | document.removeEventListener('mouseup', stopMove, false); 376 | }; 377 | if (isMobile) { 378 | document.addEventListener('touchmove', move, false); 379 | document.addEventListener('touchend', stopMove, false); 380 | return; 381 | } 382 | document.addEventListener('mousemove', move, false); 383 | document.addEventListener('mouseup', stopMove, false); 384 | } 385 | 386 | } 387 | 388 | Crop.propTypes = { 389 | isResize: PropTypes.bool, 390 | minWidth: PropTypes.number, 391 | minHeight: PropTypes.number, 392 | ratio: PropTypes.string, 393 | }; 394 | 395 | Crop.defaultProps = { 396 | isResize: false, 397 | minWidth: 50, 398 | minHeight: 50, 399 | ratio: '1:1', 400 | }; 401 | -------------------------------------------------------------------------------- /src/components/resize-bar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import helper from '../lib/helper'; 4 | import drag from '../lib/drag'; 5 | 6 | export default class ResizeBar extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | left: 0, 11 | }; 12 | this.drag = this.drag.bind(this); 13 | } 14 | 15 | render() { 16 | return ( 17 |
    18 |
    22 |
    23 | 30 |
    31 | ); 32 | } 33 | 34 | setProgress(num) { 35 | this.setState({ 36 | left: num, 37 | }) 38 | } 39 | 40 | drag(e) { 41 | const $el = e.target; 42 | this.el = $el; 43 | const $container = this.refs.container.parentElement; 44 | const self = this; 45 | const isMobile = helper.isMobile; 46 | const width = 200; 47 | const coor = { 48 | x: (isMobile ? e.touches[0]['clientX'] : e.clientX) - $el.offsetLeft, 49 | y: (isMobile ? e.touches[0]['clientY'] : e.clientY) - $el.offsetTop, 50 | maxLeft: width, 51 | maxTop: parseInt($container.style.height) - parseInt($el.style.height), 52 | minLeft: 0, 53 | minTop: 0, 54 | }; 55 | const move = function (ev) { 56 | const newCoor = drag(ev, self.el, coor); 57 | if (newCoor) { 58 | if((newCoor.left / width) < self.minProgress) { 59 | return; 60 | } 61 | self.progress = newCoor.left / width; 62 | self.setState({ 63 | left: newCoor.left / width * 100, 64 | }); 65 | self.props.resize(self.progress); 66 | } 67 | }; 68 | const stopMove = function (ev) { 69 | self.el = null; 70 | if (isMobile) { 71 | document.removeEventListener('touchmove', move, false); 72 | document.removeEventListener('touchend', stopMove, false); 73 | return; 74 | } 75 | document.removeEventListener('mousemove', move, false); 76 | document.removeEventListener('mouseup', stopMove, false); 77 | }; 78 | if (isMobile) { 79 | document.addEventListener('touchmove', move, false); 80 | document.addEventListener('touchend', stopMove, false); 81 | return; 82 | } 83 | document.addEventListener('mousemove', move, false); 84 | document.addEventListener('mouseup', stopMove, false); 85 | } 86 | }; 87 | ResizeBar.PropTypes = { 88 | minProgress: PropTypes.minProgress, 89 | resize: PropTypes.function, 90 | }; 91 | ResizeBar.defaultProps = { 92 | minProgress: 0, 93 | }; 94 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReactCoreImageUpload from './react-core-image-upload'; 2 | 3 | module.exports = ReactCoreImageUpload; 4 | -------------------------------------------------------------------------------- /src/lib/canvas-helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * compress image 3 | * reference https://github.com/brunobar79/J-I-C 4 | **/ 5 | 6 | export default { 7 | _getImageType(str) { 8 | let mimeType = 'image/jpeg'; 9 | const outputType = str.match(/(image\/[\w]+)\.*/)[0]; 10 | if (typeof outputType !== 'undefined'){ 11 | mimeType = outputType; 12 | } 13 | return mimeType; 14 | }, 15 | 16 | compress (src, quality, callback) { 17 | const reader = new FileReader(); 18 | const self = this; 19 | reader.onload = function(event) { 20 | let image = new Image(); 21 | image.src = event.target.result; 22 | image.onload = function() { 23 | const mimeType = self._getImageType(src.type); 24 | const cvs = self._getCanvas(image.naturalWidth, image.naturalHeight); 25 | const ctx = cvs.getContext("2d").drawImage(image, 0, 0); 26 | const newImageData = cvs.toDataURL(mimeType, quality/100); 27 | callback(newImageData); 28 | } 29 | }; 30 | reader.readAsDataURL(src); 31 | }, 32 | /** 33 | * crop image via canvas and generate data 34 | **/ 35 | crop(image, options, callback) { 36 | const checkNumber = function(num) { 37 | return (typeof num === 'number'); 38 | }; 39 | // check crop options 40 | if(checkNumber(options.toCropImgX) && checkNumber(options.toCropImgY) && options.toCropImgW > 0 && options.toCropImgH > 0) { 41 | let w = options.toCropImgW; 42 | let h = options.toCropImgH; 43 | if(options.maxWidth && options.maxWidth < w) { 44 | w = options.maxWidth; 45 | h = options.toCropImgH * w / options.toCropImgW; 46 | } 47 | if (options.maxHeight && options.maxHeight < h) { 48 | h = options.maxHeight 49 | } 50 | const cvs = this._getCanvas(w, h); 51 | const ctx = cvs.getContext('2d').drawImage(image, options.toCropImgX, options.toCropImgY, options.toCropImgW, options.toCropImgH, 0 , 0, w, h); 52 | const mimeType = this._getImageType(image.src); 53 | const data = cvs.toDataURL(mimeType, options.compress/100); 54 | callback(data); 55 | } 56 | }, 57 | 58 | resize(image, options, callback) { 59 | const checkNumber = function(num) { 60 | return (typeof num === 'number'); 61 | }; 62 | if(checkNumber(options.toCropImgX) && checkNumber(options.toCropImgY) && options.toCropImgW > 0 && options.toCropImgH > 0) { 63 | let w = options.toCropImgW * options.imgChangeRatio; 64 | let h = options.toCropImgH * options.imgChangeRatio; 65 | const cvs = this._getCanvas(w, h); 66 | const ctx = cvs.getContext('2d').drawImage(image, 0, 0, options.toCropImgW, options.toCropImgH, 0 , 0, w , h); 67 | const mimeType = this._getImageType(image.src); 68 | const data = cvs.toDataURL(mimeType, options.compress/100); 69 | callback(data); 70 | } 71 | }, 72 | 73 | _loadImage(data, callback) { 74 | const image = new Image(); 75 | image.src = data; 76 | image.onload = function () { 77 | callback(image); 78 | } 79 | image.onerror = function() { 80 | console.log('Error: image error!'); 81 | } 82 | }, 83 | 84 | _getCanvas(width, height) { 85 | const canvas = document.createElement('canvas'); 86 | canvas.width = width; 87 | canvas.height = height; 88 | return canvas; 89 | }, 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /src/lib/drag.js: -------------------------------------------------------------------------------- 1 | import helper from './helper'; 2 | 3 | const isMobile = helper.isMobile; 4 | export default function drag(e, el, coor) { 5 | if (!el) { 6 | return; 7 | } 8 | const currentX = isMobile ? e.changedTouches[0]['clientX'] : e.clientX; 9 | const currentY = isMobile ? e.changedTouches[0]['clientY'] : e.clientY; 10 | 11 | let left = currentX - coor.x; 12 | let top = currentY - coor.y; 13 | if (left <= coor.minLeft) { 14 | left = coor.minLeft; 15 | } 16 | if (left >= coor.maxLeft) { 17 | left = coor.maxLeft; 18 | } 19 | if (top <= coor.minTop) { 20 | top = coor.minTop; 21 | } 22 | if (top >= coor.maxTop) { 23 | top = coor.maxTop; 24 | } 25 | return { 26 | left, 27 | top 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/lib/error-code.js: -------------------------------------------------------------------------------- 1 | const SERVER_ERROR = 2500; 2 | const FILE_SIZE_ERROR = 2501; 3 | const FILE_FORMAT = 2502; 4 | 5 | export default { 6 | SERVER_ERROR, 7 | FILE_SIZE_ERROR, 8 | FILE_FORMAT, 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/helper.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), 3 | 4 | setCssText: function(obj) { 5 | var cssArr = []; 6 | for(var key in obj) { 7 | var val = obj[key]; 8 | if (typeof val === 'number') { 9 | val = '' + val + 'px'; 10 | } 11 | cssArr.push(key + ': ' + val + ';'); 12 | } 13 | return cssArr.join(''); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib/loading-gif.js: -------------------------------------------------------------------------------- 1 | const GIF_LOADING_SRC = 'data:image/gif;base64,R0lGODlhGAAYAPQAAP///3FxcePj4/v7++3t7dLS0vHx8b+/v+Dg4MfHx+jo6M7Oztvb2/f397Kysru7u9fX16qqqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJBwAAACwAAAAAGAAYAAAFriAgjiQAQWVaDgr5POSgkoTDjFE0NoQ8iw8HQZQTDQjDn4jhSABhAAOhoTqSDg7qSUQwxEaEwwFhXHhHgzOA1xshxAnfTzotGRaHglJqkJcaVEqCgyoCBQkJBQKDDXQGDYaIioyOgYSXA36XIgYMBWRzXZoKBQUMmil0lgalLSIClgBpO0g+s26nUWddXyoEDIsACq5SsTMMDIECwUdJPw0Mzsu0qHYkw72bBmozIQAh+QQJBwAAACwAAAAAGAAYAAAFsCAgjiTAMGVaDgR5HKQwqKNxIKPjjFCk0KNXC6ATKSI7oAhxWIhezwhENTCQEoeGCdWIPEgzESGxEIgGBWstEW4QCGGAIJEoxGmGt5ZkgCRQQHkGd2CESoeIIwoMBQUMP4cNeQQGDYuNj4iSb5WJnmeGng0CDGaBlIQEJziHk3sABidDAHBgagButSKvAAoyuHuUYHgCkAZqebw0AgLBQyyzNKO3byNuoSS8x8OfwIchACH5BAkHAAAALAAAAAAYABgAAAW4ICCOJIAgZVoOBJkkpDKoo5EI43GMjNPSokXCINKJCI4HcCRIQEQvqIOhGhBHhUTDhGo4diOZyFAoKEQDxra2mAEgjghOpCgz3LTBIxJ5kgwMBShACREHZ1V4Kg1rS44pBAgMDAg/Sw0GBAQGDZGTlY+YmpyPpSQDiqYiDQoCliqZBqkGAgKIS5kEjQ21VwCyp76dBHiNvz+MR74AqSOdVwbQuo+abppo10ssjdkAnc0rf8vgl8YqIQAh+QQJBwAAACwAAAAAGAAYAAAFrCAgjiQgCGVaDgZZFCQxqKNRKGOSjMjR0qLXTyciHA7AkaLACMIAiwOC1iAxCrMToHHYjWQiA4NBEA0Q1RpWxHg4cMXxNDk4OBxNUkPAQAEXDgllKgMzQA1pSYopBgonCj9JEA8REQ8QjY+RQJOVl4ugoYssBJuMpYYjDQSliwasiQOwNakALKqsqbWvIohFm7V6rQAGP6+JQLlFg7KDQLKJrLjBKbvAor3IKiEAIfkECQcAAAAsAAAAABgAGAAABbUgII4koChlmhokw5DEoI4NQ4xFMQoJO4uuhignMiQWvxGBIQC+AJBEUyUcIRiyE6CR0CllW4HABxBURTUw4nC4FcWo5CDBRpQaCoF7VjgsyCUDYDMNZ0mHdwYEBAaGMwwHDg4HDA2KjI4qkJKUiJ6faJkiA4qAKQkRB3E0i6YpAw8RERAjA4tnBoMApCMQDhFTuySKoSKMJAq6rD4GzASiJYtgi6PUcs9Kew0xh7rNJMqIhYchACH5BAkHAAAALAAAAAAYABgAAAW0ICCOJEAQZZo2JIKQxqCOjWCMDDMqxT2LAgELkBMZCoXfyCBQiFwiRsGpku0EshNgUNAtrYPT0GQVNRBWwSKBMp98P24iISgNDAS4ipGA6JUpA2WAhDR4eWM/CAkHBwkIDYcGiTOLjY+FmZkNlCN3eUoLDmwlDW+AAwcODl5bYl8wCVYMDw5UWzBtnAANEQ8kBIM0oAAGPgcREIQnVloAChEOqARjzgAQEbczg8YkWJq8nSUhACH5BAkHAAAALAAAAAAYABgAAAWtICCOJGAYZZoOpKKQqDoORDMKwkgwtiwSBBYAJ2owGL5RgxBziQQMgkwoMkhNqAEDARPSaiMDFdDIiRSFQowMXE8Z6RdpYHWnEAWGPVkajPmARVZMPUkCBQkJBQINgwaFPoeJi4GVlQ2Qc3VJBQcLV0ptfAMJBwdcIl+FYjALQgimoGNWIhAQZA4HXSpLMQ8PIgkOSHxAQhERPw7ASTSFyCMMDqBTJL8tf3y2fCEAIfkECQcAAAAsAAAAABgAGAAABa8gII4k0DRlmg6kYZCoOg5EDBDEaAi2jLO3nEkgkMEIL4BLpBAkVy3hCTAQKGAznM0AFNFGBAbj2cA9jQixcGZAGgECBu/9HnTp+FGjjezJFAwFBQwKe2Z+KoCChHmNjVMqA21nKQwJEJRlbnUFCQlFXlpeCWcGBUACCwlrdw8RKGImBwktdyMQEQciB7oACwcIeA4RVwAODiIGvHQKERAjxyMIB5QlVSTLYLZ0sW8hACH5BAkHAAAALAAAAAAYABgAAAW0ICCOJNA0ZZoOpGGQrDoOBCoSxNgQsQzgMZyIlvOJdi+AS2SoyXrK4umWPM5wNiV0UDUIBNkdoepTfMkA7thIECiyRtUAGq8fm2O4jIBgMBA1eAZ6Knx+gHaJR4QwdCMKBxEJRggFDGgQEREPjjAMBQUKIwIRDhBDC2QNDDEKoEkDoiMHDigICGkJBS2dDA6TAAnAEAkCdQ8ORQcHTAkLcQQODLPMIgIJaCWxJMIkPIoAt3EhACH5BAkHAAAALAAAAAAYABgAAAWtICCOJNA0ZZoOpGGQrDoOBCoSxNgQsQzgMZyIlvOJdi+AS2SoyXrK4umWHM5wNiV0UN3xdLiqr+mENcWpM9TIbrsBkEck8oC0DQqBQGGIz+t3eXtob0ZTPgNrIwQJDgtGAgwCWSIMDg4HiiUIDAxFAAoODwxDBWINCEGdSTQkCQcoegADBaQ6MggHjwAFBZUFCm0HB0kJCUy9bAYHCCPGIwqmRq0jySMGmj6yRiEAIfkECQcAAAAsAAAAABgAGAAABbIgII4k0DRlmg6kYZCsOg4EKhLE2BCxDOAxnIiW84l2L4BLZKipBopW8XRLDkeCiAMyMvQAA+uON4JEIo+vqukkKQ6RhLHplVGN+LyKcXA4Dgx5DWwGDXx+gIKENnqNdzIDaiMECwcFRgQCCowiCAcHCZIlCgICVgSfCEMMnA0CXaU2YSQFoQAKUQMMqjoyAglcAAyBAAIMRUYLCUkFlybDeAYJryLNk6xGNCTQXY0juHghACH5BAkHAAAALAAAAAAYABgAAAWzICCOJNA0ZVoOAmkY5KCSSgSNBDE2hDyLjohClBMNij8RJHIQvZwEVOpIekRQJyJs5AMoHA+GMbE1lnm9EcPhOHRnhpwUl3AsknHDm5RN+v8qCAkHBwkIfw1xBAYNgoSGiIqMgJQifZUjBhAJYj95ewIJCQV7KYpzBAkLLQADCHOtOpY5PgNlAAykAEUsQ1wzCgWdCIdeArczBQVbDJ0NAqyeBb64nQAGArBTt8R8mLuyPyEAOwAAAAAAAAAAAA=='; 2 | 3 | export default GIF_LOADING_SRC; 4 | -------------------------------------------------------------------------------- /src/lib/resize.js: -------------------------------------------------------------------------------- 1 | /** Reszie 2 | * @el dom 3 | * @container dom 4 | * @ratio string '1:1' like this 5 | * e events 6 | **/ 7 | import helper from './helper'; 8 | 9 | const isMobile = helper.isMobile; 10 | const W = document.body.offsetWidth; 11 | export default function resize(e, el, container, coor, ratio) { 12 | if (!el) { 13 | return ; 14 | } 15 | const H = document.body.offsetHeight; 16 | const ratioRemainder = 1 / ratio; 17 | const dotBoxW = parseInt(window.getComputedStyle(container).width); 18 | const dotBoxH = parseInt(window.getComputedStyle(container).height); 19 | const $topH = document.querySelector('.info-aside'); 20 | const halfX = (W - dotBoxW) / 2; 21 | const topH = parseInt(window.getComputedStyle($topH).height); 22 | const halfY = (H - dotBoxH - topH)/2; 23 | const resetX = isMobile ? e.changedTouches[0]['clientX'] : e.clientX; 24 | const resetY = isMobile ? e.changedTouches[0]['clientY'] : e.clientY; 25 | const elOffsetWidth = parseInt(el.offsetWidth); 26 | const elOffsetHeight = parseInt(el.offsetHeight); 27 | const CSSObj = {}; 28 | if (ratio >= 1 && resetX <= halfX + dotBoxW) { 29 | if (elOffsetWidth >= dotBoxW) { 30 | CSSObj.width = dotBoxW; 31 | } 32 | CSSObj.width = (coor.w + resetX - coor.x); 33 | CSSObj.height = elOffsetWidth * ratioRemainder; 34 | if (dotBoxW > dotBoxH) { 35 | if (elOffsetWidth > dotBoxH) { 36 | CSSObj.height = dotBoxH; 37 | CSSObj.width = dotBoxH * ratio; 38 | } 39 | } else if (dotBoxW < dotBoxH) { 40 | if (elOffsetWidth > dotBoxW) { 41 | CSSObj.width = dotBoxW; 42 | CSSObj.height = dotBoxW * ratioRemainder; 43 | } 44 | } else if (elOffsetWidth >= dotBoxW) { 45 | CSSObj.width = dotBoxW ; 46 | CSSObj.height = dotBoxW * ratioRemainder; 47 | } 48 | } else if (ratio < 1 && resetY < (halfY + dotBoxH + topH)) { 49 | CSSObj.height = (coor.h + resetY - coor.y); 50 | CSSObj.width = parseInt(el.style.height) * ratio; 51 | // 限制拖拉的范围在图片内 52 | if (dotBoxW > dotBoxH) { 53 | if (elOffsetHeight > dotBoxH) { 54 | CSSObj.height = dotBoxH; 55 | CSSObj.width = dotBoxH * ratio; 56 | } 57 | } else if (dotBoxW < dotBoxH) { 58 | if (elOffsetWidth > dotBoxW) { 59 | CSSObj.width = dotBoxW; 60 | CSSObj.height = dotBoxW * ratioRemainder; 61 | } 62 | } else if (elOffsetWidth > dotBoxW) { 63 | CSSObj.width = dotBoxW; 64 | CSSObj.height = dotBoxW * ratioRemainder; 65 | } 66 | } else if(ratio == 'auto' && resetY <= (halfY + dotBoxH + topH) && resetX <= halfY + dotBoxW) { 67 | CSSObj.height = (coor.h + resetY - coor.y); 68 | CSSObj.width = (coor.w + resetX - coor.x); 69 | } else if (resetX <= halfX + dotBoxW) { 70 | CSSObj.width = (coor.w + resetX - coor.x); 71 | CSSObj.height = el.style.width; 72 | // limit the crop box area 73 | if (dotBoxW > dotBoxH) { 74 | if (elOffsetHeight > dotBoxH) { 75 | CSSObj.height = dotBoxH; 76 | CSSObj.width = dotBoxH; 77 | } 78 | } else if (dotBoxW < dotBoxH) { 79 | if (elOffsetWidth > dotBoxW) { 80 | CSSObj.width = dotBoxW; 81 | CSSObj.height = dotBoxW; 82 | } 83 | } else if (elOffsetWidth > dotBoxW) { 84 | CSSObj.width = el.style.height = dotBoxW; 85 | } 86 | } 87 | return CSSObj; 88 | }; 89 | -------------------------------------------------------------------------------- /src/lib/xhr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * simple ajax handler 3 | **/ 4 | 5 | //ADD sendAsBinary compatibilty to older browsers 6 | if (XMLHttpRequest.prototype.sendAsBinary === undefined) { 7 | XMLHttpRequest.prototype.sendAsBinary = function(string) { 8 | var bytes = Array.prototype.map.call(string, function(c) { 9 | return c.charCodeAt(0) & 0xff; 10 | }); 11 | this.send(new Uint8Array(bytes).buffer); 12 | }; 13 | } 14 | 15 | module.exports = function (method, url, headers, data, callback, err, isBinary) { 16 | 17 | var r = new XMLHttpRequest(); 18 | var error = err || function () { 19 | console.error('AJAX ERROR!'); 20 | }; 21 | const boundary = 'webcodeimageupload'; 22 | // Binary? 23 | let binary = false; 24 | if (method === 'blob') { 25 | binary = method; 26 | method = 'GET'; 27 | } 28 | method = method.toUpperCase(); 29 | // Xhr.responseType 'json' is not supported in any of the vendors yet. 30 | r.onload = function () { 31 | let json = r.response; 32 | try { 33 | json = JSON.parse(r.responseText); 34 | } catch (_e) { 35 | if (r.status === 401) { 36 | json = error('access_denied', r.statusText); 37 | } 38 | } 39 | let headers = headersToJSON(r.getAllResponseHeaders()); 40 | headers.statusCode = r.status; 41 | callback(json || (method === 'GET' ? error('empty_response', 'Could not get resource') : {}), headers); 42 | }; 43 | r.onerror = function () { 44 | let json = r.responseText; 45 | try { 46 | json = JSON.parse(r.responseText); 47 | } catch (_e) { 48 | console.error(_e); 49 | } 50 | callback(json || error('access_denied', 'Could not get resource')); 51 | }; 52 | let x; 53 | // Should we add the query to the URL? 54 | if (method === 'GET' || method === 'DELETE') { 55 | data = null; 56 | } else if (isBinary) { 57 | const keyData = data; 58 | const code = data.base64Code.replace('data:' + data.type + ';base64,', ''); 59 | data = ['--' + boundary, 'Content-Disposition: form-data; name="' + data.filed + '"; filename="' + data.filename + '"', 'Content-Type: ' + data.type, '', window.atob(code), ''].join('\r\n'); 60 | const keyArr = Object.keys(keyData); 61 | if (keyArr.length > 4) { 62 | for (const k of keyArr) { 63 | if (['filed', 'filename', 'type', 'base64Code'].indexOf(k) == -1) { 64 | data += ['--' + boundary, 'Content-Disposition: form-data; name="' + k + '";', '', ''].join('\r\n'); 65 | data += [typeof keyData[k] === 'object' ? JSON.stringify(keyData[k]) : encodeURI(keyData[k]), ''].join('\r\n'); 66 | } 67 | } 68 | } 69 | data += '--' + boundary + '--'; 70 | } 71 | // Open the path, async 72 | r.open(method, url, true); 73 | if (binary) { 74 | if ('responseType' in r) { 75 | r.responseType = binary; 76 | } 77 | else { 78 | r.overrideMimeType('text/plain; charset=x-user-defined'); 79 | } 80 | } 81 | // Set any bespoke headers 82 | if (headers) { 83 | for (x in headers) { 84 | r.setRequestHeader(x, headers[x]); 85 | } 86 | } 87 | if (isBinary) { 88 | r.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); 89 | return r.sendAsBinary(data); 90 | } 91 | r.withCredentials = true; 92 | r.send(data); 93 | return r; 94 | // Headers are returned as a string 95 | function headersToJSON(s) { 96 | var o = {}; 97 | var reg = /([a-z\-]+):\s?(.*);?/gi; 98 | let m; 99 | while (m = reg.exec(s)) { 100 | o[m[1]] = m[2]; 101 | } 102 | return o; 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /src/propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export default { 4 | url: PropTypes.string, 5 | text: PropTypes.string, 6 | inputAccept: PropTypes.string, 7 | inputOfFile: PropTypes.string, 8 | cropBtn: PropTypes.object, 9 | cropRatio: PropTypes.string, 10 | resizeBtn: PropTypes.object, 11 | maxFileSize: PropTypes.number, 12 | maxWidth: PropTypes.number, 13 | maxHeight: PropTypes.number, 14 | minWidth: PropTypes.number, 15 | minHeight: PropTypes.number, 16 | data: PropTypes.object, 17 | header: PropTypes.object, 18 | multipleSize: PropTypes.number, 19 | compress: PropTypes.number, 20 | }; 21 | -------------------------------------------------------------------------------- /src/props.js: -------------------------------------------------------------------------------- 1 | // default props 2 | export default { 3 | url: '', 4 | text: 'upload', 5 | inputOfFile: 'files', 6 | crop: false, 7 | cropBtn: { 8 | ok: 'Save', 9 | cancel: 'Cancel', 10 | }, 11 | extensions: ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'], 12 | cropRatio: '1:1', 13 | resize: false, 14 | resizeBtn: { 15 | ok: 'Save', 16 | cancel: 'Cancel', 17 | }, 18 | inputAccept: 'image/jpg,image/jpeg,image/png', 19 | data: {}, 20 | header: {}, 21 | isXhr: true, 22 | multiple: false, 23 | compress: 0, 24 | imageUploaded: function(res) { 25 | 26 | }, 27 | imageUploading: function(res) { 28 | console.info('uploading'); 29 | }, 30 | imageChanged: function() { 31 | 32 | }, 33 | errorHandle: function(err) { 34 | console.error(err); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/react-core-image-upload.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import xhr from 'core-image-xhr'; 3 | import defaultProps from './props'; 4 | import propTypes from './propTypes'; 5 | import Crop from './components/crop'; 6 | import errorCode from './lib/error-code'; 7 | import canvasHelper from './lib/canvas-helper'; 8 | require('style!css!./style.css'); 9 | 10 | let overflowVal = ''; 11 | class ReactCoreImageUpload extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | formID: 'g-core-upload-input-' + Math.floor(Math.random() * 10000), 16 | uploading: false, 17 | hasImage: false, 18 | image: { 19 | src: 'http://img1.vued.vanthink.cn/vuedcb0efb21e5f2ca013ca1480198bbf4b3.png', 20 | width: 0, 21 | height: 0, 22 | } 23 | }; 24 | if (this.props.multiple) { 25 | this.name = this.props.inputOfFile + '[]'; 26 | } else { 27 | this.name = this.props.inputOfFile; 28 | } 29 | this.change = this.change.bind(this); 30 | this.doCrop = this.doCrop.bind(this); 31 | this.doResize = this.doResize.bind(this); 32 | this.cancel = this.cancel.bind(this); 33 | } 34 | 35 | render() { 36 | return ( 37 |
    38 | {this.props.children ? this.props.children: this.props.text} 39 |
    45 | 53 |
    54 |
    55 | this.cropbox = re} 57 | isResize={this.props.resize && !this.props.crop} 58 | ratio={this.props.ratio}> 59 | 60 |
    61 | {this.props.crop ? 62 |

    63 | 69 | 75 |

    : null} 76 | {this.props.resize ? 77 |

    78 | 84 | 90 |

    : null} 91 |
    92 |
    93 |
    94 | ); 95 | } 96 | 97 | __dispatch(name, res) { 98 | if (this.props[name] && typeof this.props[name] === 'function') { 99 | this.props[name].apply(this, Array.from(arguments).slice(1)); 100 | } 101 | } 102 | 103 | __find(ele) { 104 | let dq = document.getElementById(this.state.formID); 105 | return dq.querySelector(ele); 106 | } 107 | 108 | change(e) { 109 | let fileVal = this.__find('input').value.replace(/C:\\fakepath\\/i, ""); 110 | let fileExt = fileVal.substring(fileVal.lastIndexOf(".") + 1); 111 | const extensionsArr = this.props.extensions; 112 | if (extensionsArr.length > 1 ) { 113 | var reg = new RegExp('^[' + extensionsArr.join('|') + ']+$','i'); 114 | if (!reg.test(fileExt)) { 115 | return this.__dispatch('errorHandle', errorCode['FILE_FORMAT']); 116 | } 117 | } 118 | if (e.target.files[0].size > this.props.maxFileSize) { 119 | var formatSize; 120 | if (parseInt(this.maxFileSize / 1024 / 1024) > 0) { 121 | formatSize = (this.maxFileSize / 1024 / 1024).toFixed(2) + 'MB'; 122 | } else if (parseInt(this.maxFileSize / 1024) > 0) { 123 | formatSize = (this.maxFileSize / 1024).toFixed(2) + 'kB'; 124 | } else { 125 | formatSize = options.maxFileSize.toFixed(2) + 'Byte'; 126 | } 127 | this.__dispatch('errorHandle', errorCode['FILE_MAXSIZE'], 'FILE IS TOO LARGER THAN THE MAX VALUE ' + formatSize); 128 | return; 129 | } 130 | 131 | this.files = e.target.files; 132 | if(this.props.crop || this.props.resize) { 133 | this.__showImage(); 134 | return; 135 | } 136 | this.__dispatch('imageChanged', this.files.length > 1 ? this.files : this.files[0]); 137 | if (this.props.compress && this.files[0]['type'] !== 'image/png' && this.files[0]['type'] !== 'image/gif') { 138 | canvasHelper.compress(this.files[0], 100 - this.props.compress, (code) => { 139 | this.tryAjaxUpload('', true, code); 140 | }); 141 | } else { 142 | this.tryAjaxUpload(); 143 | } 144 | } 145 | __showImage() { 146 | this.setState({ 147 | hasImage: true 148 | }) 149 | this.__readFiles(); 150 | } 151 | __readFiles() { 152 | let reader = new FileReader(); 153 | let self = this; 154 | reader.onload = function(e) { 155 | let src = e.target.result; 156 | overflowVal = document.body.style.overflow; 157 | document.body.style.overflow = 'hidden'; 158 | self.__initImage(src); 159 | 160 | } 161 | reader.readAsDataURL(this.files[0]); 162 | } 163 | __initImage(src) { 164 | let pic = new Image(); 165 | let self = this; 166 | pic.src = src; 167 | const cropBox = this.cropbox; 168 | pic.onload= function() { 169 | self.setState({ 170 | image:{ 171 | src: src, 172 | width: pic.naturalWidth, 173 | height: pic.naturalHeight, 174 | } 175 | }); 176 | self.imgChangeRatio = cropBox.setImage(src, pic.naturalWidth, pic.naturalHeight); 177 | } 178 | } 179 | 180 | resizeImage(progress) { 181 | const cropBox = this.$refs.cropBox; 182 | cropBox.resizeImage(progress); 183 | } 184 | 185 | doCrop(e) { 186 | this.__setData('crop'); 187 | const cropBox = this.cropbox; 188 | const upload = this.__setUpload(e.target); 189 | if (this.props.crop === 'local') { 190 | const targetImage = cropBox.getCropImage(); 191 | this.props.data.comprese = 100 - this.props.compress; 192 | return canvasHelper.crop(targetImage, this.props.data, (code) => { 193 | console.log(code); 194 | upload(code); 195 | }) 196 | } 197 | upload(); 198 | } 199 | 200 | doResize(e) { 201 | this.__setData('reszie'); 202 | const cropBox = this.cropbox; 203 | const upload = this.__setUpload(e.target); 204 | if (this.props.resize === 'local') { 205 | const targetImage = cropBox.getCropImage(); 206 | this.data.comprose = 100 - this.compress; 207 | return canvasHelper.resize(targetImage, this.data, (code) => { 208 | upload(code); 209 | }) 210 | } 211 | upload(); 212 | } 213 | 214 | __setData(type) { 215 | this.props.data["request"] = type; 216 | const cropBox = this.cropbox; 217 | const newCSSObj = cropBox.getCropData(); 218 | for (const k of Object.keys(newCSSObj)) { 219 | this.props.data[k] = newCSSObj[k]; 220 | } 221 | if (this.props.maxWidth) { 222 | this.props.data['maxWidth'] = this.maxWidth; 223 | } 224 | if (this.maxHeight) { 225 | this.props.data['maxHeight'] = this.maxHeight; 226 | } 227 | if (this.minWidth) { 228 | this.props.data['minWidth'] = this.minWidth; 229 | } 230 | } 231 | __setUpload(btn) { 232 | btn.value = btn.value + '...'; 233 | btn.disabled = true; 234 | const upload = (code) => { 235 | this.tryAjaxUpload(() => { 236 | btn.value = btn.value.replace('...',''); 237 | btn.disabled = false; 238 | }, code ? true: false, code); 239 | }; 240 | return upload; 241 | } 242 | 243 | cancel() { 244 | this.setState({ 245 | hasImage: false, 246 | }) 247 | document.body.style.overflow = overflowVal; 248 | this.files = ''; 249 | this.__find('input').value = ''; 250 | } 251 | // use ajax upload IE10+ 252 | tryAjaxUpload(callback, isBinary, base64Code) { 253 | const self = this; 254 | this. __dispatch('imageuploading',this.files[0]); 255 | const done = function(res) { 256 | if(typeof callback === 'function') { 257 | callback(); 258 | } 259 | self.uploading = false; 260 | self.cancel(); 261 | self.__dispatch('imageUploaded',res); 262 | }; 263 | const errorUpload = function(err) { 264 | self.__dispatch('errorHandle', errorCode['SERVER_ERROR']); 265 | }; 266 | if (!this.props.isXhr) { 267 | if(this.props.crop) { 268 | this.setState( 269 | hasImage: false, 270 | ); 271 | } 272 | return typeof callback === 'function' && callback(); 273 | } 274 | let data; 275 | if (isBinary) { 276 | data = { 277 | type: this.files[0]['type'], 278 | filename: this.files[0]['name'], 279 | filed: this.props.inputOfFile, 280 | base64Code: base64Code 281 | }; 282 | if (typeof this.props.data === 'object') { 283 | data = Object.assign(this.props.data, data); 284 | } 285 | } else { 286 | data = new FormData(); 287 | for (let i = 0;i < this.files.length; i++) { 288 | data.append(this.name, this.files[i]); 289 | } 290 | if (typeof this.props.data === 'object') { 291 | for(let k in this.props.data) { 292 | if(this.props.data[k] !== undefined) { 293 | data.append(k,this.props.data[k]); 294 | } 295 | } 296 | } 297 | } 298 | xhr('POST', this.props.url, this.props.headers, data, done, errorUpload, isBinary); 299 | } 300 | } 301 | ReactCoreImageUpload.propTypes = propTypes; 302 | ReactCoreImageUpload.defaultProps = defaultProps; 303 | 304 | export default ReactCoreImageUpload; 305 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .g-core-image-upload-btn { 2 | position: relative; 3 | overflow: hidden; 4 | cursor: pointer; 5 | } 6 | 7 | .g-core-image-upload-form { 8 | position: absolute; 9 | left: 0; 10 | right: 0; 11 | top: 0; 12 | bottom: 0; 13 | opacity: 0; 14 | } 15 | 16 | .g-core-image-upload-container { 17 | position: absolute; 18 | background: #111; 19 | z-index: 900; 20 | } 21 | 22 | .g-core-image-upload-modal { 23 | position: absolute; 24 | left: 0; 25 | right: 0; 26 | width: 100%; 27 | height: 100%; 28 | border: 1px solid #ccc; 29 | z-index: 899; 30 | } 31 | 32 | .dropped { 33 | border: 4px solid #ea6153; 34 | } 35 | 36 | .g-core-image-corp-container { 37 | z-index: 1900; 38 | position: fixed; 39 | left: 0; 40 | right: 0; 41 | top: 0; 42 | bottom: 0; 43 | background: rgba(0, 0, 0, .9); 44 | color: #f1f1f1; 45 | } 46 | 47 | .g-core-image-corp-container .image-aside { 48 | overflow: hidden; 49 | position: absolute; 50 | right: 30px; 51 | left: 30px; 52 | top: 60px; 53 | bottom: 20px; 54 | text-align: center; 55 | } 56 | 57 | .g-core-image-corp-container .image-aside img { 58 | max-width: 100%; 59 | -webkit-touch-callout: none; 60 | -webkit-user-select: none; 61 | -khtml-user-select: none; 62 | -moz-user-select: none; 63 | -ms-user-select: none; 64 | user-select: none; 65 | } 66 | 67 | .g-core-image-corp-container .info-aside { 68 | position: absolute; 69 | left: 0; 70 | right: 0; 71 | top: 0; 72 | height: 40px; 73 | padding-left: 10px; 74 | padding-right: 10px; 75 | background: #fefefe; 76 | color: #777; 77 | } 78 | 79 | .g-core-image-corp-container .info-aside .image-corp-preview { 80 | position: relative; 81 | overflow: hidden; 82 | text-align: center; 83 | border: 2px solid #ccc; 84 | } 85 | 86 | .g-core-image-corp-container .info-aside .image-corp-preview.circled { 87 | border-radius: 160px; 88 | } 89 | 90 | .g-core-image-corp-container .info-aside .image-corp-preview img { 91 | width: 100%; 92 | } 93 | 94 | 95 | .g-core-image-corp-container .btn-groups { 96 | text-align: right; 97 | margin: 5px 0 0; 98 | } 99 | 100 | .g-core-image-corp-container .btn { 101 | display: inline-block; 102 | padding: 0 15px; 103 | height: 32px; 104 | margin-left: 15px; 105 | background: #fff; 106 | border: 1px solid #ccc; 107 | border-radius: 2px; 108 | font-size: 13px; 109 | color: #222; 110 | line-height: 32px; 111 | transition: all .1s ease-in; 112 | } 113 | 114 | .g-core-image-corp-container .btn:hover { 115 | border: 1px solid #777; 116 | box-shadow: 0 1px 3px rgba(0, 0, 0, .05); 117 | } 118 | 119 | .g-core-image-corp-container .btn:active, 120 | { 121 | background: #ddd; 122 | } 123 | 124 | .g-core-image-corp-container .btn:disabled { 125 | background: #eee !important; 126 | border-color: #ccc; 127 | cursor: not-allowed; 128 | } 129 | 130 | .g-core-image-corp-container .btn-upload { 131 | background: #2d2d2d; 132 | border-color: #3d3d3d; 133 | color: #fff; 134 | } 135 | 136 | .g-core-image-corp-container .btn-upload:hover { 137 | background: #222; 138 | border-color: #222; 139 | box-shadow: 0 1px 3px rgba(0, 0, 0, .05); 140 | } 141 | 142 | .g-core-image-corp-container .g-crop-image-box, 143 | .g-core-image-corp-container .g-crop-image-box .g-crop-image-principal { 144 | position: relative; 145 | } 146 | 147 | .g-crop-image-principal{ 148 | overflow: hidden; 149 | background-color: #fff; 150 | background-image: -webkit-linear-gradient(bottom left, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef),-webkit-linear-gradient(bottom left, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef); 151 | background-image: -moz-linear-gradient(bottom left, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef),-moz-linear-gradient(bottom left, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef); 152 | background-image: -o-linear-gradient(bottom left, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef),-o-linear-gradient(bottom left, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef); 153 | background-image: linear-gradient(to top right, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef),linear-gradient(to top right, #efefef 25%, transparent 25%, transparent 75%, #efefef 75%, #efefef); 154 | background-position: 0 0,10px 10px; 155 | -webkit-background-size: 21px 21px; 156 | background-size: 21px 21px; 157 | } 158 | .image-aside{ 159 | overflow: hidden; 160 | position: absolute; 161 | right: 30px; 162 | left:30px; 163 | top:70px; 164 | bottom:40px; 165 | text-align: center; 166 | } 167 | .image-aside .image-wrap{ 168 | position: absolute; 169 | left: 0; 170 | top: 0; 171 | -webkit-touch-callout: none; 172 | -webkit-user-select: none; 173 | -khtml-user-select: none; 174 | -moz-user-select: none; 175 | -ms-user-select: none; 176 | user-select: none; 177 | box-shadow: 0 3px 5px -2px rgba(0,0,0,.25); 178 | background-size: cover; 179 | } 180 | .image-mask{ 181 | position: absolute; 182 | left: 0; 183 | top: 0; 184 | width:100%; 185 | height: 100%; 186 | } 187 | .image-mask .mask { 188 | position: absolute; 189 | background-color: rgba(255,255,255,.6); 190 | } 191 | .crop-box{ 192 | z-index: 2000; 193 | box-sizing: border-box; 194 | position: absolute; 195 | background: none; 196 | cursor: move; 197 | width:100px; 198 | height: 100px; 199 | border:1px solid rgba(255,255,255, .95); 200 | } 201 | .crop-box:after, 202 | .crop-box:before{ 203 | content: ''; 204 | display: block; 205 | opacity: 0; 206 | position: absolute; 207 | left: 33.3333%; 208 | top: 0; 209 | width: 33.334%; 210 | height: 100%; 211 | background-color: transparent; 212 | border-color: rgba(255,255,255,.7); 213 | border-style: solid; 214 | border-width: 0; 215 | } 216 | .crop-box:active::before, 217 | .crop-box:active::after{ 218 | opacity: 1; 219 | } 220 | .crop-box:before{ 221 | border-left-width: 1px; 222 | border-right-width: 1px; 223 | } 224 | .crop-box:after{ 225 | top: 33.3333%; 226 | left: 0; 227 | height: 33.3334%; 228 | width: 100%; 229 | border-top-width: 1px; 230 | border-bottom-width: 1px; 231 | } 232 | .crop-box .g-resize{ 233 | display: inline-block; 234 | z-index: 1910; 235 | position: absolute; 236 | bottom: -8px; 237 | right: -8px; 238 | width: 16px; 239 | height: 16px; 240 | cursor: se-resize; 241 | border-radius: 10px; 242 | background-color: #fff; 243 | box-shadow: 0 2px 4px -2px rgba(0,0,0,.25); 244 | } 245 | 246 | .g-resize-bar{ 247 | position: absolute; 248 | bottom: 0px; 249 | margin: 17px auto; 250 | height: 6px; 251 | border-radius: 3px; 252 | width:200px; 253 | margin-left: -100px; 254 | left: 50%; 255 | background-color: #9cd6fd; 256 | box-shadow: 0 2px 3px -1px rgba(0,0,0,.3); 257 | } 258 | .g-resize-bar .g-resize-highlight{ 259 | position: absolute; 260 | left: 0; 261 | top:0; 262 | height: 6px; 263 | background-color: #3498db; 264 | border-radius: 3px; 265 | } 266 | .g-resize-bar .circle-btn{ 267 | display: block; 268 | position: absolute; 269 | left:0; 270 | top: -5px; 271 | width: 14px; 272 | height: 14px; 273 | margin-left: -7px; 274 | background-color: #fff; 275 | border-radius: 7px; 276 | box-shadow: 0 2px 3px -2px rgba(0,0,0,.6), 0 -2px 3px -2px rgba(0,0,0,.55); 277 | border-width: 0; 278 | } 279 | -------------------------------------------------------------------------------- /tests/react-core-image-upload.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount, render } from 'enzyme'; 3 | import ReactCoreImageUpload from '../src/react-core-image-upload'; 4 | 5 | describe('ReactCoreImageUpload Component Tests', function () { 6 | 7 | it('has a form',function() { 8 | expect(shallow().find('form').length).toBe(1); 9 | }); 10 | 11 | it('has a file input',function() { 12 | expect(shallow().find('input[type="file"]').length).toBe(1); 13 | }); 14 | 15 | it('allows us to set props', () => { 16 | let isSuccess = false; 17 | const successCallback = function() { 18 | isSuccess = true; 19 | }; 20 | const wrapper = mount( 21 | ); 22 | expect(wrapper.props().text).toEqual("Upload Your Image"); 23 | wrapper.setProps({ class: ['btn']}); 24 | expect(wrapper.props().class).toEqual(['btn']); 25 | }); 26 | }); -------------------------------------------------------------------------------- /webpack.config.build.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: "./src/react-core-image-upload", 5 | output: { 6 | path: __dirname + '/', 7 | filename: "react-core-image-upload.js", 8 | libraryTarget: 'umd', 9 | }, 10 | externals: { 11 | "react":"react", 12 | "react-dom":'react-dom', 13 | 'prop-types': 'prop-types' 14 | }, 15 | 16 | module: { 17 | loaders: [ 18 | { 19 | test: /\.js?$/, 20 | loaders: ['babel-loader?presets[]=react,presets[]=es2015'], 21 | exclude: /node_modules/ 22 | }, 23 | 24 | { 25 | test: /\.ope/, 26 | loader: "style!css" 27 | } 28 | ] 29 | }, 30 | plugins: [ 31 | new webpack.NoErrorsPlugin(), 32 | new webpack.optimize.UglifyJsPlugin( { 33 | minimize : true, 34 | sourceMap : false, 35 | mangle: true, 36 | compress: { 37 | warnings: false 38 | } 39 | } ) 40 | ] 41 | }; 42 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: [ 5 | "webpack-dev-server/client?http://localhost:9000", 6 | 'webpack/hot/only-dev-server', 7 | "./src/index" 8 | ], 9 | output: { 10 | path: __dirname + '/build', 11 | filename: "bundle.js", 12 | publicPath: '/build/', 13 | }, 14 | 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.js?$/, 19 | loaders: ["react-hot-loader/webpack",'babel-loader?presets[]=react,presets[]=es2015'], 20 | exclude: /node_modules/ 21 | }, 22 | 23 | { 24 | test: /\.ope/, 25 | loader: "style!css" 26 | } 27 | ] 28 | }, 29 | plugins: [ 30 | new webpack.NoErrorsPlugin(), 31 | new webpack.HotModuleReplacementPlugin() 32 | ] 33 | }; --------------------------------------------------------------------------------