├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
└── index.html
└── src
├── App.css
├── index.css
├── index.js
└── logo.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 只是一个练手的项目,具体方法的解释见我的博客文章,http://hpoenixf.com/2017/06/10/一个基于react的图片裁剪组件/
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-cropper",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4"
8 | },
9 | "devDependencies": {
10 | "react-scripts": "0.9.5"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hpoenixf/react-cropper/b86c2777d94bbd23d964b13c4affc4409427e7d3/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 | React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | img.img {
2 | width: 200px;
3 | height: 200px
4 | }
5 | .image-principal {
6 | position: relative;
7 | }
8 | .select-area {
9 | box-sizing: border-box;
10 | border: 2px solid green;
11 | position: absolute;
12 | background: #fff;
13 | opacity: 0.5;
14 | cursor: move;
15 | }
16 | .top-resize {
17 | position: absolute;
18 | cursor: n-resize;
19 | height: 7px;
20 | width: 100%;
21 | top: -5px;
22 | left: 0;
23 | }
24 | .bottom-resize {
25 | position: absolute;
26 | cursor: s-resize;
27 | height: 7px;
28 | width: 100%;
29 | bottom: -5px;
30 | left: 0;
31 | }
32 | .left-resize {
33 | position: absolute;
34 | cursor: w-resize;
35 | width: 7px;
36 | height: 100%;
37 | left: -5px;
38 | top: 0;
39 | }
40 | .right-resize {
41 | position: absolute;
42 | cursor: e-resize;
43 | width: 7px;
44 | height: 100%;
45 | right: -5px;
46 | top: 0;
47 | }
48 | .left-top-resize {
49 | z-index: 90;
50 | position: absolute;
51 | cursor: nw-resize;
52 | width: 20px;
53 | height: 20px;
54 | left: 1px;
55 | top: 1px;
56 | }
57 | .right-bottom-resize {
58 | z-index: 90;
59 | position: absolute;
60 | cursor: se-resize;
61 | width: 20px;
62 | height: 20px;
63 | bottom: -1px;
64 | right: -1px;
65 | }
66 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './App.css';
3 | import ReactDOM from 'react-dom'
4 |
5 | class ImageUploader extends Component {
6 | render() {
7 | return (
8 |
12 | )
13 | }
14 | }
15 |
16 | class Cropper extends Component {
17 | constructor() {
18 | super()
19 | this.state = {
20 | imageValue: null
21 | }
22 | }
23 | handleImgChange = e => {
24 | let fileReader = new FileReader()
25 | fileReader.readAsDataURL(e.target.files[0])
26 | fileReader.onload = e => {
27 | this.setState({...this.state, imageValue: e.target.result})
28 | }
29 | }
30 |
31 |
32 | setSize = () => {
33 | let img = this.refs.img
34 | let widthNum = parseInt(this.props.width, 10)
35 | let heightNum = parseInt(this.props.height, 10)
36 | this.setState({
37 | ...this.state,
38 | naturalSize: {
39 | width: img.naturalWidth,
40 | height: img.naturalHeight
41 | }
42 | })
43 | let imgStyle = img.style
44 | imgStyle.height = 'auto'
45 | imgStyle.width = 'auto'
46 | let principalStyle = ReactDOM.findDOMNode(this.refs.selectArea).parentElement.style
47 | const ratio = img.width / img.height
48 | // 设置图片大小、位置
49 | if (img.width > img.height) {
50 | imgStyle.width = principalStyle.width = this.props.width
51 | imgStyle.height = principalStyle.height = widthNum / ratio + 'px'
52 | principalStyle.marginTop = (widthNum - parseInt(principalStyle.height, 10)) / 2 + 'px'
53 | principalStyle.marginLeft = 0
54 | } else {
55 | imgStyle.height = principalStyle.height = this.props.height
56 | imgStyle.width = principalStyle.width = heightNum * ratio + 'px'
57 | principalStyle.marginLeft = (heightNum - parseInt(principalStyle.width, 10)) / 2 + 'px'
58 | principalStyle.marginTop = 0
59 | }
60 | // 设置选择框样式
61 | let selectAreaStyle = ReactDOM.findDOMNode(this.refs.selectArea).style
62 | let principalHeight = parseInt(principalStyle.height, 10)
63 | let principalWidth = parseInt(principalStyle.width, 10)
64 | if (principalWidth > principalHeight) {
65 | selectAreaStyle.top = principalHeight * 0.1 + 'px'
66 | selectAreaStyle.width = selectAreaStyle.height = principalHeight * 0.8 + 'px'
67 | selectAreaStyle.left = (principalWidth - parseInt(selectAreaStyle.width, 10)) / 2 + 'px'
68 | } else {
69 | selectAreaStyle.left = principalWidth * 0.1 + 'px'
70 | selectAreaStyle.width = selectAreaStyle.height = principalWidth * 0.8 + 'px'
71 | selectAreaStyle.top = (principalHeight - parseInt(selectAreaStyle.height, 10)) / 2 + 'px'
72 | }
73 | }
74 | getCropData = e => {
75 | e.preventDefault()
76 | let SelectArea = ReactDOM.findDOMNode(this.refs.selectArea).style
77 |
78 | let a = {
79 | width: parseInt(SelectArea.width, 10),
80 | height: parseInt(SelectArea.height, 10),
81 | left: parseInt(SelectArea.left, 10),
82 | top: parseInt(SelectArea.top, 10)
83 | }
84 | a.radio = this.state.naturalSize.width / a.width
85 |
86 | console.log(a)
87 | return a
88 | }
89 | render() {
90 | return (
91 |
92 |
93 |
94 |

95 |
96 |
97 |
98 | )
99 | }
100 | }
101 |
102 |
103 | class SelectArea extends Component {
104 | constructor () {
105 | super()
106 | this.state = {
107 | selectArea: null,
108 | el: null,
109 | container: null,
110 | resizeArea: null
111 | }
112 | }
113 | componentDidMount() {
114 | this.moveBind = this.move.bind(this)
115 | this.stopBind = this.stop.bind(this)
116 | const container = ReactDOM.findDOMNode(this.refs.selectArea).parentElement
117 | this.setState({...this.state, container})
118 | }
119 | dragStart = e => {
120 | const el = e.target
121 | const container = this.state.container
122 | let selectArea = {
123 | posLeft: e.clientX,
124 | posTop: e.clientY,
125 | left: e.clientX - el.offsetLeft,
126 | top: e.clientY - el.offsetTop,
127 | maxMoveX: container.offsetWidth - el.offsetWidth,
128 | maxMoveY: container.offsetHeight - el.offsetHeight,
129 | }
130 | this.setState({ ...this.state, selectArea, el})
131 | document.addEventListener('mousemove', this.moveBind, false)
132 | document.addEventListener('mouseup', this.stopBind, false)
133 | }
134 | move(e) {
135 | if (!this.state || !this.state.el || !this.state.selectArea) {
136 | return
137 | }
138 | let selectArea = this.state.selectArea
139 | let newPosLeft = e.clientX- selectArea.left
140 | let newPosTop = e.clientY - selectArea.top
141 | // 控制移动范围
142 | if (newPosLeft <= 0) {
143 | newPosLeft = 0
144 | } else if (newPosLeft > selectArea.maxMoveX) {
145 | newPosLeft = selectArea.maxMoveX
146 | }
147 | if (newPosTop <= 0) {
148 | newPosTop = 0
149 | } else if (newPosTop > selectArea.maxMoveY) {
150 | newPosTop = selectArea.maxMoveY
151 | }
152 | let elStyle = this.state.el.style
153 | elStyle.left = newPosLeft + 'px'
154 | elStyle.top = newPosTop + 'px'
155 | }
156 | resize(type, e) {
157 | if (!this.state || !this.state.el || !this.state.resizeArea) {
158 | return
159 | }
160 | let container = this.state.container
161 | const containerHeight = container.offsetHeight
162 | const containerWidth = container.offsetWidth
163 | const containerLeft = parseInt(container.style.left || 0, 10)
164 | const containerTop = parseInt(container.style.top || 0, 10)
165 | let resizeArea = this.state.resizeArea
166 | let el = this.state.el
167 | let elStyle = el.style
168 | if (type === 'right' || type === 'bottom') {
169 | let length
170 | if (type === 'right') {
171 | length = resizeArea.width + e.clientX - resizeArea.posLeft
172 | } else {
173 | length = resizeArea.height + e.clientY - resizeArea.posTop
174 | }
175 | if (parseInt(el.style.left, 10) + length > containerWidth || parseInt(el.style.top, 10) + length > containerHeight) {
176 | const w = containerWidth - parseInt(el.style.left, 10)
177 | const h = containerHeight - parseInt(el.style.top, 10)
178 | elStyle.width = elStyle.height = Math.min(w, h) + 'px'
179 | } else {
180 | elStyle.width = length + 'px'
181 | elStyle.height = length + 'px'
182 | }
183 | } else {
184 | let posChange
185 | let newPosLeft
186 | let newPosTop
187 | if (type === 'left') {
188 | posChange = resizeArea.posLeft - e.clientX
189 | } else {
190 | posChange = resizeArea.posTop - e.clientY
191 | }
192 | newPosLeft = resizeArea.left - posChange
193 | // 防止过度缩小
194 | if (newPosLeft > resizeArea.left + resizeArea.width) {
195 | elStyle.left = resizeArea.left + resizeArea.width + 'px'
196 | elStyle.top = resizeArea.top + resizeArea.height + 'px'
197 | elStyle.width = elStyle.height = '2px'
198 | return
199 | }
200 | newPosTop = resizeArea.top - posChange
201 | // 到达边界
202 | if (newPosLeft <= containerLeft || newPosTop < containerTop) {
203 | // 让选择框到图片最左边
204 | let newPosLeft2 = resizeArea.left -containerLeft
205 | // 判断顶部会不会超出边界
206 | if (newPosLeft2 < resizeArea.top) {
207 | // 未超出边界
208 | elStyle.top = resizeArea.top - newPosLeft2 + 'px'
209 | elStyle.left = containerLeft + 'px'
210 | } else {
211 | // 让选择框到达图片顶部
212 | elStyle.top = containerTop + 'px'
213 | elStyle.left = resizeArea.left + containerTop - resizeArea.top + 'px'
214 | }
215 | } else {
216 | if (newPosLeft < 0) {
217 | elStyle.left = 0;
218 | elStyle.width = Math.min(resizeArea.width + posChange - newPosLeft, containerWidth) + 'px'
219 | elStyle.top = newPosTop - newPosLeft;
220 | elStyle.height = Math.min(resizeArea.height + posChange - newPosLeft, containerHeight) + 'px'
221 | return;
222 | }
223 | if (newPosTop < 0) {
224 | elStyle.left = newPosLeft - newPosTop;
225 | elStyle.width = Math.min(resizeArea.width + posChange - newPosTop, containerWidth) + 'px'
226 | elStyle.top = 0;
227 | elStyle.height = Math.min(resizeArea.height + posChange - newPosTop, containerHeight) + 'px'
228 | return;
229 | }
230 | elStyle.left = newPosLeft + 'px'
231 | elStyle.top = newPosTop + 'px'
232 | elStyle.width = resizeArea.width + posChange + 'px'
233 | elStyle.height = resizeArea.height + posChange + 'px'
234 | }
235 | }
236 | }
237 | stop() {
238 | document.removeEventListener('mousemove', this.moveBind , false)
239 | document.removeEventListener('mousemove', this.resizeBind , false)
240 | document.removeEventListener('mouseup', this.stopBind, false)
241 | this.setState({...this.state, el: null, resizeArea: null, selectArea: null})
242 | }
243 | componentWillUnmount() {
244 | this.stop()
245 | }
246 | resizeStart = (e, type) => {
247 | e.stopPropagation()
248 | const el = e.target.parentElement
249 | let resizeArea = {
250 | posLeft: e.clientX,
251 | posTop: e.clientY,
252 | width: el.offsetWidth,
253 | height: el.offsetHeight,
254 | left: parseInt(el.style.left, 10),
255 | top: parseInt(el.style.top, 10)
256 | }
257 | this.setState({ ...this.state, resizeArea, el})
258 | this.resizeBind = this.resize.bind(this, type)
259 | document.addEventListener('mousemove', this.resizeBind, false)
260 | document.addEventListener('mouseup', this.stopBind, false)
261 | }
262 |
263 | render() {
264 | return (
265 |
266 |
this.resizeStart(event, 'top')}>
267 |
this.resizeStart(event, 'right')}>
268 |
this.resizeStart(event, 'bottom')}>
269 |
this.resizeStart(event, 'left')}>
270 |
this.resizeStart(event, 'right')}>
271 |
this.resizeStart(event, 'left')}>
272 |
273 | )
274 | }
275 | }
276 |
277 | ReactDOM.render((
278 |
279 | ), document.getElementById('root'))
280 |
281 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------