Draw a Circle Over a Square in Javascript

Affiliate 17Cartoon on Canvas

Drawing is deception.

Picture of a robot arm drawing on paper

Browsers requite united states several ways to brandish graphics. The simplest mode is to use styles to position and color regular DOM elements. This can get you quite far, as the game in the previous affiliate showed. By calculation partially transparent background images to the nodes, we can make them look exactly the way we want. It is fifty-fifty possible to rotate or skew nodes with the transform way.

Simply we'd be using the DOM for something that information technology wasn't originally designed for. Some tasks, such equally cartoon a line between arbitrary points, are extremely awkward to do with regular HTML elements.

In that location are two alternatives. The start is DOM-based but utilizes Scalable Vector Graphics (SVG), rather than HTML. Think of SVG as a certificate-markup dialect that focuses on shapes rather than text. You tin embed an SVG document direct in an HTML certificate or include it with an <img> tag.

The second alternative is called a canvas. A canvas is a unmarried DOM element that encapsulates a picture. Information technology provides a programming interface for drawing shapes onto the space taken up by the node. The principal difference between a canvas and an SVG picture is that in SVG the original description of the shapes is preserved and then that they tin exist moved or resized at any time. A canvass, on the other paw, converts the shapes to pixels (colored dots on a raster) as soon as they are drawn and does not think what these pixels represent. The but way to move a shape on a sheet is to clear the canvas (or the part of the sail around the shape) and redraw information technology with the shape in a new position.

SVG

This book will not go into SVG in detail, just I will briefly explicate how it works. At the cease of the chapter, I'll come back to the merchandise-offs that you must consider when deciding which drawing machinery is appropriate for a given application.

This is an HTML document with a simple SVG picture in information technology:

          <          p          >Normal HTML here.</          p          >          <          svg          xmlns="http://www.w3.org/2000/svg"          >          <          circumvolve          r="50"          cx="l"          cy="50"          fill up="ruby"          />          <          rect          ten="120"          y="5"          width="90"          summit="90"          stroke="blueish"          fill="none"          />          </          svg          >        

The xmlns attribute changes an element (and its children) to a different XML namespace. This namespace, identified past a URL, specifies the dialect that nosotros are currently speaking. The <circle> and <rect> tags, which do not exist in HTML, do have a meaning in SVG—they draw shapes using the fashion and position specified by their attributes.

These tags create DOM elements, just like HTML tags, that scripts can interact with. For instance, this changes the <circle> element to be colored cyan instead:

          let          circle          =          document.querySelector("circle");          circle.setAttribute("fill up",          "cyan");

The sheet element

Sail graphics can exist drawn onto a <canvas> chemical element. You can requite such an element width and height attributes to decide its size in pixels.

A new canvas is empty, significant it is entirely transparent and thus shows upwards as empty infinite in the document.

The <canvass> tag is intended to let unlike styles of drawing. To become access to an actual drawing interface, we first need to create a context, an object whose methods provide the drawing interface. There are currently two widely supported cartoon styles: "2d" for ii-dimensional graphics and "webgl" for three-dimensional graphics through the OpenGL interface.

This volume won't talk over WebGL—we'll stick to ii dimensions. But if you are interested in three-dimensional graphics, I practice encourage yous to look into WebGL. It provides a direct interface to graphics hardware and allows you to render even complicated scenes efficiently, using JavaScript.

Yous create a context with the getContext method on the <sail> DOM element.

          <          p          >Before canvas.</          p          >          <          canvas          width="120"          acme="sixty"          >          </          canvas          >          <          p          >After canvas.</          p          >          <          script          >          permit          canvas          =          document.querySelector("canvass");          let          context          =          sail.getContext("2d");          context.fillStyle          =          "red";          context.fillRect(10,          10,          100,          l);          </          script          >        

After creating the context object, the example draws a red rectangle 100 pixels broad and fifty pixels high, with its top-left corner at coordinates (10,10).

But like in HTML (and SVG), the coordinate system that the sail uses puts (0,0) at the top-left corner, and the positive y-axis goes down from in that location. And then (x,10) is 10 pixels beneath and to the correct of the peak-left corner.

Lines and surfaces

In the canvas interface, a shape can exist filled, pregnant its expanse is given a certain color or design, or it can be stroked, which means a line is drawn along its edge. The same terminology is used by SVG.

The fillRect method fills a rectangle. It takes start the ten- and y-coordinates of the rectangle's tiptop-left corner, then its width, and and so its top. A similar method, strokeRect, draws the outline of a rectangle.

Neither method takes any further parameters. The colour of the make full, thickness of the stroke, and so on, are not determined past an statement to the method (as you might reasonably await) but rather past properties of the context object.

The fillStyle property controls the way shapes are filled. Information technology can be set to a string that specifies a color, using the color notation used by CSS.

