简体   繁体   中英

HTML5 Canvas shape from circle to triangle

I've been looking around and I can't seem to find a clear way in which I can animate a shape to change from a circle to a triangle or to a rectangle or the other way around. I would assume that I could somehow store the shape and change its attributes in order to convert it.

Basically what I am asking is, how can I draw a circle and then animate it to a triangle on the click of a button? Is that possible with canvas shapes?

Thanks!

Here's one approach you can use to animate a circle to-and-from any regular polygon.

在此输入图像描述在此输入图像描述在此输入图像描述

Create your triangle (or any regular polygon) using quadratic curves for each side.

That way you can animate the quadratic curve's control point to control the "roundness" of your polygon.

If the control point is on the polygon line, then the curve has zero roundness and the result is your polygon.

If the control point is appropriately outside the polygon line, then the curve approximates the roundness of a circle and the result is your polygon looks like a circle.

To "morph" between circle-polygon, you just animate the control point of the curve.

I use "approximates" to describe the circle because quadratic curves can only approximate a circle. If you want an even better circle, you can refactor my code to use cubic Bezier curves instead of quadratic curves.

Here's example code and a Demo: http://jsfiddle.net/m1erickson/NMJ58/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // change sideCount to the # of poly sides desired
    //
    var sideCount=3;


    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    ctx.lineWidth=2;
    ctx.fillStyle=randomColor();

    var PI2=Math.PI*2;
    var cx=150;
    var cy=150;
    var radius=100;

    var xx=function(a){return(cx+radius*Math.cos(a));}
    var yy=function(a){return(cy+radius*Math.sin(a));}
    var lerp=function(a,b,x){ return(a+x*(b-a)); }

    var sides=[];
    for(var i=0;i<sideCount;i++){
        sides.push(makeSide(i,sideCount));
    }

    var percent=0;
    var percentDirection=0.50;

    $("#toShape").click(function(){
        percentDirection=-0.50;
    })

    $("#toCircle").click(function(){
        percentDirection=0.50;
    })

    animate();

    // functions

    function animate(){
        requestAnimationFrame(animate);
        drawSides(percent);
        percent+=percentDirection;
        if(percent>100){percent=100;}
        if(percent<0){percent=0;}
    }

    function drawSides(pct,color){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        if(pct==100){
            ctx.beginPath();
            ctx.arc(cx,cy,radius,0,PI2);
            ctx.closePath();
            ctx.fill();
        }else{
            ctx.beginPath();
            ctx.moveTo(sides[0].x0,sides[0].y0);
            for(var i=0;i<sideCount;i++){
                var side=sides[i];
                var cpx=lerp(side.midX,side.cpX,pct/100);
                var cpy=lerp(side.midY,side.cpY,pct/100);        
                ctx.quadraticCurveTo(cpx,cpy,side.x2,side.y2);
            }
            ctx.fill();
        }
    }

    function makeSide(n,sideCount){
        var sweep=PI2/sideCount;
        var sAngle=sweep*(n-1);
        var eAngle=sweep*n;

        var x0=xx(sAngle);
        var y0=yy(sAngle);
        var x1=xx((eAngle+sAngle)/2);
        var y1=yy((eAngle+sAngle)/2);
        var x2=xx(eAngle);
        var y2=yy(eAngle);

        var dx=x2-x1;
        var dy=y2-y1;
        var a=Math.atan2(dy,dx);
        var midX=lerp(x0,x2,0.50);
        var midY=lerp(y0,y2,0.50);
        var cpX=2*x1-x0/2-x2/2;
        var cpY=2*y1-y0/2-y2/2;

        return({
            x0:x0, y0:y0,
            x2:x2, y2:y2,
            midX:midX, midY:midY,
            cpX:cpX, cpY:cpY,
            color:randomColor()
        });
    }

    function randomColor(){ 
        return('#'+Math.floor(Math.random()*16777215).toString(16));
    }

}); // end $(function(){});
</script>
</head>
<body>
    <button id="toShape">Animate to Shape</button>
    <button id="toCircle">Animate to Circle</button><br>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

