摘要:要注意,的屬性是,而和,,對應的分別是,,的值設為這樣,保存拖拽選擇框時的參數,保存裁剪選擇框時的參數,為元素,為觸發事件時的。
開始
寫了一年多vue,感覺碰到了點瓶頸,學習下react找找感覺。剛好最近使用vue寫了個基于cropperJS的圖片裁剪的組件,便花費了幾個晚上的功夫用react再寫一遍。代碼地址,demo地址
項目是使用create-react-app來開發的,省去了很多webpack配置的功夫,支持eslint,自動刷新等功能,使用前npm install并npm start即可。推薦同樣是新學習react的人也用用看。
項目寫的比較簡陋,自定義配置比較差,不過也是完成了裁剪圖片的基本功能,希望可以幫助到初學react和想了解裁剪圖片組件的朋友。
組件的結構是這樣的。
ImageUploader主要做的就是上傳圖片,監聽了input的change事件,并調用了父組件Cropper的的handleImgChange方法,該方法設置了綁定到img元素的imageValue,會使得img元素出發load事件。
handleImgChange = e => { let fileReader = new FileReader() fileReader.readAsDataURL(e.target.files[0]) fileReader.onload = e => { this.setState({...this.state, imageValue: e.target.result}) } }
load事件觸發了Cropper的setSize方法,該方法可以設置了圖片和裁剪選擇框的初始位置和大小。目前裁剪選擇框是默認設置是大小為圖片的80%,中間顯示。
setSize = () => { let img = this.refs.img let widthNum = parseInt(this.props.width, 10) let heightNum = parseInt(this.props.height, 10) this.setState({ ...this.state, naturalSize: { width: img.naturalWidth, height: img.naturalHeight } }) let imgStyle = img.style imgStyle.height = "auto" imgStyle.width = "auto" let principalStyle = ReactDOM.findDOMNode(this.refs.selectArea).parentElement.style const ratio = img.width / img.height // 設置圖片大小、位置 if (img.width > img.height) { imgStyle.width = principalStyle.width = this.props.width imgStyle.height = principalStyle.height = widthNum / ratio + "px" principalStyle.marginTop = (widthNum - parseInt(principalStyle.height, 10)) / 2 + "px" principalStyle.marginLeft = 0 } else { imgStyle.height = principalStyle.height = this.props.height imgStyle.width = principalStyle.width = heightNum * ratio + "px" principalStyle.marginLeft = (heightNum - parseInt(principalStyle.width, 10)) / 2 + "px" principalStyle.marginTop = 0 } // 設置選擇框樣式 let selectAreaStyle = ReactDOM.findDOMNode(this.refs.selectArea).style let principalHeight = parseInt(principalStyle.height, 10) let principalWidth = parseInt(principalStyle.width, 10) if (principalWidth > principalHeight) { selectAreaStyle.top = principalHeight * 0.1 + "px" selectAreaStyle.width = selectAreaStyle.height = principalHeight * 0.8 + "px" selectAreaStyle.left = (principalWidth - parseInt(selectAreaStyle.width, 10)) / 2 + "px" } else { selectAreaStyle.left = principalWidth * 0.1 + "px" selectAreaStyle.width = selectAreaStyle.height = principalWidth * 0.8 + "px" selectAreaStyle.top = (principalHeight - parseInt(selectAreaStyle.height, 10)) / 2 + "px" } }
Cropper上還有一個getCropData方法,方法會打印并返回裁剪參數,
getCropData = e => { e.preventDefault() let SelectArea = ReactDOM.findDOMNode(this.refs.selectArea).style let a = { width: parseInt(SelectArea.width, 10), height: parseInt(SelectArea.height, 10), left: parseInt(SelectArea.left, 10), top: parseInt(SelectArea.top, 10) } a.radio = this.state.naturalSize.width / a.width console.log(a) return a }SelectArea
重新放一遍selectArea的結構。要注意,.top-resize的cursor屬性是 n-resize,而和left,right,bottom對應的分別是w-resize,e-resize,s-resize
this.resizeStart(event, "top")}>this.resizeStart(event, "right")}>this.resizeStart(event, "bottom")}>this.resizeStart(event, "left")}>this.resizeStart(event, "right")}>this.resizeStart(event, "left")}>
selectArea的state值設為這樣,selectArea保存拖拽選擇框時的參數,resizeArea保存裁剪選擇框時的參數,container為.image-principal元素,el為觸發事件時的event.target。
this.state = { selectArea: null, el: null, container: null, resizeArea: null }拖拽選擇框
在.select-area按下鼠標,觸發mouseDown事件,調用dragStart方法。
使用method = e => {}的形式可以避免在jsx中使用this.method.bind(this)
在這個方法中,首先保存按下鼠標時的鼠標位置,裁剪框與圖片的相對距離和裁剪框的最大位移距離,接著添加事件監聽
dragStart = e => { const el = e.target const container = this.state.container let selectArea = { posLeft: e.clientX, posTop: e.clientY, left: e.clientX - el.offsetLeft, top: e.clientY - el.offsetTop, maxMoveX: container.offsetWidth - el.offsetWidth, maxMoveY: container.offsetHeight - el.offsetHeight, } this.setState({ ...this.state, selectArea, el}) document.addEventListener("mousemove", this.moveBind, false) document.addEventListener("mouseup", this.stopBind, false) }
moveBind和stopBind來自于
this.moveBind = this.move.bind(this) this.stopBind = this.stop.bind(this)
move方法,在鼠標移動中根據記錄新的鼠標位置來計算新的相對位置newPosLeft和newPosTop,并控制該值在合理范圍內
move(e) { if (!this.state || !this.state.el || !this.state.selectArea) { return } let selectArea = this.state.selectArea let newPosLeft = e.clientX- selectArea.left let newPosTop = e.clientY - selectArea.top // 控制移動范圍 if (newPosLeft <= 0) { newPosLeft = 0 } else if (newPosLeft > selectArea.maxMoveX) { newPosLeft = selectArea.maxMoveX } if (newPosTop <= 0) { newPosTop = 0 } else if (newPosTop > selectArea.maxMoveY) { newPosTop = selectArea.maxMoveY } let elStyle = this.state.el.style elStyle.left = newPosLeft + "px" elStyle.top = newPosTop + "px" }
stop方法,移除事件監聽,清除state,避免方法錯誤調用
stop() { document.removeEventListener("mousemove", this.moveBind , false) document.removeEventListener("mousemove", this.resizeBind , false) document.removeEventListener("mouseup", this.stopBind, false) this.setState({...this.state, el: null, resizeArea: null, selectArea: null}) }裁剪選擇框
跟拖拽一樣,首先調用resizeStart方法,保存開始裁剪的鼠標位置,裁剪框的尺寸和位置,添加關于resizeBind和stopBind的事件監聽,注意,由于react的事件機制特點,需要使用stopPropagation來禁止事件冒泡,事件監聽的第三個參數使用false是無效的。
resizeStart = (e, type) => { e.stopPropagation() const el = e.target.parentElement let resizeArea = { posLeft: e.clientX, posTop: e.clientY, width: el.offsetWidth, height: el.offsetHeight, left: parseInt(el.style.left, 10), top: parseInt(el.style.top, 10) } this.setState({ ...this.state, resizeArea, el}) this.resizeBind = this.resize.bind(this, type) document.addEventListener("mousemove", this.resizeBind, false) document.addEventListener("mouseup", this.stopBind, false) }
裁剪的方法,將裁剪分為兩種情況,一種是右側,下側和右下側的拉伸。另一種是左側,上側和左上側的拉伸。
第一種情況下,選擇框的位置是不會變的,只有尺寸會變,處理起來相對簡單。新的尺寸大小為原大小加上當前的鼠標的位置再減去開始拖拽處的鼠標的位置,如果寬度或者高度有一個超標了,則將尺寸設置為剛好到邊界的大小。均為超標,設置為新的尺寸。
第二種情況下,選擇框的位置和大小同時會變,要同時控制尺寸和位置不超出邊界。
resize(type, e) { if (!this.state || !this.state.el || !this.state.resizeArea) { return } let container = this.state.container const containerHeight = container.offsetHeight const containerWidth = container.offsetWidth const containerLeft = parseInt(container.style.left || 0, 10) const containerTop = parseInt(container.style.top || 0, 10) let resizeArea = this.state.resizeArea let el = this.state.el let elStyle = el.style if (type === "right" || type === "bottom") { let length if (type === "right") { length = resizeArea.width + e.clientX - resizeArea.posLeft } else { length = resizeArea.height + e.clientY - resizeArea.posTop } if (parseInt(el.style.left, 10) + length > containerWidth || parseInt(el.style.top, 10) + length > containerHeight) { const w = containerWidth - parseInt(el.style.left, 10) const h = containerHeight - parseInt(el.style.top, 10) elStyle.width = elStyle.height = Math.min(w, h) + "px" } else { elStyle.width = length + "px" elStyle.height = length + "px" } } else { let posChange let newPosLeft let newPosTop if (type === "left") { posChange = resizeArea.posLeft - e.clientX } else { posChange = resizeArea.posTop - e.clientY } newPosLeft = resizeArea.left - posChange // 防止過度縮小 if (newPosLeft > resizeArea.left + resizeArea.width) { elStyle.left = resizeArea.left + resizeArea.width + "px" elStyle.top = resizeArea.top + resizeArea.height + "px" elStyle.width = elStyle.height = "2px" return } newPosTop = resizeArea.top - posChange // 到達邊界 if (newPosLeft <= containerLeft || newPosTop < containerTop) { // 讓選擇框到圖片最左邊 let newPosLeft2 = resizeArea.left -containerLeft // 判斷頂部會不會超出邊界 if (newPosLeft2 < resizeArea.top) { // 未超出邊界 elStyle.top = resizeArea.top - newPosLeft2 + "px" elStyle.left = containerLeft + "px" } else { // 讓選擇框到達圖片頂部 elStyle.top = containerTop + "px" elStyle.left = resizeArea.left + containerTop - resizeArea.top + "px" } } else { if (newPosLeft < 0) { elStyle.left = 0; elStyle.width = Math.min(resizeArea.width + posChange - newPosLeft, containerWidth) + "px" elStyle.top = newPosTop - newPosLeft; elStyle.height = Math.min(resizeArea.height + posChange - newPosLeft, containerHeight) + "px" return; } if (newPosTop < 0) { elStyle.left = newPosLeft - newPosTop; elStyle.width = Math.min(resizeArea.width + posChange - newPosTop, containerWidth) + "px" elStyle.top = 0; elStyle.height = Math.min(resizeArea.height + posChange - newPosTop, containerHeight) + "px" return; } elStyle.left = newPosLeft + "px" elStyle.top = newPosTop + "px" elStyle.width = resizeArea.width + posChange + "px" elStyle.height = resizeArea.height + posChange + "px" } } }結束
通過這些組件的編寫,感覺想要學好react,需要加深對this和事件模型的了解,這幾天在這上面踩了不少的坑。如果覺得這篇文章有幫助的話,歡迎star我的項目
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94355.html
自定義input上傳圖片組件,美化樣式 前段時間因為項目需求,做過一個上傳圖片組件,這里分享下大致思路,希望對有需要的人有所幫助~~~ 功能需求:1.上傳圖片限制大小、分辨率、類型 2.上傳圖片支持自由裁剪 3.圖片上傳后支持預覽和刪除 效果圖,只截取了一小部分,大致看下就ok啦,是不是感覺比原生的好看多了^_^showImg(https://segment...
自定義input上傳圖片組件,美化樣式 前段時間因為項目需求,做過一個上傳圖片組件,這里分享下大致思路,希望對有需要的人有所幫助~~~ 功能需求:1.上傳圖片限制大小、分辨率、類型 2.上傳圖片支持自由裁剪 3.圖片上傳后支持預覽和刪除 效果圖,只截取了一小部分,大致看下就ok啦,是不是感覺比原生的好看多了^_^showImg(https://segment...
自定義input上傳圖片組件,美化樣式 前段時間因為項目需求,做過一個上傳圖片組件,這里分享下大致思路,希望對有需要的人有所幫助~~~ 功能需求:1.上傳圖片限制大小、分辨率、類型 2.上傳圖片支持自由裁剪 3.圖片上傳后支持預覽和刪除 效果圖,只截取了一小部分,大致看下就ok啦,是不是感覺比原生的好看多了^_^showImg(https://segment...
摘要:需求是編寫一個頭像剪裁工具再封裝成為一個組件,然后根據功能開始逐步編寫代碼先是上傳圖片預覽圖片編輯圖片。 需求是編寫一個頭像剪裁工具再封裝成為一個組件,然后根據功能開始逐步編寫代碼:先是上傳圖片 => 預覽圖片 => 編輯圖片。 剛開始沒有去考慮兼容性的問題,直接編寫upload部分的代碼,參考了很多代碼還有HTML5 FILE API之后,發現很少有React編寫的這樣的代碼,因為想...
閱讀 2364·2021-11-25 09:43
閱讀 2872·2021-11-24 09:39
閱讀 2936·2019-08-30 11:10
閱讀 1144·2019-08-29 16:34
閱讀 607·2019-08-29 13:25
閱讀 3368·2019-08-29 11:21
閱讀 2870·2019-08-26 11:39
閱讀 2403·2019-08-26 11:34