The strokeStyle belongings works similarly but determines the colour used for a stroked line. The width of that line is determined by the lineWidth property, which may comprise any positive number.

          <          canvas          >          </          canvas          >          <          script          >          permit          cx          =          document.querySelector("sail").getContext("second");          cx.strokeStyle          =          "blue";          cx.strokeRect(5,          5,          50,          50);          cx.lineWidth          =          5;          cx.strokeRect(135,          v,          l,          l);          </          script          >        

When no width or height aspect is specified, as in the example, a canvass element gets a default width of 300 pixels and height of 150 pixels.

Paths

A path is a sequence of lines. The 2D sheet interface takes a peculiar arroyo to describing such a path. It is done entirely through side effects. Paths are not values that tin can be stored and passed around. Instead, if you lot want to exercise something with a path, you make a sequence of method calls to depict its shape.

          <          sail          >          </          sail          >          <          script          >          allow          cx          =          document.querySelector("canvas").getContext("2d");          cx.beginPath();          for          (allow          y          =          x;          y          <          100;          y          +=          x) {          cx.moveTo(10,          y);          cx.lineTo(90,          y);   }          cx.stroke();          </          script          >        

This example creates a path with a number of horizontal line segments and so strokes it using the stroke method. Each segment created with lineTo starts at the path'due south current position. That position is usually the terminate of the final segment, unless moveTo was chosen. In that case, the side by side segment would start at the position passed to moveTo.

When filling a path (using the fill method), each shape is filled separately. A path can contain multiple shapes—each moveTo motion starts a new ane. But the path needs to be closed (significant its start and terminate are in the same position) before information technology can be filled. If the path is not already airtight, a line is added from its terminate to its start, and the shape enclosed by the completed path is filled.

          <          canvas          >          </          canvas          >          <          script          >          permit          cx          =          document.querySelector("sail").getContext("2d");          cx.beginPath();          cx.moveTo(50,          ten);          cx.lineTo(10,          seventy);          cx.lineTo(xc,          70);          cx.fill();          </          script          >        

This case draws a filled triangle. Annotation that simply two of the triangle's sides are explicitly drawn. The third, from the bottom-right corner back to the top, is implied and wouldn't be in that location when you stroke the path.

You could likewise utilise the closePath method to explicitly close a path by calculation an actual line segment back to the path's start. This segment is fatigued when stroking the path.

Curves

A path may also comprise curved lines. These are unfortunately a flake more involved to draw.

The quadraticCurveTo method draws a curve to a given betoken. To determine the curvature of the line, the method is given a control point as well as a destination betoken. Imagine this control signal as attracting the line, giving it its bend. The line won't go through the control point, but its direction at the beginning and end points volition be such that a straight line in that direction would point toward the control indicate. The following example illustrates this:

          <          canvas          >          </          canvas          >          <          script          >          permit          cx          =          certificate.querySelector("canvas").getContext("2d");          cx.beginPath();          cx.moveTo(10,          90);             cx.quadraticCurveTo(60,          10,          90,          90);          cx.lineTo(60,          10);          cx.closePath();          cx.stroke();          </          script          >        

We draw a quadratic curve from the left to the correct, with (60,10) equally control point, and then describe two line segments going through that control signal and back to the first of the line. The issue somewhat resembles a Star Trek insignia. You tin see the result of the control point: the lines leaving the lower corners start off in the direction of the control point and then curve toward their target.

The bezierCurveTo method draws a similar kind of curve. Instead of a single control point, this one has two—1 for each of the line's endpoints. Here is a similar sketch to illustrate the behavior of such a bend:

          <          canvas          >          </          sail          >          <          script          >          permit          cx          =          document.querySelector("canvass").getContext("2nd");          cx.beginPath();          cx.moveTo(10,          90);             cx.bezierCurveTo(10,          10,          ninety,          10,          l,          90);          cx.lineTo(90,          x);          cx.lineTo(x,          10);          cx.closePath();          cx.stroke();          </          script          >        

The ii control points specify the direction at both ends of the curve. The farther they are away from their respective point, the more the bend volition "bulge" in that direction.

Such curves can be hard to piece of work with—it'south not always clear how to find the control points that provide the shape you lot are looking for. Sometimes you can compute them, and sometimes you'll just have to discover a suitable value by trial and error.

The arc method is a mode to depict a line that curves along the border of a circumvolve. Information technology takes a pair of coordinates for the arc's center, a radius, and then a kickoff angle and end angle.

Those last ii parameters brand it possible to draw simply part of the circle. The angles are measured in radians, non degrees. This means a full circle has an bending of 2π, or 2 * Math.PI, which is about vi.28. The angle starts counting at the bespeak to the right of the circle'southward center and goes clockwise from in that location. Yous tin can use a start of 0 and an terminate bigger than 2π (say, vii) to draw a full circle.

          <          canvas          >          </          canvas          >          <          script          >          let          cx          =          document.querySelector("canvas").getContext("2d");          cx.beginPath();             cx.arc(50,          50,          40,          0,          vii);             cx.arc(150,          fifty,          40,          0,          0.v          *          Math.PI);          cx.stroke();          </          script          >        