You might want to use Raphael ( https://dmitrybaranovskiy.github.io/raphael/ ) or a similar library to take care of this for you. The raphael site contains many examples similar to the case you describe.

It is hard to understand your exact aim and what kind of animation you need between shapes as you don't specify animation steps/states. But you could take a look at CSS3 transitions .

I made a few in the following demo that illustrate how you can simulate "shape morphing" (is this the corect term?) if that is what you are trying to do :

DEMO (click on the circles to animate them)

For the circle to rectangle, the transition is pretty easy to achieve, you just need to animate border-radius :

CSS :

#rec {
    float:left;
    width:200px;
    height:200px;
    background:gold;
    border-radius:50%;
    -webkit-transition: border-radius 1s, width 1s;
    cursor:pointer;
}
#rec.animate {
    border-radius:0%;
    width:300px;
}

For the circle to triangle, the shape transition is a bit harder to achieve, the shape morphing isn't perfect and needs improvement but you can get the idea.

The circle and the triangle are set with pseudo elements and animations display one or the other with 2 different transitions :

CSS :

/*CIRCLE TO TRIANGLE 1  */
#tr1{
    float:right;
    position:relative;    
    cursor:pointer;    
    width:200px;
    height:200px;
}
#tr1:before, #tr1:after{
    content:'';
    position:absolute;
}
#tr1:before{
    top:0; left:0;
    background:gold;
    border-radius:50%;
    width:100%;
    height:100%;
    -webkit-transition: width 1s, height 1s, left 1s, top 1s;
}
#tr1:after{
    left:50%;
    top:50%;
    border-left:0px solid transparent;
    border-right:0px solid transparent;
    border-bottom:0px solid gold;
    -webkit-transition: border-left 1s, border-right 1s, border-bottom 1s,left 1s, top 1s;
}
#tr1.animate:before{
    width:0;
    height:0;
    top:50%; left:50%;
}
#tr1.animate:after{
    top:0; left:0;
    border-left:100px solid transparent;
    border-right:100px solid transparent;
    border-bottom:200px solid gold;
}

/*CIRCLE TO TRIANGLE 2  */

#tr2{
    float:right;
    position:relative;    
    cursor:pointer;    
    width:200px;
    height:200px;
}
#tr2:before, #tr2:after{
    content:'';
    position:absolute;
}
#tr2:before{
    top:0; left:0;
    background:gold;
    border-radius:50%;
    width:100%;
    height:100%;
    -webkit-transition: width 1s, height 1s, left 1s, top 1s;
}
#tr2:after{
    left:20px;
    top:0;
    border-left:80px solid transparent;
    border-right:80px solid transparent;
    border-bottom:160px solid gold;
    -webkit-transition: border-left 1s, border-right 1s, border-bottom 1s,left 1s, top 1s;
    z-index:-1;
}
#tr2.animate:before{
    width:0;
    height:0;
    top:50%; left:50%;
}
#tr2.animate:after{
    top:0; left:0;
    border-left:100px solid transparent;
    border-right:100px solid transparent;
    border-bottom:200px solid gold;
}

( I will award @rje because he got me in the right direction and after all the effort I realized that my question wasn't detailed enough with the specifics of my issue ).

First I will explain what I was actually looking for: Basically when creating a "shape" in a canvas context it's pretty easy to use that as a mask, by mask I mean, let's say you draw a circle in the middle of the canvas which remains transparent but the canvas is filled with a certain color. Using raphael js directly is kind of strange to do this until you get the hang of it because it's actually svg and there are different rules. You have to draw a outer path which will match the paper ( the raphael "canvas" ) and an inner path which will remain transparent. The challenge for me was to make it also responsive But I managed to do it after all using something like this

var tw; // canvas width
var th; // canvas height
var tr; // radius of circle, in my case calculated dynamically smth like (th - 20) / 2
var outerpath = "M0,0L" + tw + ",0L" + tw + "," + th + "L0," + th + "L0,0z";
var innerpath = "M" + (tw/2) + "," + (th/2 - tr) + "A" + tr + "," + tr + "  0 1,0 " + (tw/2 + 0.1) + "," + (th/2 - tr) + "z";
var path = outerpath + innerpath;
var mask = paper.path(path).attr({'stroke': 0, 'fill' :  '#ccc', 'fill-opacity' : 0.9});

The great thing about this is that using Raphael you can animate them and also use for example innerpath for creating another shape and use another color to fill that. Amazing.

Thanks everybody.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM