Draw Circle Mouse Over Canvas
Chapter 17Drawing on Canvas
Drawing is deception.
Browsers give us several ways to display graphics. The simplest fashion is to utilise styles to position and color regular DOM elements. This can get you quite far, as the game in the previous chapter showed. By calculation partially transparent background images to the nodes, we tin make them look exactly the way we want. It is even possible to rotate or skew nodes with the transform style.
Simply nosotros'd be using the DOM for something that it wasn't originally designed for. Some tasks, such equally drawing a line betwixt arbitrary points, are extremely awkward to practise with regular HTML elements.
There are two alternatives. The first is DOM-based just utilizes Scalable Vector Graphics (SVG), rather than HTML. Think of SVG equally a certificate-markup dialect that focuses on shapes rather than text. You tin can embed an SVG document directly in an HTML document or include it with an <img> tag.
The 2nd alternative is called a canvas. A canvas is a single DOM element that encapsulates a picture. It provides a programming interface for drawing shapes onto the space taken upwardly by the node. The main difference between a canvas and an SVG pic is that in SVG the original clarification of the shapes is preserved and then that they can be moved or resized at any fourth dimension. A sail, on the other hand, converts the shapes to pixels (colored dots on a raster) every bit soon as they are drawn and does not remember what these pixels represent. The only way to motion a shape on a canvass is to clear the canvass (or the part of the sheet effectually the shape) and redraw it with the shape in a new position.
SVG
This book will non go into SVG in detail, but I will briefly explain how information technology works. At the end of the chapter, I'll come back to the trade-offs that yous must consider when deciding which drawing mechanism is appropriate for a given application.
This is an HTML document with a simple SVG picture in it:
< p >Normal HTML hither.</ p > < svg xmlns="http://www.w3.org/2000/svg" > < circumvolve r="50" cx="50" cy="50" fill="cherry" /> < rect x="120" y="v" width="90" height="xc" stroke="blueish" fill="none" /> </ svg >
The xmlns attribute changes an element (and its children) to a unlike XML namespace. This namespace, identified by a URL, specifies the dialect that nosotros are currently speaking. The <circumvolve> and <rect> tags, which do non 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, simply similar HTML tags, that scripts tin can collaborate with. For example, this changes the <circle> element to exist colored cyan instead:
let circumvolve = certificate.querySelector("circumvolve"); circle.setAttribute("fill", "cyan");
The canvas element
Canvas graphics can exist drawn onto a <canvas> element. Y'all can give such an chemical element width and height attributes to determine its size in pixels.
A new canvas is empty, pregnant it is entirely transparent and thus shows up as empty space in the certificate.
The <canvas> tag is intended to let different styles of drawing. To go admission to an actual drawing interface, we first need to create a context, an object whose methods provide the drawing interface. In that location are currently two widely supported drawing styles: "2d" for 2-dimensional graphics and "webgl" for three-dimensional graphics through the OpenGL interface.
This book won't discuss WebGL—we'll stick to 2 dimensions. But if you are interested in three-dimensional graphics, I do encourage yous to look into WebGL. Information technology 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 <canvas> DOM element.
< p >Before canvas.</ p > < canvas width="120" top="60" > </ canvas > < p >Later on canvas.</ p > < script > let canvass = document.querySelector("sheet"); let context = canvas.getContext("second"); context.fillStyle = "red"; context.fillRect(10, ten, 100, 50); </ script >
Later on creating the context object, the case draws a reddish rectangle 100 pixels broad and 50 pixels high, with its elevation-left corner at coordinates (ten,ten).
But like in HTML (and SVG), the coordinate arrangement that the sheet uses puts (0,0) at the meridian-left corner, and the positive y-axis goes downward from there. So (10,x) is 10 pixels beneath and to the right of the top-left corner.
Lines and surfaces
In the sail interface, a shape tin can be filled, significant its area is given a certain colour 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 first the x- and y-coordinates of the rectangle'due south pinnacle-left corner, and then its width, then its height. A similar method, strokeRect, draws the outline of a rectangle.
Neither method takes whatever further parameters. The color of the fill, thickness of the stroke, and so on, are non determined past an argument to the method (as you lot might reasonably expect) but rather past properties of the context object.
The fillStyle property controls the way shapes are filled. It tin can be set to a string that specifies a colour, using the colour notation used by CSS.
The strokeStyle property works similarly only determines the colour used for a stroked line. The width of that line is adamant by the lineWidth property, which may incorporate any positive number.
< canvas > </ canvas > < script > let cx = document.querySelector("canvas").getContext("2nd"); cx.strokeStyle = "blue"; cx.strokeRect(5, v, 50, 50); cx.lineWidth = v; cx.strokeRect(135, v, 50, 50); </ script >
When no width or summit attribute is specified, as in the example, a canvas chemical element gets a default width of 300 pixels and height of 150 pixels.
Paths
A path is a sequence of lines. The 2d canvas interface takes a peculiar approach to describing such a path. It is done entirely through side furnishings. Paths are non values that can be stored and passed around. Instead, if y'all desire to do something with a path, you make a sequence of method calls to describe its shape.
< canvas > </ canvass > < script > let cx = document.querySelector("canvass").getContext("second"); cx.beginPath(); for (allow y = 10; y < 100; y += 10) { 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's current position. That position is ordinarily the terminate of the last segment, unless moveTo was chosen. In that case, the next segment would outset at the position passed to moveTo.
When filling a path (using the fill method), each shape is filled separately. A path tin can comprise multiple shapes—each moveTo motion starts a new i. Only the path needs to be airtight (meaning its start and stop are in the same position) before it can exist filled. If the path is not already airtight, a line is added from its end to its showtime, and the shape enclosed past the completed path is filled.
< sheet > </ canvass > < script > allow cx = document.querySelector("sheet").getContext("2d"); cx.beginPath(); cx.moveTo(l, 10); cx.lineTo(10, 70); cx.lineTo(xc, 70); cx.fill(); </ script >
This example draws a filled triangle. Note that only 2 of the triangle'south sides are explicitly drawn. The third, from the bottom-right corner dorsum to the top, is unsaid and wouldn't exist there when yous stroke the path.
Y'all could also utilise the closePath method to explicitly close a path past adding an actual line segment back to the path'southward start. This segment is drawn when stroking the path.
Curves
A path may also contain curved lines. These are unfortunately a scrap more than involved to draw.
The quadraticCurveTo method draws a bend to a given signal. To determine the curvature of the line, the method is given a command point besides as a destination signal. Imagine this control point as attracting the line, giving it its curve. The line won't become through the control point, but its management at the start and end points volition be such that a directly line in that management would point toward the control point. The post-obit example illustrates this:
< canvas > </ canvas > < script > let cx = document.querySelector("sail").getContext("2d"); cx.beginPath(); cx.moveTo(ten, 90); cx.quadraticCurveTo(60, 10, ninety, 90); cx.lineTo(60, 10); cx.closePath(); cx.stroke(); </ script >
We draw a quadratic bend from the left to the right, with (60,10) every bit command point, and then draw 2 line segments going through that control point and back to the kickoff of the line. The result somewhat resembles a Star Trek insignia. You tin can see the effect of the control point: the lines leaving the lower corners offset off in the direction of the control indicate and and then bend toward their target.
The bezierCurveTo method draws a similar kind of curve. Instead of a single control bespeak, this one has ii—ane for each of the line's endpoints. Here is a like sketch to illustrate the behavior of such a curve:
< canvas > </ canvas > < script > let cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.moveTo(10, 90); cx.bezierCurveTo(10, ten, 90, ten, l, 90); cx.lineTo(90, ten); cx.lineTo(10, 10); cx.closePath(); cx.stroke(); </ script >
The two control points specify the direction at both ends of the curve. The farther they are away from their corresponding point, the more than the curve will "bulge" in that direction.
Such curves can be difficult to work with—information technology'south not ever clear how to discover the control points that provide the shape you are looking for. Sometimes you can compute them, and sometimes you'll simply accept to find a suitable value past trial and fault.
The arc method is a way to draw a line that curves along the border of a circle. It takes a pair of coordinates for the arc's center, a radius, and then a start angle and end angle.
Those last two parameters brand information technology possible to describe just part of the circle. The angles are measured in radians, non degrees. This means a full circumvolve has an bending of 2π, or two * Math.PI, which is nigh 6.28. The bending starts counting at the signal to the right of the circle'south center and goes clockwise from there. You can use a start of 0 and an end bigger than 2π (say, seven) to draw a full circle.
< canvas > </ canvass > < script > let cx = document.querySelector("canvas").getContext("2d"); cx.beginPath(); cx.arc(50, 50, 40, 0, seven); cx.arc(150, 50, 40, 0, 0.v * Math.PI); cx.stroke(); </ script >
The resulting picture contains a line from the right of the full circle (first call to arc) to the right of the quarter-circle (2nd call). Similar other path-drawing methods, a line drawn with arc is connected to the previous path segment. You can call moveTo or commencement a new path to avoid this.
Cartoon a pie chart
Imagine y'all've just taken a job at EconomiCorp, Inc., and your first assignment is to draw a pie chart of its customer satisfaction survey results.
The results binding contains an array of objects that correspond the survey responses.
const results = [ {proper name: "Satisfied", count: 1043, colour: "lightblue"}, {name: "Neutral", count: 563, color: "lightgreen"}, {proper name: "Unsatisfied", count: 510, color: "pink"}, {name: "No comment", count: 175, color: "silver"} ];
To draw a pie nautical chart, nosotros draw a number of pie slices, each made upward of an arc and a pair of lines to the center of that arc. Nosotros can compute the angle taken upwardly by each arc past dividing a full circumvolve (2π) by the total number of responses and then multiplying that number (the bending per response) past the number of people who picked a given choice.
< canvas width="200" summit="200" > </ canvas > < script > let cx = document.querySelector("canvas").getContext("2d"); let full = results .reduce((sum, {count}) => sum + count, 0); permit currentAngle = - 0.5 * Math.PI; for (let result of results) { let sliceAngle = (outcome.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 >
Merely a chart that doesn't tell us what the slices hateful isn't very helpful. We need a manner to describe text to the canvas.
Text
A 2D sheet drawing context provides the methods fillText and strokeText. The latter can be useful for outlining letters, but ordinarily fillText is what you demand. Information technology volition fill up the outline of the given text with the current fillStyle.
< canvas > </ canvass > < script > let cx = certificate.querySelector("canvas").getContext("second"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; cx.fillText("I can draw text, too!", 10, 50); </ script >
You tin specify the size, style, and font of the text with the font property. This example but gives a font size and family unit proper name. Information technology is also possible to add italic or assuming to the start of the cord to select a style.
The last two arguments to fillText and strokeText provide the position at which the font is drawn. By default, they bespeak the position of the showtime of the text's alphabetic baseline, which is the line that messages "stand" on, not counting hanging parts in letters such every bit j or p. Y'all tin change the horizontal position by setting the textAlign belongings to "stop" or "center" and the vertical position by setting textBaseline to "top", "eye", or "bottom".
Nosotros'll come back to our pie chart, and the problem of labeling the slices, in the exercises at the finish of the chapter.
Images
In reckoner graphics, a distinction is often fabricated between vector graphics and bitmap graphics. The offset is what we have been doing so far in this chapter—specifying a movie by giving a logical description of shapes. Bitmap graphics, on the other hand, don't specify actual shapes merely rather work with pixel data (rasters of colored dots).
The drawImage method allows us to describe pixel data onto a canvas. This pixel data tin originate from an <img> element or from some other sheet. The following instance creates a detached <img> element and loads an image file into it. Only it cannot immediately first cartoon from this film considering the browser may non have loaded it still. To bargain with this, we register a "load" event handler and exercise the drawing later the paradigm has loaded.
< canvas > </ canvas > < script > let cx = document.querySelector("canvas").getContext("second"); let img = document.createElement("img"); img.src = "img/hat.png"; img.addEventListener("load", () => { for (let 10 = x; 10 < 200; x += 30) { cx.drawImage(img, x, 10); } }); </ script >
By default, drawImage volition draw the image at its original size. Yous can also requite information technology two additional arguments to set up a different width and height.
When drawImage is given nine arguments, information technology can be used to draw but a fragment of an paradigm. The second through fifth arguments indicate the rectangle (x, y, width, and meridian) in the source image that should be copied, and the sixth to ninth arguments requite the rectangle (on the sail) into which it should be copied.
This can be used to pack multiple sprites (epitome elements) into a single epitome file and and then draw only the part you lot need. For example, we accept this moving-picture show containing a game character in multiple poses:
Past alternating which pose we draw, we can prove an animation that looks like a walking character.
To animate a picture on a canvas, the clearRect method is useful. It resembles fillRect, but instead of coloring the rectangle, it makes it transparent, removing the previously drawn pixels.
We know that each sprite, each subpicture, is 24 pixels wide and 30 pixels high. The following code loads the prototype and and then sets upwards an interval (repeated timer) to draw the next frame:
< canvas > </ canvas > < script > let cx = document.querySelector("sail").getContext("2d"); let img = document.createElement("img"); img.src = "img/actor.png"; let spriteW = 24, spriteH = xxx; img.addEventListener("load", () => { let bike = 0; setInterval(() => { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, wheel * spriteW, 0, spriteW, spriteH, 0, 0, spriteW, spriteH); cycle = (bike + ane) % 8; }, 120); }); </ script >
The cycle binding tracks our position in the animation. For each frame, it is incremented then clipped back to the 0 to seven range by using the rest operator. This binding is and so used to compute the x-coordinate that the sprite for the current pose has in the moving-picture show.
Transformation
But what if we want our character to walk to the left instead of to the correct? Nosotros could describe another set up of sprites, of course. But we can besides instruct the canvass to draw the motion-picture show the other way round.
Calling the scale method will crusade annihilation drawn after it to be scaled. This method takes two parameters, one to gear up a horizontal scale and one to fix a vertical scale.
< sheet > </ sheet > < script > permit cx = document.querySelector("sail").getContext("2d"); cx.scale(3, .5); cx.beginPath(); cx.arc(l, l, 40, 0, 7); cx.lineWidth = iii; cx.stroke(); </ script >
Scaling will cause everything about the drawn image, including the line width, to exist stretched out or squeezed together equally specified. Scaling by a negative amount will flip the picture around. The flipping happens around point (0,0), which means information technology will also flip the direction of the coordinate system. When a horizontal scaling of -i is practical, a shape drawn at x position 100 will stop upward at what used to be position -100.
So to turn a moving picture effectually, nosotros tin't simply add cx.scale(-one, i) before the call to drawImage because that would move our motion picture outside of the canvas, where information technology won't be visible. Yous could adjust the coordinates given to drawImage to compensate for this past drawing the image at x position -50 instead of 0. Some other solution, which doesn't require the code that does the cartoon to know virtually the calibration modify, is to adapt the axis effectually which the scaling happens.
There are several other methods besides scale that influence the coordinate system for a canvass. You can rotate afterwards drawn shapes with the rotate method and motion them with the interpret method. The interesting—and disruptive—thing is that these transformations stack, meaning that each one happens relative to the previous transformations.
Then if we interpret by 10 horizontal pixels twice, everything will be fatigued 20 pixels to the right. If we first move the center of the coordinate system to (50,l) and and so rotate by 20 degrees (about 0.1π radians), that rotation will happen around point (50,l).
But if we offset rotate by xx degrees and then translate past (fifty,50), the translation volition happen in the rotated coordinate system and thus produce a dissimilar orientation. The social club in which transformations are practical matters.
To flip a picture show around the vertical line at a given x position, we tin can do the following:
function flipHorizontally(context, around) { context.translate(around, 0); context.scale(- 1, 1); context.translate(- around, 0); }
We move the y-axis to where we want our mirror to be, 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:
This shows the coordinate systems before and after mirroring across the central line. The triangles are numbered to illustrate each step. If nosotros draw a triangle at a positive x position, information technology would, past default, be in the place where triangle one is. A call to flipHorizontally first does a translation to the right, which gets us to triangle 2. It so scales, flipping the triangle over to position iii. This is not where information technology should be, if it were mirrored in the given line. The second translate call fixes this—it "cancels" the initial translation and makes triangle 4 announced exactly where it should.
We can now describe a mirrored character at position (100,0) by flipping the world around the character's vertical center.
< sheet > </ sheet > < script > let cx = document.querySelector("sheet").getContext("2d"); let img = document.createElement("img"); img.src = "img/histrion.png"; permit spriteW = 24, spriteH = thirty; img.addEventListener("load", () => { flipHorizontally(cx, 100 + spriteW / two); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); </ script >
Storing and clearing transformations
Transformations stick effectually. Everything else we depict after drawing that mirrored graphic symbol would also be mirrored. That might be inconvenient.
It is possible to save the current transformation, do some cartoon and transforming, and then restore the old transformation. This is normally the proper thing to do for a function that needs to temporarily transform the coordinate system. Outset, we save whatever transformation the code that chosen the office was using. Then the function does its thing, adding more transformations on top of the current transformation. Finally, we revert to the transformation we started with.
The save and restore methods on the 2D canvas context exercise this transformation direction. They conceptually go along a stack of transformation states. When you phone call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used equally the context's current transformation. You tin can also call resetTransform to fully reset the transformation.
The branch function in the post-obit example illustrates what you can practise with a function that changes the transformation and and so calls a function (in this example itself), which continues cartoon with the given transformation.
This function draws a treelike shape by drawing a line, moving the eye of the coordinate organisation to the cease of the line, and calling itself twice—showtime rotated to the left and then rotated to the correct. Every telephone call reduces the length of the branch drawn, and the recursion stops when the length drops beneath 8.
< canvas width="600" top="300" > </ canvas > < script > let cx = document.querySelector("sheet").getContext("2d"); role branch(length, angle, calibration) { cx.fillRect(0, 0, 1, length); if (length < eight) return; cx.salvage(); cx.interpret(0, length); cx.rotate(- angle); branch(length * scale, angle, scale); cx.rotate(ii * bending); co-operative(length * calibration, angle, calibration); cx.restore(); } cx.translate(300, 0); branch(lx, 0.5, 0.8); </ script >
If the calls to save and restore were non there, the second recursive call to branch would finish up with the position and rotation created by the kickoff call. It wouldn't be connected to the current co-operative but rather to the innermost, rightmost branch fatigued by the outset phone call. The resulting shape might also be interesting, but it is definitely not a tree.
Back to the game
We now know enough about canvass cartoon to start working on a sail-based display system for the game from the previous affiliate. The new brandish will no longer exist showing just colored boxes. Instead, we'll use drawImage to depict pictures that represent the game's elements.
We define another display object type called CanvasDisplay, supporting the aforementioned interface every bit DOMDisplay from Affiliate 16, namely, the methods syncState and clear.
This object keeps a little more than information than DOMDisplay. Rather than using the whorl position of its DOM element, information technology tracks its own viewport, which tells u.s.a. what role of the level we are currently looking at. Finally, it keeps a flipPlayer property so that even when the player is standing still, it keeps facing the direction it last moved in.
class CanvasDisplay { constructor(parent, level) { this.canvass = certificate.createElement("canvas"); this.canvas.width = Math.min(600, level.width * scale); this.sheet.height = Math.min(450, level.height * scale); parent.appendChild(this.sheet); this.cx = this.canvas.getContext("2nd"); this.flipPlayer = faux; this.viewport = { left: 0, height: 0, width: this.sail.width / calibration, height: this.canvass.height / calibration }; } clear() { this.sail.remove(); } }
The syncState method kickoff computes a new viewport and then draws the game scene at the appropriate position.
CanvasDisplay.prototype.syncState = part(state) { this.updateViewport(state); this.clearDisplay(state.condition); this.drawBackground(state.level); this.drawActors(state.actors); };
Contrary to DOMDisplay, this display style does have to redraw the background on every update. Because shapes on a canvass are just pixels, after we draw them in that location is no skillful way to move them (or remove them). The only way to update the canvass brandish is to articulate it and redraw the scene. We may too take scrolled, which requires the groundwork to be in a unlike position.
The updateViewport method is similar to DOMDisplay'southward scrollPlayerIntoView method. Information technology checks whether the histrion is too shut to the edge of the screen and moves the viewport when this is the case.
CanvasDisplay.prototype.updateViewport = function(land) { let view = this.viewport, margin = view.width / 3; allow role player = land.player; let center = player.pos.plus(histrion.size.times(0.five)); if (eye.x < view.left + margin) { view.left = Math.max(centre.x - margin, 0); } else if (eye.x > view.left + view.width - margin) { view.left = Math.min(center.x + margin - view.width, state.level.width - view.width); } if (heart.y < view.elevation + margin) { view.height = Math.max(centre.y - margin, 0); } else if (center.y > view.top + view.height - margin) { view.top = Math.min(heart.y + margin - view.superlative, country.level.tiptop - view.height); } };
The calls to Math.max and Math.min ensure that the viewport does not finish up showing space outside of the level. Math.max(ten, 0) makes sure the resulting number is not less than nix. Math.min similarly guarantees that a value stays below a given bound.
When clearing the brandish, we'll employ a slightly different color depending on whether the game is won (brighter) or lost (darker).
CanvasDisplay.prototype.clearDisplay = function(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.canvass.width, this.canvas.height); };
To draw the background, we run through the tiles that are visible in the current viewport, using the aforementioned play tricks used in the touches method from the previous affiliate.
permit otherSprites = certificate.createElement("img"); otherSprites.src = "img/sprites.png"; CanvasDisplay.prototype.drawBackground = role(level) { permit {left, pinnacle, width, height} = this.viewport; let xStart = Math.floor(left); let xEnd = Math.ceil(left + width); let yStart = Math.floor(top); let yEnd = Math.ceil(top + height); for (let y = yStart; y < yEnd; y ++) { for (let x = xStart; 10 < xEnd; ten ++) { permit tile = level.rows[y][10]; if (tile == "empty") keep; let screenX = (x - left) * scale; allow screenY = (y - top) * calibration; permit tileX = tile == "lava" ? calibration : 0; this.cx.drawImage(otherSprites, tileX, 0, scale, scale, screenX, screenY, scale, scale); } } };
Tiles that are not empty are drawn with drawImage. The otherSprites image contains the pictures used for elements other than the actor. It contains, from left to right, the wall tile, the lava tile, and the sprite for a coin.
Background tiles are 20 by 20 pixels since nosotros will utilize the same scale that nosotros used in DOMDisplay. Thus, the first for lava tiles is 20 (the value of the scale bounden), and the offset for walls is 0.
We don't carp waiting for the sprite epitome to load. Calling drawImage with an image that hasn't been loaded yet will but exercise cypher. Thus, nosotros might fail to depict the game properly for the get-go few frames, while the image is all the same loading, but that is not a serious problem. Since nosotros keep updating the screen, the correct scene will appear as before long as the loading finishes.
The walking grapheme shown earlier will be used to stand for the thespian. The lawmaking that draws information technology needs to pick the right sprite and management based on the actor's current motion. The first eight sprites contain a walking animation. When the player is moving along a flooring, nosotros bicycle through them based on the current fourth dimension. We want to switch frames every 60 milliseconds, so the time is divided by 60 outset. When the player is standing withal, we draw the ninth sprite. During jumps, which are recognized by the fact that the vertical speed is not zero, we apply the tenth, rightmost sprite.
Because the sprites are slightly wider than the role player object—24 instead of 16 pixels to allow some infinite for feet and artillery—the method has to accommodate the x-coordinate and width by a given amount (playerXOverlap).
let playerSprites = document.createElement("img"); playerSprites.src = "img/role player.png"; const playerXOverlap = 4; CanvasDisplay.prototype.drawPlayer = function(player, x, y, width, top){ width += playerXOverlap * two; ten -= playerXOverlap; if (player.speed.ten != 0) { this.flipPlayer = player.speed.10 < 0; } let tile = viii; if (histrion.speed.y != 0) { tile = 9; } else if (player.speed.x != 0) { tile = Math.floor(Date.at present() / sixty) % 8; } this.cx.salvage(); if (this.flipPlayer) { flipHorizontally(this.cx, x + width / ii); } let tileX = tile * width; this.cx.drawImage(playerSprites, tileX, 0, width, top, x, y, width, height); this.cx.restore(); };
The drawPlayer method is chosen by drawActors, which is responsible for drawing all the actors in the game.
CanvasDisplay.prototype.drawActors = function(actors) { for (permit actor of actors) { let width = thespian.size.ten * scale; let height = actor.size.y * scale; let 10 = (actor.pos.10 - this.viewport.left) * scale; let y = (actor.pos.y - this.viewport.top) * scale; if (actor.type == "histrion") { this.drawPlayer(actor, x, y, width, height); } else { let tileX = (actor.type == "coin" ? two : 1) * calibration; this.cx.drawImage(otherSprites, tileX, 0, width, acme, x, y, width, height); } } };
When drawing something that is not the player, nosotros expect at its blazon to find the commencement of the correct sprite. The lava tile is found at beginning twenty, and the coin sprite is constitute at 40 (two times scale).
Nosotros have to decrease the viewport's position when computing the thespian's position since (0,0) on our sail corresponds to the top left of the viewport, not the summit left of the level. Nosotros could also take used translate for this. Either way works.
This document plugs the new brandish into runGame:
< body > < script > runGame(GAME_LEVELS, CanvasDisplay); </ script > </ body >
Choosing a graphics interface
Then when you need to generate graphics in the browser, you lot tin can choose between plain HTML, SVG, and canvas. There is no unmarried best arroyo that works in all situations. Each option has strengths and weaknesses.
Plain HTML has the advantage of being uncomplicated. Information technology likewise integrates well with text. Both SVG and canvas allow y'all to draw text, but they won't assist you position that text or wrap it when it takes upward more than one line. In an HTML-based motion-picture show, it is much easier to include blocks of text.
SVG can be used to produce well-baked graphics that look expert at any zoom level. Unlike HTML, it is designed for drawing and is thus more suitable for that purpose.
Both SVG and HTML build up a data construction (the DOM) that represents your motion-picture show. This makes information technology possible to modify elements afterwards they are drawn. If y'all need to repeatedly modify a modest part of a big picture in response to what the user is doing or as part of an animation, doing it in a canvas can exist needlessly expensive. The DOM also allows u.s. to register mouse event handlers on every chemical element in the motion picture (even on shapes drawn with SVG). Yous can't do that with sheet.
But sail'southward pixel-oriented approach can exist 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 same pixel surface gives canvas a lower cost per shape.
At that place are also effects, such as rendering a scene ane pixel at a fourth dimension (for example, using a ray tracer) or postprocessing an image with JavaScript (blurring or distorting information technology), that can be realistically handled merely past a pixel-based arroyo.
In some cases, you may want to combine several of these techniques. For instance, you lot might draw a graph with SVG or canvas but prove textual information by positioning an HTML element on elevation of the moving picture.
For nondemanding applications, it really doesn't affair much which interface you choose. The brandish we congenital for our game in this chapter could take been implemented using any of these three graphics technologies since it does not 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 <canvass> chemical element.
A sail node represents an area in a certificate that our program may draw on. This drawing is done through a cartoon context object, created with the getContext method.
The 2D drawing interface allows us to fill and stroke various shapes. The context's fillStyle property determines how shapes are filled. The strokeStyle and lineWidth backdrop control the way lines are fatigued.
Rectangles and pieces of text can exist drawn with a unmarried method phone call. The fillRect and strokeRect methods draw rectangles, and the fillText and strokeText methods depict text. To create custom shapes, nosotros must beginning build up a path.
Calling beginPath starts a new path. A number of other methods add lines and curves to the current path. For example, lineTo can add together a straight line. When a path is finished, it can be filled with the fill method or stroked with the stroke method.
Moving pixels from an paradigm or another canvas onto our canvas is done with the drawImage method. Past default, this method draws the whole source image, but by giving it more parameters, y'all tin copy a specific area of the image. We used this for our game past copying individual poses of the game character out of an prototype that contained many such poses.
Transformations allow you to draw a shape in multiple orientations. A 2D drawing context has a electric current transformation that tin be changed with the translate, scale, and rotate methods. These volition affect all subsequent drawing operations. A transformation state can exist saved with the salve method and restored with the restore method.
When showing an blitheness on a canvas, the clearRect method can be used to clear part of the canvas before redrawing it.
Exercises
Shapes
Write a programme that draws the following shapes on a sail:
-
A trapezoid (a rectangle that is wider on one side)
-
A carmine diamond (a rectangle rotated 45 degrees or ¼π radians)
-
A zigzagging line
-
A screw made upwardly of 100 straight line segments
-
A yellowish star
When drawing the last ii, you may want to refer to the explanation of Math.cos and Math.sin in Chapter 14, which describes how to become coordinates on a circumvolve using these functions.
I recommend creating a role for each shape. Pass the position, and optionally other properties such as the size or the number of points, as parameters. The alternative, which is to difficult-code numbers all over your code, tends to make the code needlessly difficult to read and alter.
< canvas width="600" height="200" > </ sheet > < script > let cx = document.querySelector("sheet").getContext("2d"); </ script >
The trapezoid (one) is easiest to draw using a path. Choice suitable center coordinates and add each of the four corners around the center.
The diamond (two) can be drawn the straightforward way, with a path, or the interesting mode, with a rotate transformation. To use rotation, you volition have to utilize a trick similar to what nosotros did in the flipHorizontally function. Considering you want to rotate effectually the center of your rectangle and not around the betoken (0,0), you lot must first translate to at that place, so rotate, and then translate back.
Make sure you reset the transformation after cartoon whatsoever shape that creates one.
For the zigzag (3) information technology becomes impractical to write a new telephone call to lineTo for each line segment. Instead, you should use a loop. You tin have each iteration draw either two line segments (right and so left again) or one, in which case you must use the evenness (% two) of the loop alphabetize to determine whether to become left or right.
You'll also need a loop for the spiral (4). If you describe a serial of points, with each point moving farther along a circumvolve around the spiral's center, yous get a circle. If, during the loop, yous vary the radius of the circle on which you are putting the current point and go effectually more than than once, the consequence is a spiral.
The star (five) depicted is built out of quadraticCurveTo lines. You could also describe ane with direct lines. Divide a circumvolve into 8 pieces for a star with 8 points, or however many pieces you desire. Draw lines between these points, making them bend toward the heart of the star. With quadraticCurveTo, you tin use the center as the control point.
The pie chart
Earlier in the chapter, we saw an example program that drew a pie chart. Modify this program so that the proper noun of each category is shown side by side to the piece that represents information technology. 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 big enough to leave aplenty room for their labels.
Y'all might need Math.sin and Math.cos again, which are described in Chapter 14.
< canvas width="600" tiptop="300" > </ canvas > < script > let cx = document.querySelector("canvas").getContext("2d"); allow full = results .reduce((sum, {count}) => sum + count, 0); permit currentAngle = - 0.5 * Math.PI; let centerX = 300, centerY = 150; for (allow result of results) { let sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); cx.arc(centerX, centerY, 100, currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(centerX, centerY); cx.fillStyle = effect.color; cx.fill(); } </ script >
You will need to phone call fillText and set the context'southward textAlign and textBaseline backdrop in such a way that the text ends up where you want it.
A sensible way to position the labels would be to put the text on the line going from the centre of the pie through the centre of the slice. You don't want to put the text straight against the side of the pie but rather move the text out to the side of the pie by a given number of pixels.
The angle of this line is currentAngle + 0.. The following code finds a position on this line 120 pixels from the centre:
let middleAngle = currentAngle + 0.five * sliceAngle; let textX = Math.cos(middleAngle) * 120 + centerX; let textY = Math.sin(middleAngle) * 120 + centerY;
For textBaseline, the value "centre" is probably appropriate when using this approach. What to use for textAlign depends on which side of the circle we are on. On the left, information technology should exist "right", and on the right, it should exist "left", then that the text is positioned away from the pie.
If you are not certain how to find out which side of the circumvolve a given angle is on, look to the explanation of Math.cos in Chapter xiv. The cosine of an angle tells us which x-coordinate it corresponds to, which in turn tells us exactly which side of the circumvolve nosotros are on.
A bouncing brawl
Employ the requestAnimationFrame technique that nosotros saw in Affiliate 14 and Chapter 16 to describe a box with a bouncing ball in it. The ball moves at a abiding speed and bounces off the box'southward sides when it hits them.
< sheet width="400" height="400" > </ sail > < script > let cx = document.querySelector("sail").getContext("2d"); allow lastTime = null; function frame(time) { if (lastTime != nix) { updateAnimation(Math.min(100, time - lastTime) / 1000); } lastTime = time; requestAnimationFrame(frame); } requestAnimationFrame(frame); office updateAnimation(pace) { } </ script >
A box is piece of cake to draw with strokeRect. Define a binding that holds its size or ascertain two bindings if your box'south width and height differ. To create a round ball, start a path and call arc(x, y, radius, 0, 7), which creates an arc going from nada to more than a whole circle. Then fill the path.
To model the ball'south position and speed, you can use the Vec class from Affiliate sixteen (which is available on this page). Give information technology a starting speed, preferably one that is not purely vertical or horizontal, and for every frame multiply that speed by the amount of fourth dimension that elapsed. When the brawl gets too close to a vertical wall, capsize the x component in its speed. Too, invert the y component when it hits a horizontal wall.
Subsequently finding the ball's new position and speed, employ clearRect to delete the scene and redraw it using the new position.
Precomputed mirroring
One unfortunate thing about transformations is that they slow downwardly the cartoon of bitmaps. The position and size of each pixel has to exist transformed, and though it is possible that browsers will go cleverer well-nigh transformation in the time to come, they currently cause a measurable increase in the time it takes to draw a bitmap.
In a game like ours, where we are drawing only a unmarried transformed sprite, this is a nonissue. But imagine that we need to draw hundreds of characters or thousands of rotating particles from an explosion.
Think of a way to allow us to draw an inverted grapheme without loading additional epitome files and without having to make transformed drawImage calls every frame.
The cardinal to the solution is the fact that we can apply a canvas element as a source image when using drawImage. It is possible to create an extra <canvas> chemical element, without calculation it to the certificate, and draw our inverted sprites to it, once. When cartoon an actual frame, we just re-create the already inverted sprites to the primary canvas.
Some care would be required because images do not load instantly. Nosotros do the inverted drawing only once, and if we do it before the image loads, it won't draw anything. A "load" handler on the image tin be used to draw the inverted images to the extra canvas. This canvas tin can be used every bit a drawing source immediately (information technology'll only be blank until we draw the character onto it).
Source: https://eloquentjavascript.net/17_canvas.html
0 Response to "Draw Circle Mouse Over Canvas"
Enregistrer un commentaire