The resulting picture contains a line from the correct of the full circumvolve (beginning phone call to arc) to the correct of the quarter-circumvolve (second telephone call). Like other path-drawing methods, a line drawn with arc is connected to the previous path segment. You tin telephone call moveTo or start a new path to avoid this.

Drawing a pie nautical chart

Imagine yous've only taken a task at EconomiCorp, Inc., and your first consignment is to draw a pie nautical chart of its customer satisfaction survey results.

The results bounden contains an assortment of objects that represent the survey responses.

          const          results          =          [   {name:          "Satisfied",          count:          1043,          color:          "lightblue"},   {name:          "Neutral",          count:          563,          color:          "lightgreen"},   {name:          "Unsatisfied",          count:          510,          color:          "pink"},   {proper noun:          "No comment",          count:          175,          colour:          "argent"} ];

To describe a pie nautical chart, we draw a number of pie slices, each made up of an arc and a pair of lines to the eye of that arc. We tin can compute the angle taken up by each arc by dividing a full circle (2π) by the total number of responses and then multiplying that number (the angle per response) by the number of people who picked a given selection.

          <          canvass          width="200"          meridian="200"          >          </          canvas          >          <          script          >          let          cx          =          document.querySelector("canvas").getContext("2d");          permit          total          =          results          .reduce((sum, {count})          =>          sum          +          count,          0);             allow          currentAngle          =          -          0.5          *          Math.PI;          for          (let          outcome          of          results) {          permit          sliceAngle          =          (result.count          /          total)          *          2          *          Math.PI;          cx.beginPath();                    cx.arc(100,          100,          100,          currentAngle,          currentAngle          +          sliceAngle);          currentAngle          +=          sliceAngle;          cx.lineTo(100,          100);          cx.fillStyle          =          result.color;          cx.fill();   }          </          script          >        

But a chart that doesn't tell united states what the slices mean isn't very helpful. We need a style to draw text to the canvas.

Text

A 2D canvas drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but usually fillText is what you need. It will fill up the outline of the given text with the electric current fillStyle.

          <          canvas          >          </          canvas          >          <          script          >          permit          cx          =          certificate.querySelector("sheet").getContext("2d");          cx.font          =          "28px Georgia";          cx.fillStyle          =          "fuchsia";          cx.fillText("I can draw text, besides!",          10,          50);          </          script          >        

You can specify the size, style, and font of the text with the font belongings. This example just gives a font size and family name. It is also possible to add italic or bold to the start of the string to select a style.

The last two arguments to fillText and strokeText provide the position at which the font is drawn. By default, they point the position of the start of the text's alphabetic baseline, which is the line that letters "stand" on, not counting hanging parts in letters such equally j or p. You can modify the horizontal position by setting the textAlign property to "terminate" or "heart" and the vertical position by setting textBaseline to "top", "middle", or "bottom".

We'll come back to our pie nautical chart, and the trouble of labeling the slices, in the exercises at the stop of the chapter.

Images

In figurer graphics, a distinction is often made betwixt vector graphics and bitmap graphics. The first is what we have been doing so far in this chapter—specifying a motion picture past giving a logical description of shapes. Bitmap graphics, on the other manus, don't specify actual shapes only rather work with pixel data (rasters of colored dots).

The drawImage method allows united states of america to draw pixel data onto a canvas. This pixel data can originate from an <img> chemical element or from another canvas. The following example creates a detached <img> element and loads an image file into it. But it cannot immediately start drawing from this motion picture considering the browser may not have loaded it yet. To bargain with this, nosotros register a "load" event handler and exercise the drawing after the image has loaded.

          <          canvas          >          </          sheet          >          <          script          >          let          cx          =          document.querySelector("canvas").getContext("2d");          allow          img          =          document.createElement("img");          img.src          =          "img/chapeau.png";          img.addEventListener("load", ()          =>          {          for          (permit          ten          =          ten;          ten          <          200;          x          +=          thirty) {          cx.drawImage(img,          x,          10);     }   });          </          script          >        

Past default, drawImage volition draw the image at its original size. You tin can likewise give it two boosted arguments to set a dissimilar width and top.

When drawImage is given nine arguments, it tin can be used to draw only a fragment of an image. The second through fifth arguments indicate the rectangle (x, y, width, and height) in the source prototype that should exist copied, and the 6th to ninth arguments give the rectangle (on the canvas) into which information technology should be copied.

This can exist used to pack multiple sprites (image elements) into a single prototype file and then draw just the function you need. For example, we accept this moving-picture show containing a game character in multiple poses:

Various poses of a game character

By alternating which pose we draw, nosotros tin can show an animation that looks like a walking grapheme.

To animate a picture on a canvas, the clearRect method is useful. It resembles fillRect, just instead of coloring the rectangle, information technology makes information technology transparent, removing the previously drawn pixels.

We know that each sprite, each subpicture, is 24 pixels broad and 30 pixels high. The following code loads the image so sets up an interval (repeated timer) to draw the next frame:

          <          sheet          >          </          canvass          >          <          script          >          let          cx          =          document.querySelector("canvas").getContext("second");          let          img          =          document.createElement("img");          img.src          =          "img/player.png";          let          spriteW          =          24,          spriteH          =          30;          img.addEventListener("load", ()          =>          {          allow          wheel          =          0;          setInterval(()          =>          {          cx.clearRect(0,          0,          spriteW,          spriteH);          cx.drawImage(img,                              cycle          *          spriteW,          0,          spriteW,          spriteH,                              0,          0,          spriteW,          spriteH);          cycle          =          (cycle          +          1)          %          viii;     },          120);   });          </          script          >        

The cycle binding tracks our position in the animation. For each frame, information technology is incremented and then clipped back to the 0 to vii range by using the residuum operator. This binding is and then used to compute the x-coordinate that the sprite for the electric current pose has in the moving picture.

Transformation

But what if we want our grapheme to walk to the left instead of to the right? We could draw another set of sprites, of course. Merely we tin also instruct the canvass to draw the picture the other way round.

Calling the calibration method will cause annihilation drawn after it to be scaled. This method takes two parameters, i to set a horizontal scale and one to set a vertical scale.

          <          sail          >          </          sheet          >          <          script          >          let          cx          =          document.querySelector("canvas").getContext("2d");          cx.scale(3,          .5);          cx.beginPath();          cx.arc(50,          l,          40,          0,          7);          cx.lineWidth          =          iii;          cx.stroke();          </          script          >        

Scaling volition cause everything virtually the drawn epitome, including the line width, to be stretched out or squeezed together as specified. Scaling past a negative amount will flip the picture around. The flipping happens around point (0,0), which ways it volition also flip the direction of the coordinate system. When a horizontal scaling of -1 is practical, a shape drawn at x position 100 will end up at what used to exist position -100.

So to turn a picture around, we can't simply add cx.scale(-one, 1) before the call to drawImage because that would move our picture outside of the canvas, where information technology won't be visible. You could accommodate the coordinates given to drawImage to recoup for this past drawing the paradigm at ten position -fifty instead of 0. Another solution, which doesn't require the code that does the drawing to know about the calibration change, is to adjust the centrality around which the scaling happens.

There are several other methods besides scale that influence the coordinate system for a canvas. You tin rotate later on drawn shapes with the rotate method and movement them with the interpret method. The interesting—and confusing—thing is that these transformations stack, meaning that each one happens relative to the previous transformations.

So if we translate by 10 horizontal pixels twice, everything will be drawn twenty pixels to the right. If we first move the eye of the coordinate arrangement to (50,50) and then rotate by 20 degrees (about 0.1π radians), that rotation will happen around point (50,50).

Stacking transformations

But if we first rotate past xx degrees and then translate by (50,50), the translation will happen in the rotated coordinate arrangement and thus produce a different orientation. The order in which transformations are applied matters.

To flip a film around the vertical line at a given ten position, we tin can do the following:

          function          flipHorizontally(context,          around) {          context.translate(around,          0);          context.scale(-          1,          one);          context.translate(-          effectually,          0); }

We move the y-centrality to where we want our mirror to exist, apply the mirroring, and finally move the y-centrality back to its proper place in the mirrored universe. The following picture explains why this works:

Mirroring around a vertical line

This shows the coordinate systems before and later on mirroring beyond the cardinal line. The triangles are numbered to illustrate each step. If nosotros draw a triangle at a positive 10 position, it would, by default, be in the place where triangle 1 is. A telephone call to flipHorizontally first does a translation to the right, which gets us to triangle 2. It and so scales, flipping the triangle over to position iii. This is not where information technology should be, if information technology were mirrored in the given line. The second translate phone call fixes this—it "cancels" the initial translation and makes triangle four appear exactly where it should.

We can now draw a mirrored grapheme at position (100,0) by flipping the globe effectually the character's vertical center.

          <          canvass          >          </          canvas          >          <          script          >          allow          cx          =          document.querySelector("sail").getContext("2d");          permit          img          =          document.createElement("img");          img.src          =          "img/thespian.png";          allow          spriteW          =          24,          spriteH          =          30;          img.addEventListener("load", ()          =>          {          flipHorizontally(cx,          100          +          spriteW          /          2);          cx.drawImage(img,          0,          0,          spriteW,          spriteH,          100,          0,          spriteW,          spriteH);   });          </          script          >        

Storing and clearing transformations

Transformations stick around. Everything else we draw afterwards drawing that mirrored grapheme would also exist mirrored. That might exist inconvenient.

It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper matter to practice for a function that needs to temporarily transform the coordinate system. Start, nosotros relieve whatever transformation the code that called the part was using. Then the function does its thing, adding more transformations on top of the electric current transformation. Finally, we revert to the transformation we started with.

The relieve and restore methods on the second canvas context do this transformation management. They conceptually proceed a stack of transformation states. When y'all call save, the current country is pushed onto the stack, and when yous call restore, the country on top of the stack is taken off and used as the context'southward current transformation. You tin also call resetTransform to fully reset the transformation.

The branch function in the following instance illustrates what you lot tin can practise with a function that changes the transformation and then calls a function (in this instance itself), which continues cartoon with the given transformation.

This function draws a treelike shape by drawing a line, moving the center of the coordinate system to the stop of the line, and calling itself twice—start rotated to the left and and so rotated to the correct. Every call reduces the length of the branch drawn, and the recursion stops when the length drops beneath eight.

          <          sheet          width="600"          height="300"          >          </          sail          >          <          script          >          let          cx          =          certificate.querySelector("sail").getContext("2d");          role          branch(length,          angle,          scale) {          cx.fillRect(0,          0,          1,          length);          if          (length          <          8)          return;          cx.salvage();          cx.translate(0,          length);          cx.rotate(-          bending);          branch(length          *          scale,          bending,          scale);          cx.rotate(ii          *          angle);          branch(length          *          scale,          bending,          scale);          cx.restore();   }          cx.translate(300,          0);          branch(sixty,          0.5,          0.8);          </          script          >        

If the calls to salve and restore were not there, the second recursive phone call to branch would end upwards with the position and rotation created past the first call. Information technology wouldn't be connected to the current co-operative but rather to the innermost, rightmost branch drawn by the start phone call. The resulting shape might also be interesting, just it is definitely not a tree.

Back to the game

We now know enough about canvas drawing to start working on a sail-based display system for the game from the previous chapter. The new display volition no longer be showing merely colored boxes. Instead, we'll use drawImage to describe pictures that represent the game's elements.

Nosotros define another display object blazon chosen CanvasDisplay, supporting the aforementioned interface equally DOMDisplay from Chapter 16, namely, the methods syncState and articulate.

This object keeps a piddling more information than DOMDisplay. Rather than using the scroll position of its DOM chemical element, it tracks its own viewport, which tells the states what part of the level we are currently looking at. Finally, it keeps a flipPlayer holding so that even when the thespian is continuing nevertheless, information technology keeps facing the direction it final moved in.

          class          CanvasDisplay          {          constructor(parent,          level) {          this.sail          =          document.createElement("sheet");          this.sheet.width          =          Math.min(600,          level.width          *          scale);          this.sail.superlative          =          Math.min(450,          level.peak          *          calibration);          parent.appendChild(this.canvas);          this.cx          =          this.canvas.getContext("2d");          this.flipPlayer          =          false;          this.viewport          =          {          left:          0,          superlative:          0,          width:          this.canvas.width          /          scale,          height:          this.sheet.height          /          calibration          };   }          articulate() {          this.canvas.remove();   } }

The syncState method starting time computes a new viewport so draws the game scene at the appropriate position.

          CanvasDisplay.epitome.syncState          =          role(state) {          this.updateViewport(country);          this.clearDisplay(state.status);          this.drawBackground(state.level);          this.drawActors(land.actors); };

Opposite to DOMDisplay, this display style does have to redraw the background on every update. Because shapes on a canvas are just pixels, after we draw them there is no adept way to move them (or remove them). The merely fashion to update the canvas display is to articulate it and redraw the scene. We may as well take scrolled, which requires the background to be in a different position.

The updateViewport method is similar to DOMDisplay's scrollPlayerIntoView method. It checks whether the player is likewise close to the edge of the screen and moves the viewport when this is the case.

          CanvasDisplay.prototype.updateViewport          =          part(land) {          let          view          =          this.viewport,          margin          =          view.width          /          3;          allow          player          =          state.player;          let          center          =          player.pos.plus(role player.size.times(0.5));          if          (center.x          <          view.left          +          margin) {          view.left          =          Math.max(middle.x          -          margin,          0);   }          else          if          (center.x          >          view.left          +          view.width          -          margin) {          view.left          =          Math.min(centre.x          +          margin          -          view.width,          state.level.width          -          view.width);   }          if          (middle.y          <          view.elevation          +          margin) {          view.height          =          Math.max(heart.y          -          margin,          0);   }          else          if          (centre.y          >          view.top          +          view.height          -          margin) {          view.elevation          =          Math.min(eye.y          +          margin          -          view.height,          state.level.height          -          view.superlative);   } };

The calls to Math.max and Math.min ensure that the viewport does not end up showing space exterior of the level. Math.max(x, 0) makes sure the resulting number is non less than aught. Math.min similarly guarantees that a value stays below a given leap.

When clearing the brandish, we'll employ a slightly different color depending on whether the game is won (brighter) or lost (darker).

          CanvasDisplay.epitome.clearDisplay          =          role(status) {          if          (status          ==          "won") {          this.cx.fillStyle          =          "rgb(68, 191, 255)";   }          else          if          (status          ==          "lost") {          this.cx.fillStyle          =          "rgb(44, 136, 214)";   }          else          {          this.cx.fillStyle          =          "rgb(52, 166, 251)";   }          this.cx.fillRect(0,          0,          this.canvas.width,          this.canvas.height); };

