├── .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 |
9 | 10 | 11 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------