Realize the front-end picture cropping function step by step

Table of contents

Implementation process

1. Upload and read image files

2. Picture display and mask processing

CSS clip-path

3. Display of cropping frame

The scale point of the crop box

cursor mouse style

Fourth, the cropping frame movement event

5. Cropping box scaling operation

Sixth, complete the cutting function

drawImage

postscript


In our front-end development, when we encounter the function of uploading pictures or avatars, if there is a size and resolution limit, we need to use the cropping function of the picture. The use of canvas makes it possible for us to directly realize the image cropping function on the web side.

This article will use the front-end technology to realize the cropping function of a picture. For the specific code, you can see the source code of picture cropping .

Implementation process

1. Upload and read image files

Use the file upload control to realize image upload. After obtaining the image file (File object), you can  complete the conversion of file data through two APIs, FileReader  or URL.createObjectURL . The front-end binary is described in detail in the previous article: Blob, File, FileReader, ArrayBuffer, TypeArray, DataView .

  • FileReader: Generally use the readAsDataURL method to read File as Base64 data of image files, which can be directly loaded as image data. Base64 knowledge can be seen in the previous article for an in-depth understanding of Base64 string encoding knowledge .
  • URL.createObjectURL: Generate a pseudo-protocol Blob-Url link, which is used here as an image URL link to load image resources.

As follows, in response to the file upload event, load the image with Base64 string data:

// 上传控件事件响应,加载图片文件
document.getElementById('input-file').onchange = (e) => {
  const file = e.target.files[0]
  const reader = new FileReader()
  reader.onload = async (event) => {
    initImageCut(event.target.result)
  }
  reader.readAsDataURL(file)
}

The data read in this way is the image Base64 string data, which can be  loaded by the Image object as an image resource.

2. Picture display and mask processing

After obtaining the data of the image file, load the image to obtain the pixel width and height:

const img = new Image()
img.src = dataUrl
img.onload = function () {
  resolve(img)
}

Generally, an img instance is generated through the Image  object, and the image data is loaded. The img instance contains the width and height of the image.

The width and height of the picture are important data, such as calculating the zoom ratio of the picture display area, and the subsequent drag and drop and zooming of the cropping box are also needed.

The following code calculates the zoom ratio (zoom):

zoom = Math.min(WIDTH / img.width, HEIGHT / img.height)

zoom = zoom > 1 ? 1 : zoom

Among them, WIDTH  and HEIGHT  are to set a fixed area to display pictures and cropping frames. The size of the value can be set arbitrarily, and it is best within the visible area of ​​the display;

The role of zoom can facilitate us to obtain the relative size of the picture later.

Next, you can display the picture on the page and set the masking process.

For the display of pictures, we directly use the <img>  tag of html here:

<img class="image" id="bgMaskImg"/>
<img class="image" id="cutBoxImg"/>

Two <img> tag elements are used here  , and the two elements load the same image resource, the difference is:

+ Where bgMaskImg  is used as the base map, set the transparency (such as 0.5) to simulate the mask effect;

+ cutBoxImg  is displayed as a picture of the cropping box area, that is, a clear picture without a mask layer.

Here is a picture showing the effect that needs to be achieved, as follows:

In the display above, the first img tag appears to have a mask effect;

The clear picture block in the middle area is the effect of the second img tag, which is  accomplished with the help of the clip-path attribute in CSS.

Then, you need to load images for the two img tag elements and set the style of each element:

bgMaskImgElm.src = cutBoxImgElm.src = imgUrl
setStyle()

CSS clip-path

clip-path  is a CSS property that can only display a part of the element, while other areas are hidden.

This feature can be used as a cropping function, and it can simulate the effect of cropping the mask layer by using it on the picture.

There was a clip attribute in CSS before, but it has been discarded. Although some browsers still support it, it is recommended to use clip-path.

The clip-path attribute has many values. We use its polygon value polygon to simulate a square clipping area:

img {
  clip-path: polygon(0 0, 100px 0, 100px 100px, 0 100px);
}

The above code uses pixel values ​​to locate the coordinates of the four vertices of the square (upper left, upper right, lower right, and lower left), and displays a square clipping area of ​​the image. At this time, other areas are invisible, forming a clear area in the above image display.

Because this cropping area will change as the cropping frame moves or zooms, it needs to be changed through JS:

const clipPath = 'polygon(...)'
cutBoxImgElm.style.clipPath = clipPath

After moving or dragging, recalculate the coordinate points of the clipping area, and then update the clip-path attribute.