To draw the background, we run through the tiles that are visible in the electric current viewport, using the same pull a fast one on used in the touches method from the previous affiliate.

          allow          otherSprites          =          document.createElement("img");          otherSprites.src          =          "img/sprites.png";          CanvasDisplay.prototype.drawBackground          =          office(level) {          permit          {left,          top,          width,          height}          =          this.viewport;          let          xStart          =          Math.flooring(left);          allow          xEnd          =          Math.ceil(left          +          width);          let          yStart          =          Math.floor(top);          allow          yEnd          =          Math.ceil(peak          +          pinnacle);          for          (let          y          =          yStart;          y          <          yEnd;          y          ++) {          for          (allow          ten          =          xStart;          10          <          xEnd;          x          ++) {          let          tile          =          level.rows[y][ten];          if          (tile          ==          "empty")          continue;          let          screenX          =          (ten          -          left)          *          calibration;          permit          screenY          =          (y          -          top)          *          scale;          let          tileX          =          tile          ==          "lava"          ?          calibration          :          0;          this.cx.drawImage(otherSprites,          tileX,          0,          calibration,          scale,          screenX,          screenY,          scale,          calibration);     }   } };

Tiles that are not empty are drawn with drawImage. The otherSprites prototype contains the pictures used for elements other than the player. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin.

Sprites for our game

Background tiles are 20 past 20 pixels since nosotros will apply the same calibration that we used in DOMDisplay. Thus, the offset for lava tiles is 20 (the value of the calibration binding), and the kickoff for walls is 0.

We don't carp waiting for the sprite image to load. Calling drawImage with an image that hasn't been loaded yet will just practise zilch. Thus, we might neglect to draw the game properly for the first few frames, while the image is even so loading, but that is not a serious trouble. Since we go along updating the screen, the correct scene will appear as presently every bit the loading finishes.

The walking character shown earlier volition exist used to correspond the role player. The code that draws it needs to pick the right sprite and direction based on the actor'due south current motion. The first eight sprites contain a walking animation. When the player is moving along a floor, we cycle through them based on the current fourth dimension. Nosotros desire to switch frames every 60 milliseconds, so the time is divided by 60 beginning. When the player is continuing still, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we use the tenth, rightmost sprite.

Considering the sprites are slightly wider than the actor object—24 instead of 16 pixels to allow some infinite for feet and artillery—the method has to adjust the ten-coordinate and width by a given amount (playerXOverlap).

          permit          playerSprites          =          certificate.createElement("img");          playerSprites.src          =          "img/player.png";          const          playerXOverlap          =          iv;          CanvasDisplay.prototype.drawPlayer          =          role(player,          10,          y,          width,          top){          width          +=          playerXOverlap          *          2;          x          -=          playerXOverlap;          if          (actor.speed.x          !=          0) {          this.flipPlayer          =          player.speed.10          <          0;   }          let          tile          =          8;          if          (role player.speed.y          !=          0) {          tile          =          9;   }          else          if          (player.speed.x          !=          0) {          tile          =          Math.floor(Date.now()          /          lx)          %          8;   }          this.cx.save();          if          (this.flipPlayer) {          flipHorizontally(this.cx,          x          +          width          /          2);   }          let          tileX          =          tile          *          width;          this.cx.drawImage(playerSprites,          tileX,          0,          width,          height,          x,          y,          width,          height);          this.cx.restore(); };

The drawPlayer method is chosen past drawActors, which is responsible for cartoon all the actors in the game.

          CanvasDisplay.prototype.drawActors          =          function(actors) {          for          (permit          actor          of          actors) {          allow          width          =          actor.size.10          *          scale;          let          height          =          actor.size.y          *          scale;          let          10          =          (actor.pos.10          -          this.viewport.left)          *          scale;          let          y          =          (actor.pos.y          -          this.viewport.superlative)          *          scale;          if          (actor.type          ==          "histrion") {          this.drawPlayer(histrion,          x,          y,          width,          peak);     }          else          {          let          tileX          =          (histrion.type          ==          "coin"          ?          two          :          1)          *          calibration;          this.cx.drawImage(otherSprites,          tileX,          0,          width,          pinnacle,          x,          y,          width,          tiptop);     }   } };

When drawing something that is not the thespian, nosotros look at its type to find the offset of the correct sprite. The lava tile is institute at offset 20, and the coin sprite is found at twoscore (two times scale).

We have to subtract the viewport'due south position when computing the actor's position since (0,0) on our sail corresponds to the top left of the viewport, not the top left of the level. Nosotros could as well take used translate for this. Either manner works.

This document plugs the new brandish into runGame:

          <          torso          >          <          script          >          runGame(GAME_LEVELS,          CanvasDisplay);          </          script          >          </          body          >        

Choosing a graphics interface

So when you need to generate graphics in the browser, you tin cull betwixt plain HTML, SVG, and canvas. There is no unmarried best arroyo that works in all situations. Each option has strengths and weaknesses.

Obviously HTML has the reward of being uncomplicated. It likewise integrates well with text. Both SVG and sail allow you to describe text, but they won't aid yous position that text or wrap information technology when it takes upwards more than one line. In an HTML-based picture, it is much easier to include blocks of text.

