简体   繁体   中英

how to make circle to rectangle using javascript canvas

hi guys i'm starting to learn about canvas using javascript. I have a question if there is any way to transform a circle to rectangle smoothly

something like this??1

Morphing shapes.

The easiest method is to create two reference shapes and tween between the two shapes.

As a rectangle and circle are created from lines, and arcs respectively it is not easy to create a square from circles. However you can create a circle from many small lines.

The example/demo snippet implements the following methods

Tweening

Tweening is a method of interpolating between two states.

The amount that we shift between the two states is defined by a unit value (a value from 0 to 1).

If the value is 0 the we are in the start state, if 1 then in the end state, values between are a fraction between the two end states.

The function that does the Tweening is called tweenPaths(ctx, pathA, pathB, pod)

Create the shapes

To get the correct look the points on start shape must be as close as possible to the points on the end shape. You also need to consider things like corners making sure you have a point on both shapes that will define a corner on one of the shapes.

The circle

The function circle(r, points) creates a circle out of a set of line segments. r is radius and points is the number of points

The square

The function square(size, points) starts by calculating a point on the circle and then pushes that point out from the center to where it would be on the square.

size is the width and height and points is the number of points

Points, Corners, position

Note that both circle and square MUST have the same number of points. To get the corners of the square the number of points needs to be divisible by 4

Note both shapes are centered on coordinate 0, 0. To draw at a specific point the 2D context transform sets the center (last two values).

ctx.setTransform(1,0,0,1, ctx.canvas.width * 0.5, ctx.canvas.height * 0.5);

In this case that is the center of the canvas.

Example

The code below tweens between a circle and square.

The function eCurve eases in and out the tweening value.

