亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

一個基于react的圖片裁剪組件示例

2024-05-06 16:43:46
字體:
來源:轉載
供稿:網友

開始

寫了一年多vue,感覺碰到了點瓶頸,學習下react找找感覺。剛好最近使用vue寫了個基于cropperJS的圖片裁剪的組件,便花費了幾個晚上的功夫用react再寫一遍。代碼地址

項目是使用create-react-app來開發的,省去了很多webpack配置的功夫,支持eslint,自動刷新等功能,使用前npm install并npm start即可。推薦同樣是新學習react的人也用用看。

項目寫的比較簡陋,自定義配置比較差,不過也是完成了裁剪圖片的基本功能,希望可以幫助到初學react和想了解裁剪圖片組件的朋友。

組件的結構是這樣的。

<!--Cropper-->   <div>   <ImageUploader handleImgChange={this.handleImgChange} getCropData={this.getCropData}/>    <div className="image-principal">     <img src={this.state.imageValue}     <SelectArea ref="selectArea"></SelectArea>    </div>   </div><!--ImageUploader   -->   <form className="image-upload-form" method="post" encType="multipart/form-data" >    <input type="file" name="inputOfFile" ref="imgInput" id="imgInput" onChange={this.props.handleImgChange}/>    <button onClick={this.props.getCropData}>獲取裁剪參數</button>   </form><!--SelectArea   -->   <div className="select-area" onMouseDown={ this.dragStart} ref="selectArea" >    <div className="top-resize" onMouseDown={ event => this.resizeStart(event, 'top')}></div>    <div className="right-resize" onMouseDown={ event => this.resizeStart(event, 'right')}></div>    <div className="bottom-resize" onMouseDown={ event => this.resizeStart(event, 'bottom')}></div>    <div className="left-resize" onMouseDown={ event => this.resizeStart(event, 'left')}></div>    <div className="right-bottom-resize" onMouseDown={ event => this.resizeStart(event, 'right')}></div>    <div className="left-top-resize" onMouseDown={ event => this.resizeStart(event, 'left')}></div>   </div>