SVG can be used to produce crisp graphics that look good at whatever zoom level. Unlike HTML, information technology is designed for drawing and is thus more than suitable for that purpose.

Both SVG and HTML build up a data structure (the DOM) that represents your moving picture. This makes information technology possible to modify elements after they are drawn. If you need to repeatedly change a small function of a big picture in response to what the user is doing or equally role of an animation, doing it in a canvass can exist needlessly expensive. The DOM also allows us to register mouse event handlers on every chemical element in the picture (even on shapes drawn with SVG). You can't do that with canvas.

But sheet's pixel-oriented approach tin be an advantage when drawing a huge number of tiny elements. The fact that information technology does not build up a data construction simply only repeatedly draws onto the aforementioned pixel surface gives canvass a lower cost per shape.

There are also furnishings, such as rendering a scene one pixel at a time (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting it), that can be realistically handled only by a pixel-based approach.

In some cases, you may want to combine several of these techniques. For instance, you might draw a graph with SVG or canvas simply show textual information by positioning an HTML chemical element on peak of the picture show.

For nondemanding applications, it really doesn't matter much which interface you choose. The display we built for our game in this chapter could have been implemented using any of these three graphics technologies since it does non need to draw text, handle mouse interaction, or work with an extraordinarily large number of elements.

Summary

In this chapter we discussed techniques for drawing graphics in the browser, focusing on the <sheet> element.

A canvas node represents an area in a document that our programme may draw on. This cartoon is washed through a cartoon context object, created with the getContext method.

The 2nd drawing interface allows us to fill up and stroke diverse shapes. The context'south fillStyle holding determines how shapes are filled. The strokeStyle and lineWidth properties control the fashion lines are drawn.

Rectangles and pieces of text can be drawn with a unmarried method call. The fillRect and strokeRect methods draw rectangles, and the fillText and strokeText methods describe text. To create custom shapes, we must first build up a path.

Calling beginPath starts a new path. A number of other methods add lines and curves to the current path. For case, lineTo can add together a direct line. When a path is finished, information technology can exist filled with the fill up method or stroked with the stroke method.

Moving pixels from an image or another canvas onto our canvas is done with the drawImage method. Past default, this method draws the whole source image, merely by giving it more parameters, yous can copy a specific area of the image. We used this for our game by copying private poses of the game grapheme out of an image that independent many such poses.

Transformations allow you to draw a shape in multiple orientations. A 2nd drawing context has a electric current transformation that can be changed with the translate, scale, and rotate methods. These will impact all subsequent drawing operations. A transformation state can be saved with the salve method and restored with the restore method.

When showing an animation on a canvas, the clearRect method can be used to clear part of the canvas before redrawing information technology.

Exercises

Shapes

Write a program that draws the following shapes on a canvas:

  1. A trapezoid (a rectangle that is wider on one side)

  2. A red diamond (a rectangle rotated 45 degrees or ¼π radians)

  3. A zigzagging line

  4. A screw fabricated upwardly of 100 direct line segments

  5. A yellow star

The shapes to draw

When drawing the concluding two, you may want to refer to the explanation of Math.cos and Math.sin in Chapter 14, which describes how to get coordinates on a circle using these functions.

I recommend creating a function for each shape. Laissez passer the position, and optionally other properties such as the size or the number of points, every bit parameters. The alternative, which is to hard-lawmaking numbers all over your code, tends to make the lawmaking needlessly difficult to read and modify.

          <          sail          width="600"          height="200"          >          </          canvas          >          <          script          >          permit          cx          =          certificate.querySelector("canvas").getContext("second");              </          script          >        

The trapezoid (1) is easiest to draw using a path. Pick suitable center coordinates and add each of the four corners around the center.

The diamond (ii) tin be drawn the straightforward way, with a path, or the interesting way, with a rotate transformation. To use rotation, you will take to apply a trick similar to what we did in the flipHorizontally function. Because you lot want to rotate around the centre of your rectangle and not around the point (0,0), you lot must beginning translate to there, then rotate, and so interpret back.

Make sure you reset the transformation after cartoon any shape that creates one.

For the zigzag (three) it becomes impractical to write a new phone call to lineTo for each line segment. Instead, you should use a loop. Y'all can take each iteration depict either two line segments (right and then left again) or one, in which instance you must use the evenness (% 2) of the loop alphabetize to decide whether to go left or right.

You'll also need a loop for the spiral (4). If you draw a series of points, with each point moving further along a circle around the spiral'due south center, you lot get a circle. If, during the loop, you vary the radius of the circle on which yous are putting the current point and go around more than once, the result is a screw.

The star (5) depicted is congenital out of quadraticCurveTo lines. You could also draw one with straight lines. Divide a circle into eight pieces for a star with 8 points, or however many pieces you want. Draw lines between these points, making them curve toward the center of the star. With quadraticCurveTo, you lot can apply the eye as the control indicate.

The pie chart