To animate the process the example uses requestAnimationFrame to control the timing and rendering of the animation

 function circle(r, points) { var i = 0; const path = []; while (i < points) { const a = (i++ / points) * Math.PI * 2; path.push({x: Math.cos(a) * r, y: Math.sin(a) * r}); } return path; } function square(size, points) { var i = 0; const path = []; while (i < points) { const a = (i++ / points) * Math.PI * 2; const x = Math.cos(a), y = Math.sin(a); const l = Math.abs(x) - Math.abs(y) > 0? (size * 0.5) / x * Math.sign(x): (size * 0.5) / y * Math.sign(y); path.push({x: x * l, y: y * l}) } return path; } function tweenPaths(ctx, pathA, pathB, pos) { // pos = 0 PathA, pos = 1 PathB var i = 0; ctx.beginPath(); while (i < pathA.length) { const A = pathA[i]; const B = pathB[i]; const x = (Bx - Ax) * pos + Ax; const y = (By - Ay) * pos + Ay; ctx.lineTo(x, y); i++; } ctx.closePath(); ctx.stroke(); } const eCurve = (v, p = 2, vp) => v < 0? 0: v > 1? 1: (vp = v ** p) / (vp + (1 - v) ** p); const ctx = canvas.getContext("2d"); const size = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.4; const cir = circle(size, 200); const sqr = square(size * 2, 200); var tween = 0; var tweenDir = 0.01; requestAnimationFrame(animate); function animate() { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); tween += tweenDir; if (tweenDir < 0 && tween <= 0) { tweenDir = -tweenDir; tween = 0; } else if (tweenDir > 0 && tween >= 1) { tweenDir = -tweenDir; tween = 1; } ctx.lineWidth = 4; ctx.setTransform(1,0,0,1, ctx.canvas.width * 0.5, ctx.canvas.height * 0.5); tweenPaths(ctx, cir, sqr, eCurve(tween)); requestAnimationFrame(animate); }
 <canvas id="canvas" width="200" height="200"></canvas>

The Canvas API allows the use of path-functions similar to SVG's <path> d -attribute .
If you have ever worked with SVG and CSS, then transforming a square to a circle seems like an easy task:

 svg { width: 100px; height: 100px; }
 <svg viewBox="0 0 100 100" xmlns="http://w3.org/2000/svg" fill="transparent" stroke="black"> <rect x="5" y="5" width="90" height="90" rx="0"> <animate attributeName="rx" values="0;45;0" dur="2s" repeatCount="indefinite" /> </rect> </svg>

Here, we have a <rect> -element, whose rx -attribute is animated, from 0 to half its size. This results in a form, that looks like its transforming from a circle to a square.

As any form is made up from lines, curves and other geometric forms, it can be modelled using SVG's <path> -element and its d -attribute.

The Canvas API includes a class called Path2D , which behaves exactly like SVG's <path> -element, just purpose-built for the canvas.
The Canvas API also includes path-functions to programmatically define the path to draw/fill.

Either Path2D or the path-functions may be used to achieve a transforming square-to-circle form.

Example

As we are going to implement it using JavaScript, we can define our own custom animation-curve, here in calcMutliplier() .

The path to draw is defined in render() .

The animation is depending on how much time has passed. The objects are informed of passed time by calling update() with dt as its argument, containing the passed time in milliseconds.

 const canvas = document.querySelector('canvas'); const ctx = canvas.getContext('2d'); class SquareCircleForm { x; y; size; cycleLength; progress = 0; constructor(x = 0, y = 0, size = 0, cycleLength = 0) { this.x = x; this.y = y; this.size = size; this.cycleLength = cycleLength; } calcMultiplier() { return Math.abs((this.progress / this.cycleLength) * 2 - 1); } update(dt) { this.progress = (this.progress + dt) % this.cycleLength; } render(ctx) { const mult = this.calcMultiplier(); ctx.save(); ctx.strokeStyle = 'black'; ctx.translate(this.x, this.y); ctx.beginPath(); ctx.moveTo(0, -this.size / 2); // Start point ctx.lineTo(this.size / 2 * mult, -this.size / 2); // Top edge - right half ctx.arcTo( // Top-right corner this.size / 2, -this.size / 2, this.size / 2, this.size / 2, this.size / 2 * (1 - mult) ); ctx.lineTo(this.size / 2, this.size / 2 * mult); // Right edge ctx.arcTo( // Bottom-right corner this.size / 2, this.size / 2, -this.size / 2, this.size / 2, this.size / 2 * (1 - mult) ); ctx.lineTo(-this.size / 2 * mult, this.size / 2); // Bottom edge ctx.arcTo( // Bottom-left corner -this.size / 2, this.size / 2, -this.size / 2, -this.size / 2, this.size / 2 * (1 - mult) ); ctx.lineTo(-this.size / 2, -this.size / 2 * mult); // Left edge ctx.arcTo( // Top-left corner -this.size / 2, -this.size / 2, this.size / 2, -this.size / 2, this.size / 2 * (1 - mult) ); // Top edge - left half not needed; `ctx.closePath()` connects start with end ctx.closePath(); ctx.stroke(); ctx.restore(); } } class SquareCircleForm2 extends SquareCircleForm { constructor(x, y, size, cycleLength) { super(x, y, size, cycleLength); this.progress = this.cycleLength / 4; } calcMultiplier() { return Math.sin(2 * Math.PI * this.progress / this.cycleLength) / 2 + 0.5; } } const forms = [] // Using linear transformation forms.push(new SquareCircleForm(canvas.width / 4, canvas.height / 2, 100, 2000)); // Using sine-wave transformation forms.push(new SquareCircleForm2(canvas.width * 3 / 4, canvas.height / 2, 100, 2000)); const framesPerSecond = 30; let start = Date.now(); setInterval(() => { const dt = Date.now() - start; // Reset canvas ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Update and draw forms forms.forEach(f => f.update(dt)); forms.forEach(f => f.render(ctx)); start = Date.now(); }, 1000 / framesPerSecond);
 canvas {border: 1px solid black}
 <canvas></canvas>

Here, the left form animates linearly, and the right by sine-wave.

End note

This answer is posted to offer another way of implementing your desired result.
However, I think Blindman67's implementation is, for most cases, better suited.

His example animates differently from mine, which is due to him positioning the square-points on the radial lines.

To achieve the same effect as in my example, you would have to create forms of n points, with n being divisible by 12. Then, you would have to place n / 4 points in each of the corners, in the same direction in which you placed the circle-points, meaning clock- or counter-clockwise.
Then, it would be a simple matter of just using his tweenPaths() function.

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