Search code examples
javascriptcanvashtml5-canvaspaperjs

Transform Rounded Rectangle to Circle


I've been working on a specific animation in which I need to convert(with animation) a Rounded Rectangle Shape to Circle. I've checked the documentation of paper.js and haven't found any predefined function to achieve this.

From this shape --> To This shape

The animation needs to be smooth. As the number of rectangles I'm working with is very high, I can't use the "remove current rounded rect and redraw one more rounded version" method. It reduces the performace and the animation gets laggy.

This is the code I'm using to generate rounded rectangle.

// Had to paste something to post the question
// Though the whole code can be seen on codepen link
var rect = new Rectangle();
var radius = 100, origin = {x: 100, y: 100};
rect.size = new Size(radius, radius);
rect.center = new Point(origin.x, origin.y);
var cornerSize = radius / 4;
var shape = new Path.Rectangle(rect, cornerSize);

Prepared this Codepen example to show the progress.

If we can work out the whole animation using any other object types, that will be fine too. For now I can't find any any property which can transform the rounded rectangle to circle.

I'm also animating color of the object and position. I've gone through many documents to find out color animation.

PS: If there is any other(better) technique to animate colors of object, please share that too.


Solution

  • You will first have to create a path as a rounded rectangle. Then with each step in your animation you have to modify the eight segments of the path. This will only work with Path objects, not if your rectangle is a Shape.

    The segment points and the handles have to be set like this: rounded rect point and handle position

    κ (kappa) is defined in paper.js as Numerical.KAPPA (more on Kappa here).

    The code to change the radius could look like this (Click here for the Sketch):

    var rect = new Path.Rectangle(new Point(100, 100), new Size(100, 100), 30);
    rect.fullySelected = true;
    
    var step = 1;
    var percentage = 0;
    
    function onFrame(event) {
        percentage += step;
        setCornerRadius(rect, percentage)
        if (percentage > 50 || percentage < 0) {
            step *= -1;
        }
    }
    
    function setCornerRadius(rectPath, roundingPercent) {
        roundingPercent = Math.min(50, Math.max(0, roundingPercent));
        var rectBounds = rectPath.bounds;
        var radius = roundingPercent/100 * Math.min(rectBounds.width, rectBounds.height);
        var handleLength = radius * Numerical.KAPPA;
    
        l = rectBounds.getLeft(),
        t = rectBounds.getTop(),
        r = rectBounds.getRight(),
        b = rectBounds.getBottom();
    
        var segs = rectPath.segments;
        segs[0].point.x = segs[3].point.x = l + radius;
        segs[0].handleOut.x = segs[3].handleIn.x = -handleLength;
        segs[4].point.x = segs[7].point.x = r - radius;
        segs[4].handleOut.x = segs[7].handleIn.x = handleLength;
        segs[1].point.y = segs[6].point.y = b - radius;
        segs[1].handleIn.y = segs[6].handleOut.y = handleLength;
        segs[2].point.y = segs[5].point.y = t + radius;
        segs[2].handleOut.y = segs[5].handleIn.y = -handleLength;
    }
    



    Edit: I just found a much easier way using a shape. Not sure which approach performs faster.

    Here is the implementation using a Shape (Click here for the Sketch).

    var size = 100;
    var rect = new Shape.Rectangle(new Rectangle(new Point(100, 100), new Size(size, size)), 30);
    rect.strokeColor = "red";
    
    var step = 1;
    var percentage = 0;
    
    function onFrame(event) {
        percentage =  Math.min(50, Math.max(0, percentage + step));
        rect.radius = size * percentage / 100;
        if (percentage >= 50 || percentage <= 0) {
            step *= -1;
        }
    }