3. Display of cropping frame

After the loading and display of the image, the processing of the mask layer and the cropping area are realized above, the next step is to realize the cropping frame.

The cropping box can be used in a div way, define a cropping box:

<div id="cutBox" class="cut-box" style="display: none;">
...
</div>

What needs to be noted here is to change the position and size of the cropping div through JS, and also change the clip-path attribute mentioned above synchronously  :

cutBoxElm.style.width = cutBoxWidth * zoom  - 2 + 'px'
cutBoxElm.style.height = cutBoxHeight * zoom  - 2 + 'px'
cutBoxElm.style.left = cutBoxLeft + 'px'
cutBoxElm.style.top = cutBoxTop + 'px'

The scale point of the crop box

For the display of the cropping frame, eight zoom points are generally designed, as shown in the following figure:

It should be noted that the approximate name of the zoom point is marked on the figure, and the event processing of the corresponding point will be involved in the following, so that it is clear which operation it is.

This kind of code is also better implemented with div, just use a small icon or CSS to draw a thick line, put it under the cutbox div of cutBox  , and follow the movement.

<div class="box-corner topleft" style="cursor:nw-resize;"></div>
<div class="box-corner topright" style="cursor:ne-resize;"></div>
<div class="box-corner bottomright" style="cursor:se-resize;"></div>
<div class="box-corner bottomleft" style="cursor:sw-resize;"></div>
<div class="box-middle topmiddle" style="cursor:n-resize;"></div>
<div class="box-middle bottommiddle" style="cursor:s-resize;"></div>
<div class="box-middle leftmiddle" style="cursor:w-resize;"></div>
<div class="box-middle rightmiddle" style="cursor:e-resize;"></div>

The above html code is the eight points defined, and with the corresponding css style, the desired effect can be achieved.

It should be noted that when the mouse style is changed, the zoom point here needs to display different mouse styles when the mouse hovers over it.

cursor mouse style

The cursor attribute mainly sets the type of the cursor. When using the mouse on the browser, different style icons of the mouse will be displayed.

Such as pointer  floating finger style, grab  gripper style, etc.

Eight scale-related mouse pointer styles are used in the crop box:

value describe
nw-resize、se-resize double left arrow
ne-resize、sw-resize double right arrow
n-resize、s-resize vertical double arrow
w-resize、e-resize horizontal double arrow

Fourth, the cropping frame movement event

After processing the structure and display of the picture, masking effect, and cropping frame, some operation events need to be added below.

The first thing to deal with is the movement event of the cropping frame, so that the cropping frame can be moved arbitrarily within the image area, using basic mouse events:

isMoveDown = false

cutBoxElm.addEventListener('mousedown', (event) => {
  isMoveDown = true
  const { offsetLeft, offsetTop } = cutBoxElm
  const disX = event.clientX - offsetLeft
  const disY = event.clientY - offsetTop

  document.onmousemove = (docEvent) => {
    const left = docEvent.clientX - disX
    const top = docEvent.clientY - disY
    if (isMoveDown) {
      //...
    }
    docEvent.preventDefault()
  }
  document.onmouseup = () => {
    isMoveDown = false
  }
})

Moving the cropping frame does not involve zooming, but the position information is updated synchronously with the mouse, so the focus is on calculating the offset position data of the mouse event and the cropping frame.

After calculating the positioning data, one more thing needs to be done, that is, the cropping frame cannot be separated from the image area, that is, it cannot be moved outside the image, which is invalid.

// 裁剪框 left 数据
cutBoxLeft = Math.max(0, Math.min(left, curImageWidth - curCutBoxWidth))
// 裁剪框 top 数据
cutBoxTop = Math.max(0, Math.min(top, curImageHeight - curCutBoxHeight))

The above code obtains the limit point of the cropping frame position by processing the width and height of the image and the width and height of the cropping frame.

5. Cropping box scaling operation

The zoom event of the cropping frame also needs to be bound. In the example of this article, by binding the respective events of the eight zoom points, it is still the same mouse event as moving the cropping frame:

// 是左上角的缩放点
document.querySelector('.topleft').addEventListener('mousedown', (event) => {
  reSizeDown('topleft', event)
})
// ...
// 其他点各自绑定

 The mousemove event is still processed in the reSizeDown function:

