一個基於react的圖片裁剪組件

最先

寫了一年多vue,以為遇到了點瓶頸,進修下react找找以為。恰好近來運用vue寫了個基於cropperJS的圖片裁剪的組件,便花費了幾個晚上的工夫用react再寫一遍。代碼地點demo地點
項目是運用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} alt="" className="img" ref="img" onLoad={this.setSize}/>
         <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我的項目

    原文作者:hpoenixf
    原文地址: https://segmentfault.com/a/1190000014451179
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