Canvas image segmentation effect

Keywords: Javascript less

Previously, when I was browsing CSS design awards, I found an effect of dividing the content of the picture (web address: https://weareludwig.com You can click in and have a look. It feels dazzling and cool. So you try to achieve it yourself. The effect is good. Effect View https://codepen.io/geeknoble/....

Analysis

First, we can find that the content of the picture is divided into a small rectangle, and each rectangle is randomly translated. The drawImage function of Canvas can cut the content of the picture and draw it into Canvas canvas, so the main principle of this effect is to use drawImage. There are two main effects, one is the disruption and restoration of image content, the other is the switching between the next image and the next one. Both effects can use drawImage, but the distance of movement is different. With the general idea, we can start to realize it.

Initial work

First, we initialize some variables, such as the width and height of the picture, the number of rectangles, the size of the cut, etc. Then we calculate the coordinates of each rectangle, and use a double loop to save the rectangular coordinates in data. Each rectangle has a random displacement, which also needs to be preserved in random. Where x,y denotes the coordinates of canvas canvas canvas, x1,y1 denotes the coordinates of image clipping.

init: function (context, width, height, area, img) {
            this.context = context;
            this.img = img;
            this.imgWidth = img[0].width;          //Picture width and height
            this.imgHeight = img[0].height;
            this.index = 0;                       //Current Picture Number
            this.width = width;                  //Canvas width and height
            this.height = height;
            this.area = height/12;                     //Small rectangular length
            this.countX = width / this.area;             //Small Rectangles in Horizontal and Vertical Directions
            this.countY = height / this.area;
            this.wx = this.imgWidth / this.countX;      //The width and height of the picture in a small rectangle
            this.wy = this.imgHeight / this.countY;
            this.state = true;                   //Picture status, true means unclassified
            this.dataFlag = true;                //Small rectangular coordinate states, true denotes the absence of random values
            this.duration = 1000;                 //Animation time
            this.duration2 = 1500;
            this.startTime = 0;
            this.data = [];                       //Small rectangular coordinate information
            this.randoms = [];                    //Random value of position
            //Initialize rectangular coordinates
            var x1 = 0, y1 = 0, x = 0, y = 0;
            for (var i = 0; i < this.countY; i++) {
                for (var j = 0; j < this.countX; j++) {
                    context.drawImage(this.img[this.index], x1, y1, this.wx, this.wy, x, y, this.area, this.area);
                    //Store rectangular coordinates
                    this.data.push({
                        x1: x1,
                        y1: y1,
                        x: x,
                        y: y
                    });
                    //Adding Random Values
                    this.randoms.push(random(-this.area, this.area));
                    x1 += this.wx;
                    x += this.area;

                }
                x1 = 0;
                y1 += this.wy;
                x = 0;
                y += this.area;
            }
            this.checkMargin();
        }

Edge Detection

Before adding displacement to the rectangle, we need to determine whether the coordinates after displacement exceed the picture boundary. For example, if the rectangle at the top moves on the Y axis, it can only move upward, if the current coordinate plus displacement value is less than 0 or larger than the width of the picture. If the updated coordinates are less than 0, then the random value must be negative. We need to change the random value to positive number. If the coordinates are higher than the height of the picture, then we can change it to negative number. Since every rectangle moves in one direction, I write here as an even displacement moving x-axis and an odd displacement moving y-axis.

//Edge Detection
        checkMargin: function () {
            var self = this;
            this.data.forEach(function (item, index) {
                if (index % 2 == 0) {  // Move the x-axis when the subscript is a multiple of 2, otherwise move the y-axis
                    if ( item.x1 + self.randoms[index] < 0)
                        // Change to positive
                        self.randoms[index] = -self.randoms[index];
                    if (item.x1 + self.wx + self.randoms[index] > self.imgWidth )
                        // Change to negative number
                        self.randoms[index] = -Math.abs(self.randoms[index])
                } else {
                    if (item.y1 + self.randoms[index] < 0)
                        self.randoms[index] = -self.randoms[index];
                    if (item.y1 + self.randoms[index] + self.wy > self.imgHeight)
                        self.randoms[index] = -Math.abs(self.randoms[index])
                }
            })
        }

Separation and recovery

The separation and restoration of animation content is to update the value of rectangular coordinates. If the content is disturbed, only the coordinates in the data are added with random values, and the restoration is to subtract the random values.

//Adjust rectangular coordinates
        update: function (val) {
            var self = this;

            if (val) {          //Reduction coordinates
                if (!this.dataFlag) {
                    this.data.forEach(function (item, index) {
                        if (index % 2 == 0) {
                            item.x1 -= self.randoms[index];
                        } else {
                            item.y1 -= self.randoms[index];
                        }
                    })
                }
                this.dataFlag = true;
            } else {      //Disturbing coordinates
                if (this.dataFlag) {
                    this.data.forEach(function (item, index) {
                        if (index % 2 == 0) {
                            item.x1 += self.randoms[index];
                        } else {
                            item.y1 += self.randoms[index];
                        }
                    })
                }
                this.dataFlag = false;
            }
        }

After storing the coordinates, we can realize the translation drawing. The movement process has a smooth transition. We can use the Tween.js slow motion algorithm. The algorithm has four parameters: the current time, the initial position, the end position and the animation time. Details can be found in this article by Zhang Xinxu. https://www.zhangxinxu.com/wo... . Through Tween.js, you can calculate the distance of each frame to move, and then use the request Animation Frame to update the coordinates.

blockAnimation: function () {
            var flag = 1;
            if (this.state) {  // Judge whether to scramble or restore the picture
                this.update(true)
            } else {
                flag = -1;
                this.update(false);
            }
            var self = this;
            this.startTime = +new Date();  // Get the current time

            this.state = !this.state;
            (function animation() {
                var t = +new Date();
                if (t >= self.startTime + self.duration) {  // Animation End Conditions
                    return false;
                }
                self.data.forEach(function (item, index) {
                    if (index % 2 == 0) {
                        var pos = Math.tween.Expo.easeInOut(t - self.startTime, 0, self.randoms[index] * flag, self.duration);   // Calculate the moving distance of each frame
                        self.context.drawImage(self.img[self.index], item.x1 + pos, item.y1, self.wx, self.wy, item.x, item.y, self.area, self.area);
                    } else {
                        var pos = Math.tween.Expo.easeInOut(t - self.startTime, 0, self.randoms[index] * flag, self.duration);  
                        self.context.drawImage(self.img[self.index], item.x1, item.y1 + pos, self.wx, self.wy, item.x, item.y, self.area, self.area);
                    }
                });
                requestAnimationFrame(animation);
            })();
        }

So far, the animation of separation and recovery has been realized.

Picture switching

Next, we begin to deal with the image switching part, which is a bit like the rotation map. The rotation map animation is to move the distance of the width of the visual window for each picture position. The same is true here. The switching on the y axis can be achieved by adding the coordinates and the height of the picture. Unlike the rotation chart, we only have one canvas tag. When switching, we only need to change the coordinates of the current graph and the next graph. The current graph has a moving distance of y1 + pos, and the next graph has a moving distance of y1 + pos - imgHeight.

//Vertical Sliding Animation
        verticalAnimation: function (val) {
            if (!this.time2) {
                return false;
            }
            this.checkTime(2);

            var self = this;
            val ? val = 1 : val = -1;  //Judging whether to go up or down
            if ((this.index + val) < 0 || (this.index + val) >= (this.img.length)) {   //Judging whether the serial number of the picture is in the end
                return false;
            }

            this.state ? this.update(true) : this.update(false);
            this.startTime = +new Date();

            (function animation() {
                var t = +new Date();
                if (t >= self.startTime + self.duration2) {
                    val === 1 ? self.index++ : self.index--;  //Adjust the order of pictures
                    self.index < 0 ? self.index = self.img.length - 1 : self.index;
                    self.index >= self.img.length ? self.index = 0 : self.index;
                    return false;
                }
                self.data.forEach(function (item) {
                    var pos = Math.tween.Cubic.easeInOut(t - self.startTime, 0, (self.imgHeight) * val, self.duration2);
                    // Update current picture coordinates
                    self.context.drawImage(self.img[self.index], item.x1, item.y1 + pos, self.wx, self.wy, item.x, item.y, self.area, self.area);
                    // Update the coordinates of the next picture
                    self.context.drawImage(self.img[self.index + val], item.x1, item.y1 + pos - self.imgHeight * val, self.wx, self.wy, item.x, item.y, self.area, self.area);

                });
                requestAnimationFrame(animation);
            })()
        }

The switch of the x-axis is the same. Now all the functions are almost complete. The complete code can be viewed in codepen.

Posted by 4evernomad on Sun, 28 Jul 2019 07:07:14 -0700