Home > Web Front-end > JS Tutorial > How to crop images in react

How to crop images in react

php中世界最好的语言
Release: 2018-06-07 13:53:21
Original
2021 people have browsed it

This time I will show you how to crop images in React, and what are the precautions for cropping images in React. The following is a practical case, let’s take a look.

Start

I have been writing vue for more than a year, and I feel that I have hit a bottleneck. I want to learn react to find out the feeling. I just recently used Vue to write an image cropping component based on cropperJS, so I spent a few nights writing it again using React. The code address

project is developed using create-react-app, which saves a lot of effort in webpack configuration. It supports eslint, automatic refresh and other functions. Just npm install and npm start before use. It is recommended that people who are new to react also give it a try.

The project is relatively simple to write and the custom configuration is relatively poor. However, it also completes the basic function of cropping pictures. I hope it can help friends who are new to react and want to understand the cropping picture component.

The structure of the component is like this.

<!--Cropper-->
   <p>
   <ImageUploader handleImgChange={this.handleImgChange} getCropData={this.getCropData}/>
    <p className="image-principal">
     <img src={this.state.imageValue} alt="" className="img" ref="img" onLoad={this.setSize}/>
     <SelectArea ref="selectArea"></SelectArea>
    </p>
   </p>
<!--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   -->
   <p className="select-area" onMouseDown={ this.dragStart} ref="selectArea" >
    <p className="top-resize" onMouseDown={ event => this.resizeStart(event, 'top')}></p>
    <p className="right-resize" onMouseDown={ event => this.resizeStart(event, 'right')}></p>
    <p className="bottom-resize" onMouseDown={ event => this.resizeStart(event, 'bottom')}></p>
    <p className="left-resize" onMouseDown={ event => this.resizeStart(event, 'left')}></p>
    <p className="right-bottom-resize" onMouseDown={ event => this.resizeStart(event, 'right')}></p>
    <p className="left-top-resize" onMouseDown={ event => this.resizeStart(event, 'left')}></p>
   </p>
Copy after login

ImageUploader & Cropper

The main thing ImageUploader does is to upload images, listen to the input change event, and call the handleImgChange method of the parent component Cropper. This method Setting the imageValue bound to the img element will cause the img element to trigger the load event.

 handleImgChange = e => {
  let fileReader = new FileReader()
  fileReader.readAsDataURL(e.target.files[0])
  fileReader.onload = e => {
   this.setState({...this.state, imageValue: e.target.result})
  }
 }
Copy after login

The load event triggers Cropper's setSize method, which can set the initial position and size of the picture and cropping selection box. Currently, the default setting of the crop selection box is that the size is 80% of the image and is displayed in the middle.

 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'
  }
 }
Copy after login

There is also a getCropData method on Cropper, which will print and return the cropping parameters.

 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
 }
Copy after login

SelectArea

Replay the structure of selectArea. It should be noted that the cursor attribute of .top-resize is n-resize, and the corresponding ones for left, right, and bottom are w-resize, e-resize, and s-resize

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

The state value of selectArea is set to In this way, selectArea saves the parameters when dragging the selection box, resizeArea saves the parameters when cropping the selection box, container is the .image-principal element, and el is the event.target when the event is triggered.

  this.state = {
   selectArea: null,
   el: null,
   container: null,
   resizeArea: null
  }
Copy after login

Drag selection box

Press the mouse in .select-area, trigger the mouseDown event, and call the dragStart method.

Use the form of method = e => {} to avoid using this.method.bind(this) in jsx

In this method, first save the mouse when the mouse is pressed position, the relative distance between the cropping frame and the picture and the maximum displacement distance of the cropping frame, then add event listeners

 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)
 }
Copy after login

moveBind and stopBind come from the

 this.moveBind = this.move.bind(this)
 this.stopBind = this.stop.bind(this)
Copy after login

move method, and record new data during mouse movement. Use the mouse position to calculate the new relative positions newPosLeft and newPosTop, and control the values ​​within a reasonable range

 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'
 }
Copy after login

stop method, remove event listening, clear state, and avoid incorrect method calls

 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})
 }
Copy after login

Crop selection box

Same as dragging, first call the resizeStart method, save the mouse position when starting cropping, the size and position of the cropping box, and add event listeners about resizeBind and stopBind. Note, Due to the characteristics of react's event mechanism, stopPropagation needs to be used to prevent event bubbling. Using false as the third parameter of event listening is invalid.

 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)
 }
Copy after login

The cutting method divides cutting into two situations, one is stretching of the right side, lower side and lower right side. The other is the stretch on the left side, upper side and upper left side.

In the first case, the position of the selection box will not change, only the size will change, and the processing is relatively simple. The new size is the original size plus the current mouse position minus the position of the mouse at the beginning of dragging. If either the width or height exceeds the standard, the size is set to a size that just reaches the boundary. All exceed the standard and are set to new sizes.

In the second case, the position and size of the selection box will change at the same time, and the size and position must be controlled at the same time not to exceed the boundary.

 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'
   }
  }
 }
Copy after login

I believe you have mastered the method after reading the case in this article. For more exciting information, please pay attention to other related articles on the php Chinese website!

Recommended reading:

How to use vue to hide div

Summary of how to use the state object of vuex

The above is the detailed content of How to crop images in react. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template