Earlier in the affiliate, nosotros saw an example program that drew a pie chart. Alter this program so that the proper noun of each category is shown side by side to the piece that represents it. Try to find a pleasing-looking way to automatically position this text that would work for other data sets also. You may assume that categories are large enough to leave ample room for their labels.

You might demand Math.sin and Math.cos again, which are described in Affiliate 14.

          <          canvas          width="600"          superlative="300"          >          </          canvas          >          <          script          >          permit          cx          =          certificate.querySelector("canvas").getContext("2nd");          let          total          =          results          .reduce((sum, {count})          =>          sum          +          count,          0);          permit          currentAngle          =          -          0.5          *          Math.PI;          permit          centerX          =          300,          centerY          =          150;              for          (let          upshot          of          results) {          let          sliceAngle          =          (consequence.count          /          total)          *          2          *          Math.PI;          cx.beginPath();          cx.arc(centerX,          centerY,          100,          currentAngle,          currentAngle          +          sliceAngle);          currentAngle          +=          sliceAngle;          cx.lineTo(centerX,          centerY);          cx.fillStyle          =          effect.colour;          cx.fill();   }          </          script          >        

Y'all volition need to call fillText and set the context's textAlign and textBaseline properties in such a way that the text ends upward where you desire it.

A sensible fashion to position the labels would exist to put the text on the line going from the center of the pie through the middle of the slice. Y'all don't want to put the text directly against the side of the pie just rather move the text out to the side of the pie past a given number of pixels.

The angle of this line is currentAngle + 0.5 * sliceAngle. The following code finds a position on this line 120 pixels from the center:

            let            middleAngle            =            currentAngle            +            0.five            *            sliceAngle;            let            textX            =            Math.cos(middleAngle)            *            120            +            centerX;            let            textY            =            Math.sin(middleAngle)            *            120            +            centerY;

For textBaseline, the value "center" is probably advisable when using this approach. What to use for textAlign depends on which side of the circle nosotros are on. On the left, it should exist "correct", and on the right, it should exist "left", and so that the text is positioned away from the pie.

If you lot are non sure how to find out which side of the circle a given bending is on, look to the explanation of Math.cos in Chapter 14. The cosine of an angle tells u.s.a. which x-coordinate it corresponds to, which in turn tells united states of america exactly which side of the circle nosotros are on.

A bouncing brawl

Use the requestAnimationFrame technique that we saw in Chapter 14 and Chapter 16 to draw a box with a billowy ball in it. The ball moves at a constant speed and bounces off the box's sides when it hits them.

          <          canvass          width="400"          summit="400"          >          </          canvas          >          <          script          >          allow          cx          =          document.querySelector("canvas").getContext("2d");          let          lastTime          =          null;          function          frame(time) {          if          (lastTime          !=          nil) {          updateAnimation(Math.min(100,          time          -          lastTime)          /          1000);     }          lastTime          =          time;          requestAnimationFrame(frame);   }          requestAnimationFrame(frame);          function          updateAnimation(step) {        }          </          script          >        

A box is easy to draw with strokeRect. Define a binding that holds its size or ascertain two bindings if your box's width and height differ. To create a circular ball, start a path and call arc(x, y, radius, 0, vii), which creates an arc going from goose egg to more than a whole circle. So fill up the path.

To model the brawl'southward position and speed, you can employ the Vec class from Chapter xvi (which is available on this page). Give it a starting speed, preferably ane that is non purely vertical or horizontal, and for every frame multiply that speed by the amount of time that elapsed. When the ball gets besides shut to a vertical wall, invert the x component in its speed. As well, capsize the y component when information technology hits a horizontal wall.

After finding the brawl's new position and speed, utilize clearRect to delete the scene and redraw it using the new position.

Precomputed mirroring

I unfortunate affair about transformations is that they boring down the drawing of bitmaps. The position and size of each pixel has to be transformed, and though information technology is possible that browsers will go cleverer about transformation in the hereafter, they currently crusade a measurable increment in the time it takes to draw a bitmap.

In a game like ours, where we are drawing only a single transformed sprite, this is a nonissue. Merely imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion.

Retrieve of a style to allow us to draw an inverted graphic symbol without loading additional epitome files and without having to make transformed drawImage calls every frame.

The primal to the solution is the fact that nosotros tin use a canvas element as a source image when using drawImage. Information technology is possible to create an extra <sail> element, without adding it to the document, and draw our inverted sprites to information technology, once. When drawing an bodily frame, nosotros just re-create the already inverted sprites to the main canvass.

Some intendance would be required because images practise not load instantly. We do the inverted drawing only one time, and if we do information technology before the paradigm loads, information technology won't draw anything. A "load" handler on the image tin can exist used to describe the inverted images to the extra canvas. This canvas can be used every bit a drawing source immediately (it'll simply exist blank until we depict the character onto information technology).

masseywortatuslege.blogspot.com

Source: https://eloquentjavascript.net/17_canvas.html

0 Response to "Draw a Circle Over a Square in Javascript"

Publicar un comentario

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel