Implementation of H5 mobile terminal image clipping with canvas


Not long ago, we launched an H5 activity page on Chinese Valentine’s day. We need to do a local image clipping and uploading function to generate some specific confession images. Mainly used H5FileApiandCanvas. I feel that the image cutting function is very practical, so write an article to share.

Demo address:…
Effect GIF:
Implementation of H5 mobile terminal image clipping with canvas

PS: This demo was originally on the mobile side, but I ran it in chrome to make it easier to record.
Also, demo is a cooperationVueBecause I copied and modified it directly in the project, I’m too lazy to convert it.)

Picture reading

The first thing to cut is to read the image file. In fact, it’s very simple. Just use an input to specify the file type.

<input type="file" class="file" accept="image/*;capture=camera" name="img" @change="clipImg($event)">

//Methods in Vue
    this.clip = new Clip('container',this);
    this.isClip = true;

Then there is the processing of the obtained files

//These methods are written in the clip class

    let t = this;
    let reader = new FileReader();
    reader.onload = function() {
        t.imgUrl = this.result;

Create a new one firstFileReaderClass, and then use thereadAsDataURLConvert to Base64 encoding, and then pass thepaintImgMethod.

It should be noted that the file reading is asynchronous, so the operation after reading needs to be done in theonloadFunction.

Draw clipping interface


First, initialize some basic parameters

init(file){ = 0; // Initial X of clipping box = 0; // Initial y of clipping box
    this.sWidth = 233; // The width of the crop box
    this.sHeight = 175; // Height of crop box 
    this.chooseBoxScale = 233/175;// The aspect ratio of clipping box


Here, the ratio of the clipping box is fixed at 4:3, which can be changed by students if necessary. As for the width and height, first set a value according to the design draft, and then reset it according to different pictures.

Draw clipping interface

    //other code
    img.onload = function() {

        let imgScale = img.width / img.height;
        let boxScale = t.regional.offsetWidth / t.regional.offsetHeight;

        //Judge the comparison between the box and the picture
        if (imgScale < boxScale) {
            //Set the pixels of the picture
            t.imgWidth = t.regional.offsetHeight * imgScale;
            t.imgHeight = t.regional.offsetHeight;
        } else {
            //Set the pixels of the picture
            t.imgWidth = t.regional.offsetWidth;
            t.imgHeight = t.regional.offsetWidth / imgScale;

        //Judge the ratio between the picture and the selection box and cut it
        if (imgScale < t.chooseBoxScale) {
            //Sets the pixels of the selection box
            t.sWidth = t.imgWidth;
            t.sHeight = t.imgWidth / t.chooseBoxScale;

            //Sets the position of the initial box
   = 0;
   = (t.imgHeight - t.sHeight) / 2;
        } else {
            //Sets the pixels of the selection box
            t.sWidth = t.imgHeight * t.chooseBoxScale;
            t.sHeight = t.imgHeight;

   = (t.imgWidth - t.sWidth) / 2;
   = 0;

This is a long code.

Let’s talk about the structure of the clipping interface first. Looking at the demo and animation, we can see that in the clipping interface, there is first an outermost container containing pictures, that is, the ID iscontainerThe div of is called 1;
And then there’s the image container, where id isimage-boxCanvass of 2;
Finally, the fuzzy layer of the outermost hollowed out clipping area, i.e. ID iscover-boxIt’s called 3.

In these containers, the width and height of 1 are fixed. While 2 has one side occupying the whole 1 under the condition of keeping the proportion unchanged. So you can see that the above section of the code to judge the box and picture comparison is to realize this display mode.
At the same time, we can see that the clipping region of 3 is the same, and one side occupies the whole 2 with the same proportion. In other words, the proportion of the clipping area is designed in advance, which is 4:3 here.

So I put it in the renderingsHigh fill containerandFill the container wideI’ve demonstrated all the pictures.

Some treatment

//Continued (1)
    //The picture under the high score screen is blurred and needs to be processed twice
    t.getImage.height = 2 * t.imgHeight;
    t.getImage.width = 2 * t.imgWidth; = t.imgWidth + 'px'; = t.imgHeight + 'px';
    let vertSquashRatio = t.detectVerticalSquash(img);
    cxt.drawImage(img, 0, 0,2 * t.imgWidth * vertSquashRatio, 2 * t.imgHeight * vertSquashRatio)

The first one is the problem of image blur in high score screen. When drawing some images with canvas in high score screen, there will be blur. It is estimated that it is related to the drawing mechanism of canvas. The specific reasons are as followsPoke here. The solution is also relatively simple. Fix the width and height of canvas’s CSS, and double the width and height of the container( In my understanding, the width and height of CSS is the width and height of the canvas label style setting, and the width and height of the container is the width and height of the drawing board inside, not the same thing). After such processing, the blur problem of most pictures has been solved.

The second is the problem of image compression. When drawing more than 1m images in low version IOS models, there will be compression, flipping and other problems. Details and SolutionsPoke here. I just used the fix method in stackflow.

From these two problems, we can see that canvas drawing is not very mature, so we should pay attention to some bugs and related repair methods when using it.

Draw on move

    let t = this;
    let draging = false;

    //Record page X and page y of initial click. Used to record displacement
    let pageX = 0;
    let pageY = 0;

    //Initial displacement
    let startX = 0;
    let startY = 0;

    t.editBox.addEventListener('touchmove', function(ev) {
        let e = ev.touches[0];

        let offsetX = e.pageX - pageX;
        let offsetY = e.pageY - pageY;
        if (draging) {

            if (t.imgHeight == t.sHeight) {

       = startX + offsetX;

                if ( <= 0) {
           = 0;
                } else if ( >= t.imgWidth - t.sWidth) {
           = t.imgWidth - t.sWidth;
            } else {
       = startY + offsetY;

                if ( <= 0) {
           = 0;
                } else if ( >= t.imgHeight - t.sHeight) {
           = t.imgHeight - t.sHeight;
    t.editBox.addEventListener('touchstart', function(ev) {
        let e = ev.touches[0];
        draging = true;

        pageX = e.pageX;
        pageY = e.pageY;

        startX =;
        startY =;

    t.editBox.addEventListener('touchend', function() {
        draging = false;

The logic here is not too complicated. First, record the initial position, then judge whether it is moving left and right or up and down (corresponding to high filling container and wide filling container), and then judge the position quantity according to pagex and pagey.

Save picture

    let t = this;
    let saveCanvas = document.createElement('canvas');
    let ctx = saveCanvas.getContext('2d');

    //The size of the cut image
    saveCanvas.width = 466;
    saveCanvas.height = 350;

    let images = new Image();
    images.src = t.imgUrl;

    images.onload = function(){

        //Calculate the clipping size scale, which is used to clip the picture
        let cropWidthScale = images.width/t.imgWidth;
        let cropHeightScale = images.height/t.imgHeight;

        t.drawImageIOSFix(ctx, images,cropWidthScale * , cropHeightScale*,
                        t.sWidth * cropWidthScale, t.sHeight * cropHeightScale, 0, 0, 466, 350);
    //    ctx.drawImage(images,2 *, 2 *, t.sWidth * 2, t.sHeight * 2, 0, 0, 466, 350);
        t.$vm.clipUrl = saveCanvas.toDataURL();

This part is also very simple. In the clipping box, the coordinates of the beginning and end of clipping are recorded, and then a new canvas is created to cut it out and use thetoDataURLMethod is converted to Base64 encoding and can be transferred to the background. The cut size here is fixed, which is business requirement and can be changed if necessary


This is the general process. Thank you for your reading. If you have any mistakes, please forgive me.

Recommended Today

Go cannot find package “go-sql-driver/mysql” in any of

The solution of cannot find package “GitHub. COM / go SQL driver / MySQL” in any of The main problem is lack of informationgo.modDocuments.To solve the problem, you need to initialize ago.modDocuments; Use the following command: go mod init Gone This work adoptsCC agreementReprint must indicate the author and the link of this article