let isResizeDown = false
function reSizeDown (type, event) {
  isResizeDown = true
  document.onmousemove = (docEvent) => {
    const disX = docEvent.clientX - event.clientX
    const disY = docEvent.clientY - event.clientY
    if (isResizeDown) {
      let cutW = currentCutBoxWidth
      let cutH = currentCutBoxHeight
      switch (type) {
        case 'topleft':
          cutBoxLeft = Math.min(currentBoxLeft + (currentCutBoxWidth * zoom ) - 16, Math.max(0, currentBoxLeft + disX))
          cutBoxTop = Math.min(currentBoxTop + (currentCutBoxHeight * zoom ) - 16, Math.max(0, currentBoxTop + disY))
          const nwWidth = currentCutBoxWidth - (disX / zoom)
          const nwHeight = currentCutBoxHeight - (disY / zoom)
          cutW = +(cutBoxLeft > 0 ? nwWidth : (currentCutBoxWidth + currentBoxLeft / zoom)).toFixed(0)
          cutH = +(cutBoxTop > 0 ? nwHeight : (currentCutBoxHeight + currentBoxTop / zoom)).toFixed(0)
          break
        // case 'topright':
        // ...
        // 对每个缩放点进行处理
      }
      // ...
    }
  }
  document.onmouseup = () => {
    isResizeDown = false
  }
}

For the above code, take the scaling of the upper left corner as an example. When dragging the upper left corner to zoom, the position and width and height of the cropping frame will change, so these four values ​​(left, top, width, height) need to be calculated.

The position of the four corners of the cropping frame needs to be processed like this, but the scaling in the other four straight-line directions is relatively simple:

case 'leftmiddle':
  cutBoxLeft = Math.min(currentBoxLeft + (currentCutBoxWidth * zoom ) - 16, Math.max(0, currentBoxLeft + disX))
  const wWidth = currentCutBoxWidth - (disX / zoom)
  cutW = +(cutBoxLeft > 0 ? wWidth : (currentCutBoxWidth + currentBoxLeft / zoom)).toFixed(0)
  break

The above code only needs to calculate the left  offset and the width of the cropping box.

After having the position and width and height data, change the style attribute of the cropping frame and the clip-path attribute of the mask image in real time , and change synchronously, and the event processing of the cropping frame is basically completed.

Sixth, complete the cutting function

After the event is bound, the basic function of the crop frame has been completed, and the rest is to perform the final image cropping operation.

After the cropping frame is moved or scaled, we need to get the data of the current cropping frame: position, width and height data.

According to the position and width and height, you can use canvas to crop out the picture:

const left = cutBoxLeft / zoom
const top = cutBoxTop / zoom

const myCanvas = document.createElement('canvas')
const ctx = myCanvas.getContext('2d')
myCanvas.width = cutBoxWidth
myCanvas.height = cutBoxHeight

ctx.drawImage(imgObj, left, top, cutBoxWidth, cutBoxHeight, 0, 0, cutBoxWidth, cutBoxHeight)

As above code,

The position information was calculated before as relative position, and to restore to the original image, it needs to be restored by scaling; the width and height of the cropping frame have been restored before, so you can directly take the value here.

After the canvas  myCanvas  is successfully clipped and drawn, the required image will be obtained. You can directly display the canvas on the page, or export the canvas as image Base64 data or Blob-Url and then load it.

myCanvas.toBlob((blob) => {
  const url = URL.createObjectURL(blob)
}, 'image/jpeg')
// 或
myCanvas.toDataURL()

drawImage

drawImage is an API in canvas, which is used to handle various image operations. It has three syntaxes, which we use here for cropping:

context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

in,

  • The parameters sx, sy, sWidth, and sHeight can be understood as cutting the original image according to the position and width and height dimensions, and a new cropped image can be obtained;
  • The parameters dx, dy, dWidth, and dHeight are to draw the new picture cut above on the canvas according to the position, width and height. At this time, the new picture will be displayed on the canvas after cutting.

The following figure is the display effect of the interface with complete cropping function in the example:

The figure above shows the style modification of the  input tag, using the pseudo-element ::file-selector-button to change the style and beautify the upload control button. For details, please refer to the detailed explanation of the CSS pseudo-element and the difference between pseudo-elements and pseudo-classes .

postscript

After the above steps, a basic image cropping function based on front-end technology is completed.

Image cropping can also be processed in a variety of ways. The examples use the original tags and APIs for easy understanding.

Subsequent encapsulation of various components can also be carried out, regardless of vue, react, webcomponent, etc., can be quickly introduced for encapsulation.

Guess you like

Origin blog.csdn.net/jimojianghu/article/details/127620196