ImageUploader & Cropper

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

   <div className="select-area" onMouseDown={ this.dragStart} ref="selectArea" >    <div className="top-resize" onMouseDown={ event => this.resizeStart(event, 'top')}></div>    <div className="right-resize" onMouseDown={ event => this.resizeStart(event, 'right')}></div>    <div className="bottom-resize" onMouseDown={ event => this.resizeStart(event, 'bottom')}></div>    <div className="left-resize" onMouseDown={ event => this.resizeStart(event, 'left')}></div>    <div className="right-bottom-resize" onMouseDown={ event => this.resizeStart(event, 'right')}></div>    <div className="left-top-resize" onMouseDown={ event => this.resizeStart(event, 'left')}></div>   </div>

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我的項目

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产一区二区久久精品| 国产精品三级网站| 亚洲精品久久久久久下一站| 久久久久久国产精品久久| 精品国产自在精品国产浪潮| 亚洲欧美综合图区| 亚洲第五色综合网| 日韩专区中文字幕| 久久精品福利视频| 欧美最猛黑人xxxx黑人猛叫黄| 成人激情视频免费在线| 尤物九九久久国产精品的特点| 久久中文字幕在线| 成人激情电影一区二区| 日韩在线观看视频免费| 久久精品视频一| 国产在线观看一区二区三区| 午夜精品久久久久久久99黑人| 91精品视频免费观看| 日韩在线观看免费全| 亚洲自拍偷拍福利| 久热精品视频在线观看一区| 国产欧美日韩丝袜精品一区| 日本亚洲欧洲色| 韩日精品中文字幕| 欧美日韩国产激情| 国产欧美 在线欧美| 欧美精品久久久久a| 国产精品视频一区二区高潮| 国产成人精品视频在线观看| 97超级碰碰人国产在线观看| 国产欧美亚洲视频| 高清欧美电影在线| 亚洲已满18点击进入在线看片| 91九色精品视频| 国产精品久久久久免费a∨大胸| 在线精品播放av| 欧美乱妇高清无乱码| 国产原创欧美精品| 日韩av免费在线观看| 亚洲在线第一页| 欧美一区二区影院| 国产成人精品国内自产拍免费看| 欧美性xxxxxxxxx| 久久成人人人人精品欧| 久久久在线视频| 欧洲s码亚洲m码精品一区| 日韩在线视频一区| 欧美精品videosex性欧美| 日韩大片免费观看视频播放| 中文字幕在线日韩| 青草青草久热精品视频在线观看| 欧美亚洲另类激情另类| 国产亚洲精品久久| 久热精品视频在线观看| 欧美日韩亚洲高清| 55夜色66夜色国产精品视频| 国产91在线播放九色快色| 亚洲欧洲在线观看| 日韩不卡在线观看| 91在线网站视频| 成人在线播放av| 国产亚洲一级高清| 成人黄色影片在线| 91免费看片在线| 亚洲福利在线视频| 97视频在线观看播放| 黑人巨大精品欧美一区二区| 亚洲欧美精品伊人久久| 日韩精品中文字幕在线播放| 国产欧美日韩亚洲精品| 亚洲一区二区三区成人在线视频精品| 日本午夜在线亚洲.国产| 欧美成人中文字幕| 色偷偷88888欧美精品久久久| 成人h视频在线观看播放| 久久夜精品香蕉| 国产日韩中文字幕在线| 91精品国产色综合| 久久久电影免费观看完整版| 成人国内精品久久久久一区| 亚洲国产97在线精品一区| 黑人巨大精品欧美一区免费视频| 97色在线视频| 91精品国产777在线观看| 亚洲第一页自拍| 日韩欧美中文字幕在线观看| 成人激情综合网| 精品国产老师黑色丝袜高跟鞋| 亚洲石原莉奈一区二区在线观看| 久久精品国产一区二区三区| 久久99青青精品免费观看| 精品中文字幕久久久久久| 日韩电影在线观看免费| 久久综合免费视频影院| 久久久久在线观看| 久久久久久久久久婷婷| 欧美一区二区.| 91精品国产综合久久香蕉最新版| 成人亲热视频网站| 日韩中文字幕国产精品| 日韩在线高清视频| 久久国产精品亚洲| 国产日韩欧美一二三区| 国产精品福利在线观看| 日韩第一页在线| 日韩美女在线播放| 日韩在线国产精品| 91精品国产自产在线老师啪| 最近2019年日本中文免费字幕| 国产精品三级网站| 欧美久久精品一级黑人c片| 久久久噜久噜久久综合| 国产日韩欧美中文在线播放| 97在线免费观看视频| 亚洲高清久久网| www.欧美精品一二三区| 国产97在线|亚洲| 色系列之999| 一二美女精品欧洲| 欧美成人精品在线观看| 夜夜嗨av色综合久久久综合网| 中文字幕亚洲一区在线观看| 欧美国产日本高清在线| 国产精品 欧美在线| 欧美高清在线视频观看不卡| 88xx成人精品| 国产精品久久久久久五月尺| 亚洲第一免费网站| 成人欧美一区二区三区在线湿哒哒| 久久精品国产一区二区电影| 一本一本久久a久久精品牛牛影视| 91精品国产乱码久久久久久蜜臀| 第一福利永久视频精品| 川上优av一区二区线观看| 中文国产成人精品久久一| 欧美午夜视频在线观看| 国产精品白嫩美女在线观看| 亚洲伊人成综合成人网| 亚洲国产精久久久久久| 国内精品美女av在线播放| 91国产视频在线| 丝袜亚洲欧美日韩综合| 98午夜经典影视| 国产精品主播视频| 91视频88av| 国产欧美精品日韩精品| 国产91露脸中文字幕在线| 久久中文字幕在线视频| 欧美激情久久久久久| 精品无人区乱码1区2区3区在线| 日本中文字幕久久看| 亚洲精品女av网站| 欧美丝袜一区二区| 97视频在线观看视频免费视频| 亚洲美女精品久久| 欧美亚洲午夜视频在线观看| 成人444kkkk在线观看| 久久久久在线观看| 亚洲日韩欧美视频一区| 一夜七次郎国产精品亚洲| 欧美成在线视频| 精品久久久久久久久久久久| 久久久精品国产一区二区|