` element), for example, becomes the JavaScript htmlFor property. “class” is a reserved word in JavaScript, and the very important HTML class attribute is an exception to the rule: it becomes className in JavaScript code.
The properties that represent HTML attributes usually have string values. But when the attribute is a boolean or numeric value (the defaultChecked and maxLength attributes of an ` ` element, for example), the properties are booleans or numbers instead of strings. Event handler attributes always have functions (or null) as their values.
Note that this property-based API for getting and setting attribute values does not define any way to remove an attribute from an element. In particular, the delete operator cannot be used for this purpose. If you need to delete an attribute, use the removeAttribute() method.
THE CLASS ATTRIBUTE
The class attribute of an HTML element is a particularly important one. Its value is a space-separated list of CSS classes that apply to the element and affect how it is styled with CSS. Because class is a reserved word in JavaScript, the value of this attribute is available through the className property on Element objects. The className property can set and return the value of the class attribute as a string. But the class attribute is poorly named: its value is a list of CSS classes, not a single class, and it is common in client-side JavaScript programming to want to add and remove individual class names from this list rather than work with the list as a single string.
For this reason, Element objects define a classList property that allows you to treat the class attribute as a list. The value of the classList property is an iterable Array-like object. Although the name of the property is classList, it behaves more like a set of classes, and defines add(), remove(), contains(), and toggle() methods:
```js
// When we want to let the user know that we are busy, we display
// a spinner. To do this we have to remove the "hidden" class and add the
// "animated" class (assuming the stylesheets are configured correctly).
let spinner = document.querySelector("#spinner");
spinner.classList.remove("hidden");
spinner.classList.add("animated");
```
DATASET ATTRIBUTES
It is sometimes useful to attach additional information to HTML elements, typically when JavaScript code will be selecting those elements and manipulating them in some way. In HTML, any attribute whose name is lowercase and begins with the prefix “data-” is considered valid, and you can use them for any purpose. These “dataset attributes” will not affect the presentation of the elements on which they appear, and they define a standard way to attach additional data without compromising document validity.
In the DOM, Element objects have a dataset property that refers to an object that has properties that correspond to the data- attributes with their prefix removed. Thus, dataset.x would hold the value of the data-x attribute. Hyphenated attributes map to camelCase property names: the attribute data-section-number becomes the property dataset.sectionNumber.
Suppose an HTML document contains this text:
```html
Attributes
```
Then you could write JavaScript like this to access that section number:
```js
let number = document.querySelector("#title").dataset.sectionNumber;
```
### 15.3.4 Element Content
Look again at the document tree pictured in Figure 15-1, and ask yourself what the “content” of the `` element is. There are two ways we might answer this question:
- The content is the HTML string “This is a ``simple` ` document”.
- The content is the plain-text string “This is a simple document”.
Both of these are valid answers, and each answer is useful in its own way. The sections that follow explain how to work with the HTML representation and the plain-text representation of an element’s content.
ELEMENT CONTENT AS HTML
Reading the innerHTML property of an Element returns the content of that element as a string of markup. Setting this property on an element invokes the web browser’s parser and replaces the element’s current content with a parsed representation of the new string. You can test this out by opening the developer console and typing:
```js
document.body.innerHTML = "
Oops ";
```
You will see that the entire web page disappears and is replaced with the single heading, “Oops”. Web browsers are very good at parsing HTML, and setting innerHTML is usually fairly efficient. Note, however, that appending text to the innerHTML property with the += operator is not efficient because it requires both a serialization step to convert element content to a string and then a parsing step to convert the new string back into element content.
WARNING
When using these HTML APIs, it is very important that you never insert user input into the document. If you do this, you allow malicious users to inject their own scripts into your application. See “Cross-site scripting” for details.
The outerHTML property of an Element is like innerHTML except that its value includes the element itself. When you query outerHTML, the value includes the opening and closing tags of the element. And when you set outerHTML on an element, the new content replaces the element itself.
A related Element method is insertAdjacentHTML(), which allows you to insert a string of arbitrary HTML markup “adjacent” to the specified element. The markup is passed as the second argument to this method, and the precise meaning of “adjacent” depends on the value of the first argument. This first argument should be a string with one of the values “beforebegin,” “afterbegin,” “beforeend,” or “afterend.” These values correspond to insertion points that are illustrated in Figure 15-2.
Insertion points for insertAdjacentHTML()
#### ELEMENT CONTENT AS PLAIN TEXT
Sometimes you want to query the content of an element as plain text or to insert plain text into a document (without having to escape the angle brackets and ampersands used in HTML markup). The standard way to do this is with the textContent property:
```js
let para = document.querySelector("p"); // First in the document
let text = para.textContent; // Get the text of the paragraph
para.textContent = "Hello World!"; // Alter the text of the paragraph
```
The textContent property is defined by the Node class, so it works for Text nodes as well as Element nodes. For Element nodes, it finds and returns all text in all descendants of the element.
The Element class defines an innerText property that is similar to textContent. innerText has some unusual and complex behaviors, such as attempting to preserve table formatting. It is not well specified nor implemented compatibly between browsers, however, and should no longer be used.
TEXT IN `
```
You’ll notice that the descendants of the `` tag are not normal HTML tags. ``, ``, and `` tags have obvious purposes, though, and it should be clear how this SVG graphic works. There are many other SVG tags, however, and you’ll need to consult an SVG reference to learn more. You may also notice that the stylesheet is odd. Styles like fill, stroke-width, and text-anchor are not normal CSS style properties. In this case, CSS is essentially being used to set attributes of SVG tags that appear in the document. Note also that the CSS font shorthand property does not work for SVG tags, and you must explicitly set font-family, font-size, and font-weight as separate style properties.
### 15.7.2 Scripting SVG
One reason to embed SVG directly into your HTML files (instead of just using static ` `tags) is that if you do this, then you can use the DOM API to manipulate the SVG image. Suppose you use SVG to display icons in your web application. You could embed SVG within a `` tag (§15.6.2) and then clone the template content whenever you need to insert a copy of that icon into your UI. And if you want the icon to respond to user activity—by changing color when the user hovers the pointer over it, for example—you can often achieve this with CSS.
It is also possible to dynamically manipulate SVG graphics that are directly embedded in HTML. The clock face example in the previous section displays a static clock with hour and minute hands facing straight up displaying the time noon or midnight. But you may have noticed that the HTML file includes a `
```
We’ve seen that SVG describes complex shapes as a “path” of lines and curves that can be drawn or filled. The Canvas API also uses the notion of a path. Instead of describing a path as a string of letters and numbers, a path is defined by a series of method calls, such as the beginPath() and arc() invocations in the preceding code. Once a path is defined, other methods, such as fill(), operate on that path. Various properties of the context object, such as fillStyle, specify how these operations are performed.
The subsections that follow demonstrate the methods and properties of the 2D Canvas API. Much of the example code that follows operates on a variable c. This variable holds the CanvasRenderingContext2D object of the canvas, but the code to initialize that variable is sometimes not shown. In order to make these examples run, you would need to add HTML markup to define a canvas with appropriate width and height attributes, and then add code like this to initialize the variable c:
```js
let canvas = document.querySelector("#my_canvas_id");
let c = canvas.getContext('2d');
```
### 15.8.1 Paths and Polygons
To draw lines on a canvas and to fill the areas enclosed by those lines, you begin by defining a path. A path is a sequence of one or more subpaths. A subpath is a sequence of two or more points connected by line segments (or, as we’ll see later, by curve segments). Begin a new path with the beginPath() method. Begin a new subpath with the moveTo() method. Once you have established the starting point of a subpath with moveTo(), you can connect that point to a new point with a straight line by calling lineTo(). The following code defines a path that includes two line segments:
```js
c.beginPath(); // Start a new path
c.moveTo(100, 100); // Begin a subpath at (100,100)
c.lineTo(200, 200); // Add a line from (100,100) to (200,200)
c.lineTo(100, 200); // Add a line from (200,200) to (100,200)
```
This code simply defines a path; it does not draw anything on the canvas. To draw (or “stroke”) the two line segments in the path, call the stroke() method, and to fill the area defined by those line segments, call fill():
```js
c.fill(); // Fill a triangular area
c.stroke(); // Stroke two sides of the triangle
```
This code (along with some additional code to set line widths and fill colors) produced the drawing shown in Figure 15-7.
A simple path, filled and stroked
Notice that the subpath defined in Figure 15-7 is “open.” It consists of just two line segments, and the end point is not connected back to the starting point. This means that it does not enclose a region. The fill() method fills open subpaths by acting as if a straight line connected the last point in the subpath to the first point in the subpath. That is why this code fills a triangle, but strokes only two sides of the triangle.
If you wanted to stroke all three sides of the triangle just shown, you would call the closePath() method to connect the end point of the subpath to the start point. (You could also call lineTo(100,100), but then you end up with three line segments that share a start and end point but are not truly closed. When drawing with wide lines, the visual results are better if you use closePath().)
There are two other important points to notice about stroke() and fill(). First, both methods operate on all subpaths in the current path. Suppose we had added another subpath in the preceding code:
```js
c.moveTo(300,100); // Begin a new subpath at (300,100);
c.lineTo(300,200); // Draw a vertical line down to (300,200);
```
If we then called stroke(), we would draw two connected edges of a triangle and a disconnected vertical line.
The second point to note about stroke() and fill() is that neither one alters the current path: you can call fill() and the path will still be there when you call stroke(). When you are done with a path and want to begin another, you must remember to call beginPath(). If you don’t, you’ll end up adding new subpaths to the existing path, and you may end up drawing those old subpaths over and over again.
Example 15-5 defines a function for drawing regular polygons and demonstrates the use of moveTo(), lineTo(), and closePath() for defining subpaths and of fill() and stroke() for drawing those paths. It produces the drawing shown in Figure 15-8.
Regular polygons
Example 15-5. Regular polygons with moveTo(), lineTo(), and closePath()
```js
// Define a regular polygon with n sides, centered at (x,y) with radius r.
// The vertices are equally spaced along the circumference of a circle.
// Put the first vertex straight up or at the specified angle.
// Rotate clockwise, unless the last argument is true.
function polygon(c, n, x, y, r, angle=0, counterclockwise=false) {
c.moveTo(x + r*Math.sin(angle), // Begin a new subpath at the first vertex
y - r*Math.cos(angle)); // Use trigonometry to compute position
let delta = 2*Math.PI/n; // Angular distance between vertices
for(let i = 1; i < n; i++) { // For each of the remaining vertices
angle += counterclockwise?-delta:delta; // Adjust angle
c.lineTo(x + r*Math.sin(angle), // Add line to next vertex
y - r*Math.cos(angle));
}
c.closePath(); // Connect last vertex back to the first
}
// Assume there is just one canvas, and get its context object to draw with.
let c = document.querySelector("canvas").getContext("2d");
// Start a new path and add polygon subpaths
c.beginPath();
polygon(c, 3, 50, 70, 50); // Triangle
polygon(c, 4, 150, 60, 50, Math.PI/4); // Square
polygon(c, 5, 255, 55, 50); // Pentagon
polygon(c, 6, 365, 53, 50, Math.PI/6); // Hexagon
polygon(c, 4, 365, 53, 20, Math.PI/4, true); // Small square inside the hexagon
// Set some properties that control how the graphics will look
c.fillStyle = "#ccc"; // Light gray interiors
c.strokeStyle = "#008"; // outlined with dark blue lines
c.lineWidth = 5; // five pixels wide.
// Now draw all the polygons (each in its own subpath) with these calls
c.fill(); // Fill the shapes
c.stroke(); // And stroke their outlines
```
Notice that this example draws a hexagon with a square inside it. The square and the hexagon are separate subpaths, but they overlap. When this happens (or when a single subpath intersects itself), the canvas needs to be able to determine which regions are inside the path and which are outside. The canvas uses a test known as the “nonzero winding rule” to achieve this. In this case, the interior of the square is not filled because the square and the hexagon were drawn in the opposite directions: the vertices of the hexagon were connected with line segments moving clockwise around the circle. The vertices of the square were connected counterclockwise. Had the square been drawn clockwise as well, the call to fill() would have filled the interior of the square as well.
### 15.8.2 Canvas Dimensions and Coordinates
The width and height attributes of the `` element and the corresponding width and height properties of the Canvas object specify the dimensions of the canvas. The default canvas coordinate system places the origin (0,0) at the upper-left corner of the canvas. The x coordinates increase to the right and the y coordinates increase as you go down the screen. Points on the canvas can be specified using floating-point values.
The dimensions of a canvas cannot be altered without completely resetting the canvas. Setting either the width or height properties of a Canvas (even setting them to their current value) clears the canvas, erases the current path, and resets all graphics attributes (including current transformation and clipping region) to their original state.
The width and height attributes of a canvas specify the actual number of pixels that the canvas can draw into. Four bytes of memory are allocated for each pixel, so if width and height are both set to 100, the canvas allocates 40,000 bytes to represent 10,000 pixels.
The width and height attributes also specify the default size (in CSS pixels) at which the canvas will be displayed on the screen. If window.devicePixelRatio is 2, then 100 × 100 CSS pixels is actually 40,000 hardware pixels. When the contents of the canvas are drawn onto the screen, the 10,000 pixels in memory will need to be enlarged to cover 40,000 physical pixels on the screen, and this means that your graphics will not be as crisp as they could be.
For optimum image quality, you should not use the width and height attributes to set the on-screen size of the canvas. Instead, set the desired on-screen size CSS pixel size of the canvas with CSS width and height style attributes. Then, before you begin drawing in your JavaScript code, set the width and height properties of the canvas object to the number of CSS pixels times window.devicePixelRatio. Continuing with the preceding example, this technique would result in the canvas being displayed at 100 × 100 CSS pixels but allocating memory for 200 × 200 pixels. (Even with this technique, the user can zoom in on the canvas and may see fuzzy or pixelated graphics if they do. This is in contrast to SVG graphics, which remain crisp no matter the on-screen size or zoom level.)
### 15.8.3 Graphics Attributes
Example 15-5 set the properties fillStyle, strokeStyle, and lineWidth on the context object of the canvas. These properties are graphics attributes that specify the color to be used by fill() and by stroke(), and the width of the lines to be drawn by stroke(). Notice that these parameters are not passed to the fill() and stroke() methods, but are instead part of the general graphics state of the canvas. If you define a method that draws a shape and do not set these properties yourself, the caller of your method can define the color of the shape by setting the strokeStyle and fillStyle properties before calling your method. This separation of graphics state from drawing commands is fundamental to the Canvas API and is akin to the separation of presentation from content achieved by applying CSS stylesheets to HTML documents.
There are a number of properties (and also some methods) on the context object that affect the graphics state of the canvas. They are detailed below.
LINE STYLES
The lineWidth property specifies how wide (in CSS pixels) the lines drawn by stroke() will be. The default value is 1. It is important to understand that line width is determined by the lineWidth property at the time stroke() is called, not at the time that lineTo() and other path-building methods are called. To fully understand the lineWidth property, it is important to visualize paths as infinitely thin one-dimensional lines. The lines and curves drawn by the stroke() method are centered over the path, with half of the lineWidth on either side. If you’re stroking a closed path and only want the line to appear outside the path, stroke the path first, then fill with an opaque color to hide the portion of the stroke that appears inside the path. Or if you only want the line to appear inside a closed path, call the save() and clip() methods first, then call stroke() and restore(). (The save(), restore(), and clip() methods are described later.)
When drawing lines that are more than about two pixels wide, the lineCap and lineJoin properties can have a significant impact on the visual appearance of the ends of a path and the vertices at which two path segments meet. Figure 15-9 illustrates the values and resulting graphical appearance of lineCap and lineJoin.
The lineCap and lineJoin attributes
The default value for lineCap is “butt.” The default value for lineJoin is “miter.” Note, however, that if two lines meet at a very narrow angle, then the resulting miter can become quite long and visually distracting. If the miter at a given vertex would be longer than half of the line width times the miterLimit property, that vertex will be drawn with a beveled join instead of a mitered join. The default value for miterLimit is 10.
The stroke() method can draw dashed and dotted lines as well as solid lines, and a canvas’s graphics state includes an array of numbers that serves as a “dash pattern” by specifying how many pixels to draw, then how many to omit. Unlike other line-drawing properties, the dash pattern is set and queried with the methods setLineDash() and getLineDash() instead of with a property. To specify a dotted dash pattern, you might use setLineDash() like this:
```js
c.setLineDash([18, 3, 3, 3]); // 18px dash, 3px space, 3px dot, 3px space
```
Finally, the lineDashOffset property specifies how far into the dash pattern drawing should begin. The default is 0. Paths stroked with the dash pattern shown here begin with an 18-pixel dash, but if lineDashOffset is set to 21, then that same path would begin with a dot followed by a space and a dash.
COLORS, PATTERNS, AND GRADIENTS
The fillStyle and strokeStyle properties specify how paths are filled and stroked. The word “style” often means color, but these properties can also be used to specify a color gradient or an image to be used for filling and stroking. (Note that drawing a line is basically the same as filling a narrow region on both sides of the line, and filling and stroking are fundamentally the same operation.)
If you want to fill or stroke with a solid color (or a translucent color), simply set these properties to a valid CSS color string. Nothing else is required.
To fill (or stroke) with a color gradient, set fillStyle (or strokeStyle) to a CanvasGradient object returned by the createLinearGradient() or createRadialGradient() methods of the context. The arguments to createLinearGradient() are the coordinates of two points that define a line (it does not need to be horizontal or vertical) along which the colors will vary. The arguments to createRadialGradient() specify the centers and radii of two circles. (They need not be concentric, but the first circle typically lies entirely inside the second.) Areas inside the smaller circle or outside the larger will be filled with solid colors; areas between the two will be filled with a color gradient.
After creating the CanvasGradient object that defines the regions of the canvas that will be filled, you must define the gradient colors by calling the addColorStop() method of the CanvasGradient. The first argument to this method is a number between 0.0 and 1.0. The second argument is a CSS color specification. You must call this method at least twice to define a simple color gradient, but you may call it more than that. The color at 0.0 will appear at the start of the gradient, and the color at 1.0 will appear at the end. If you specify additional colors, they will appear at the specified fractional position within the gradient. Between the points you specify, colors will be smoothly interpolated. Here are some examples:
```js
// A linear gradient, diagonally across the canvas (assuming no transforms)
let bgfade = c.createLinearGradient(0,0,canvas.width,canvas.height);
bgfade.addColorStop(0.0, "#88f"); // Start with light blue in upper left
bgfade.addColorStop(1.0, "#fff"); // Fade to white in lower right
// A gradient between two concentric circles. Transparent in the middle
// fading to translucent gray and then back to transparent.
let donut = c.createRadialGradient(300,300,100, 300,300,300);
donut.addColorStop(0.0, "transparent"); // Transparent
donut.addColorStop(0.7, "rgba(100,100,100,.9)"); // Translucent gray
donut.addColorStop(1.0, "rgba(0,0,0,0)"); // Transparent again
```
An important point to understand about gradients is that they are not position-independent. When you create a gradient, you specify bounds for the gradient. If you then attempt to fill an area outside of those bounds, you’ll get the solid color defined at one end or the other of the gradient.
In addition to colors and color gradients, you can also fill and stroke using images. To do this, set fillStyle or strokeStyle to a CanvasPattern returned by the createPattern() method of the context object. The first argument to this method should be an ` ` or `` element that contains the image you want to fill or stroke with. (Note that the source image or canvas does not need to be inserted into the document in order to be used in this way.) The second argument to createPattern() is the string “repeat,” “repeat-x,” “repeat-y,” or “no-repeat,” which specifies whether (and in which dimensions) the background images repeat.
TEXT STYLES
The font property specifies the font to be used by the text-drawing methods fillText() and strokeText() (see “Text”). The value of the font property should be a string in the same syntax as the CSS font attribute.
The textAlign property specifies how the text should be horizontally aligned with respect to the X coordinate passed to fillText() or strokeText(). Legal values are “start,” “left,” “center,” “right,” and “end.” The default is “start,” which, for left-to-right text, has the same meaning as “left.”
The textBaseline property specifies how the text should be vertically aligned with respect to the y coordinate. The default value is “alphabetic,” and it is appropriate for Latin and similar scripts. The value “ideographic” is intended for use with scripts such as Chinese and Japanese. The value “hanging” is intended for use with Devanagari and similar scripts (which are used for many of the languages of India). The “top,” “middle,” and “bottom” baselines are purely geometric baselines, based on the “em square” of the font.
SHADOWS
Four properties of the context object control the drawing of drop shadows. If you set these properties appropriately, any line, area, text, or image you draw will be given a shadow, which will make it appear as if it is floating above the canvas surface.
The shadowColor property specifies the color of the shadow. The default is fully transparent black, and shadows will never appear unless you set this property to a translucent or opaque color. This property can only be set to a color string: patterns and gradients are not allowed for shadows. Using a translucent shadow color produces the most realistic shadow effects because it allows the background to show through.
The shadowOffsetX and shadowOffsetY properties specify the X and Y offsets of the shadow. The default for both properties is 0, which places the shadow directly beneath your drawing, where it is not visible. If you set both properties to a positive value, shadows will appear below and to the right of what you draw, as if there were a light source above and to the left, shining onto the canvas from outside the computer screen. Larger offsets produce larger shadows and make drawn objects appear as if they are floating “higher” above the canvas. These values are not affected by coordinate transformations (§15.8.5): shadow direction and “height” remain consistent even when shapes are rotated and scaled.
The shadowBlur property specifies how blurred the edges of the shadow are. The default value is 0, which produces crisp, unblurred shadows. Larger values produce more blur, up to an implementation-defined upper bound.
TRANSLUCENCY AND COMPOSITING
If you want to stroke or fill a path using a translucent color, you can set strokeStyle or fillStyle using a CSS color syntax like “rgba(…)” that supports alpha transparency. The “a” in “RGBA” stands for “alpha” and is a value between 0 (fully transparent) and 1 (fully opaque). But the Canvas API provides another way to work with translucent colors. If you do not want to explicitly specify an alpha channel for each color, or if you want to add translucency to opaque images or patterns, you can set the globalAlpha property. Every pixel you draw will have its alpha value multiplied by globalAlpha. The default is 1, which adds no transparency. If you set globalAlpha to 0, everything you draw will be fully transparent, and nothing will appear in the canvas. But if you set this property to 0.5, then pixels that would otherwise have been opaque will be 50% opaque, and pixels that would have been 50% opaque will be 25% opaque instead.
When you stroke lines, fill regions, draw text, or copy images, you generally expect the new pixels to be drawn on top of the pixels that are already in the canvas. If you are drawing opaque pixels, they simply replace the pixels that are already there. If you are drawing with translucent pixels, the new (“source”) pixel is combined with the old (“destination”) pixel so that the old pixel shows through the new pixel based on how transparent that pixel is.
This process of combining new (possibly translucent) source pixels with existing (possibly translucent) destination pixels is called compositing, and the compositing process described previously is the default way that the Canvas API combines pixels. But you can set the globalCompositeOperation property to specify other ways of combining pixels. The default value is “source-over,” which means that source pixels are drawn “over” the destination pixels and are combined with them if the source is translucent. But if you set globalCompositeOperation to “destination-over”, then the canvas will combine pixels as if the new source pixels were drawn beneath the existing destination pixels. If the destination is translucent or transparent, some or all of the source pixel color is visible in the resulting color. As another example, the compositing mode “source-atop” combines the source pixels with the transparency of the destination pixels so that nothing is drawn on portions of the canvas that are already fully transparent. There are a number of legal values for globalCompositeOperation, but most have only specialized uses and are not covered here.
SAVING AND RESTORING GRAPHICS STATE
Since the Canvas API defines graphics attributes on the context object, you might be tempted to call getContext() multiple times to obtain multiple context objects. If you could do this, you could define different attributes on each context: each context would then be like a different brush and would paint with a different color or draw lines of different widths. Unfortunately, you cannot use the canvas in this way. Each `` element has only a single context object, and every call to getContext() returns the same CanvasRenderingContext2D object.
Although the Canvas API only allows you to define a single set of graphics attributes at a time, it does allow you to save the current graphics state so that you can alter it and then easily restore it later. The save() method pushes the current graphics state onto a stack of saved states. The restore() method pops the stack and restores the most recently saved state. All of the properties that have been described in this section are part of the saved state, as are the current transformation and clipping region (both of which are explained later). Importantly, the currently defined path and the current point are not part of the graphics state and cannot be saved and restored.
### 15.8.4 Canvas Drawing Operations
We’ve already seen some basic canvas methods—beginPath(), moveTo(), lineTo(), closePath(), fill(), and stroke()—for defining, filling, and drawing lines and polygons. But the Canvas API includes other drawing methods as well.
RECTANGLES
CanvasRenderingContext2D defines four methods for drawing rectangles. All four of these rectangle methods expect two arguments that specify one corner of the rectangle followed by the rectangle width and height. Normally, you specify the upper-left corner and then pass a positive width and positive height, but you may also specify other corners and pass negative dimensions.
fillRect() fills the specified rectangle with the current fillStyle. strokeRect() strokes the outline of the specified rectangle using the current strokeStyle and other line attributes. clearRect() is like fillRect(), but it ignores the current fill style and fills the rectangle with transparent black pixels (the default color of all blank canvases). The important thing about these three methods is that they do not affect the current path or the current point within that path.
The final rectangle method is named rect(), and it does affect the current path: it adds the specified rectangle, in a subpath of its own, to the path. Like other path-definition methods, it does not fill or stroke anything itself.
CURVES
A path is a sequence of subpaths, and a subpath is a sequence of connected points. In the paths we defined in §15.8.1, those points were connected with straight line segments, but that need not always be the case. The CanvasRenderingContext2D object defines a number of methods that add a new point to the subpath and connect the current point to that new point with a curve:
arc()
This method adds a circle, or a portion of a circle (an arc), to the path. The arc to be drawn is specified with six parameters: the x and y coordinates of the center of a circle, the radius of the circle, the start and end angles of the arc, and the direction (clockwise or counterclockwise) of the arc between those two angles. If there is a current point in the path, then this method connects the current point to the beginning of the arc with a straight line (which is useful when drawing wedges or pie slices), then connects the beginning of the arc to the end of the arc with a portion of a circle, leaving the end of the arc as the new current point. If there is no current point when this method is called, then it only adds the circular arc to the path.
ellipse()
This method is much like arc() except that it adds an ellipse or a portion of an ellipse to the path. Instead of one radius, it has two: an x-axis radius and a y-axis radius. Also, because ellipses are not radially symmetrical, this method takes another argument that specifies the number of radians by which the ellipse is rotated clockwise about its center.
arcTo()
This method draws a straight line and a circular arc just like the arc() method does, but it specifies the arc to be drawn using different parameters. The arguments to arcTo() specify points P1 and P2 and a radius. The arc that is added to the path has the specified radius. It begins at the tangent point with the (imaginary) line from the current point to P1 and ends at the tangent point with the (imaginary) line between P1 and P2. This unusual-seeming method of specifying arcs is actually quite useful for drawing shapes with rounded corners. If you specify a radius of 0, this method just draws a straight line from the current point to P1. With a nonzero radius, however, it draws a straight line from the current point in the direction of P1, then curves that line around in a circle until it is heading in the direction of P2.
bezierCurveTo()
This method adds a new point P to the subpath and connects it to the current point with a cubic Bezier curve. The shape of the curve is specified by two “control points,” C1 and C2. At the start of the curve (at the current point), the curve heads in the direction of C1. At the end of the curve (at point P), the curve arrives from the direction of C2. In between these points, the direction of the curve varies smoothly. The point P becomes the new current point for the subpath.
quadraticCurveTo()
This method is like bezierCurveTo(), but it uses a quadratic Bezier curve instead of a cubic Bezier curve and has only a single control point.
You can use these methods to draw paths like those in Figure 15-10.
Curved paths in a canvas
Example 15-6 shows the code used to create Figure 15-10. The methods demonstrated in this code are some of the most complicated in the Canvas API; consult an online reference for complete details on the methods and their arguments.
Example 15-6. Adding curves to a path
```js
// A utility function to convert angles from degrees to radians
function rads(x) { return Math.PI*x/180; }
// Get the context object of the document's canvas element
let c = document.querySelector("canvas").getContext("2d");
// Define some graphics attributes and draw the curves
c.fillStyle = "#aaa"; // Gray fills
c.lineWidth = 2; // 2-pixel black (by default) lines
// Draw a circle.
// There is no current point, so draw just the circle with no straight
// line from the current point to the start of the circle.
c.beginPath();
c.arc(75,100,50, // Center at (75,100), radius 50
0,rads(360),false); // Go clockwise from 0 to 360 degrees
c.fill(); // Fill the circle
c.stroke(); // Stroke its outline.
// Now draw an ellipse in the same way
c.beginPath(); // Start new path not connected to the circle
c.ellipse(200, 100, 50, 35, rads(15), // Center, radii, and rotation
0, rads(360), false); // Start angle, end angle, direction
// Draw a wedge. Angles are measured clockwise from the positive x axis.
// Note that arc() adds a line from the current point to the arc start.
c.moveTo(325, 100); // Start at the center of the circle.
c.arc(325, 100, 50, // Circle center and radius
rads(-60), rads(0), // Start at angle -60 and go to angle 0
true); // counterclockwise
c.closePath(); // Add radius back to the center of the circle
// Similar wedge, offset a bit, and in the opposite direction
c.moveTo(340, 92);
c.arc(340, 92, 42, rads(-60), rads(0), false);
c.closePath();
// Use arcTo() for rounded corners. Here we draw a square with
// upper left corner at (400,50) and corners of varying radii.
c.moveTo(450, 50); // Begin in the middle of the top edge.
c.arcTo(500,50,500,150,30); // Add part of top edge and upper right corner.
c.arcTo(500,150,400,150,20); // Add right edge and lower right corner.
c.arcTo(400,150,400,50,10); // Add bottom edge and lower left corner.
c.arcTo(400,50,500,50,0); // Add left edge and upper left corner.
c.closePath(); // Close path to add the rest of the top edge.
// Quadratic Bezier curve: one control point
c.moveTo(525, 125); // Begin here
c.quadraticCurveTo(550, 75, 625, 125); // Draw a curve to (625, 125)
c.fillRect(550-3, 75-3, 6, 6); // Mark the control point (550,75)
// Cubic Bezier curve
c.moveTo(625, 100); // Start at (625, 100)
c.bezierCurveTo(645,70,705,130,725,100); // Curve to (725, 100)
c.fillRect(645-3, 70-3, 6, 6); // Mark control points
c.fillRect(705-3, 130-3, 6, 6);
// Finally, fill the curves and stroke their outlines.
c.fill();
c.stroke();
```
TEXT
To draw text in a canvas, you normally use the fillText() method, which draws text using the color (or gradient or pattern) specified by the fillStyle property. For special effects at large text sizes, you can use strokeText() to draw the outline of the individual font glyphs. Both methods take the text to be drawn as their first argument and take the x and y coordinates of the text as the second and third arguments. Neither method affects the current path or the current point.
fillText() and strokeText() take an optional fourth argument. If given, this argument specifies the maximum width of the text to be displayed. If the text would be wider than the specified value when drawn using the font property, the canvas will make it fit by scaling it or by using a narrower or smaller font.
If you need to measure text yourself before drawing it, pass it to the measureText() method. This method returns a TextMetrics object that specifies the measurements of the text when drawn with the current font. At the time of this writing, the only “metric” contained in the TextMetrics object is the width. Query the on-screen width of a string like this:
let width = c.measureText(text).width;
This is useful if you want to center a string of text within a canvas, for example.
IMAGES
In addition to vector graphics (paths, lines, etc.), the Canvas API also supports bitmap images. The drawImage() method copies the pixels of a source image (or of a rectangle within the source image) onto the canvas, scaling and rotating the pixels of the image as necessary.
drawImage() can be invoked with three, five, or nine arguments. In all cases, the first argument is the source image from which pixels are to be copied. This image argument is often an ` ` element, but it can also be another `` element or even a `` element (from which a single frame will be copied). If you specify an ` ` or `` element that is still loading its data, the drawImage() call will do nothing.
In the three-argument version of drawImage(), the second and third arguments specify the x and y coordinates at which the upper-left corner of the image is to be drawn. In this version of the method, the entire source image is copied to the canvas. The x and y coordinates are interpreted in the current coordinate system, and the image is scaled and rotated if necessary, depending on the canvas transform currently in effect.
The five-argument version of drawImage() adds width and height arguments to the x and y arguments described earlier. These four arguments define a destination rectangle within the canvas. The upper-left corner of the source image goes at (x,y), and the lower-right corner goes at (x+width, y+height). Again, the entire source image is copied. With this version of the method, the source image will be scaled to fit the destination rectangle.
The nine-argument version of drawImage() specifies both a source rectangle and a destination rectangle and copies only the pixels within the source rectangle. Arguments two through five specify the source rectangle. They are measured in CSS pixels. If the source image is another canvas, the source rectangle uses the default coordinate system for that canvas and ignores any transformations that have been specified. Arguments six through nine specify the destination rectangle into which the image is drawn and are in the current coordinate system of the canvas, not in the default coordinate system.
In addition to drawing images into a canvas, we can also extract the content of a canvas as an image using the toDataURL() method. Unlike all the other methods described here, toDataURL() is a method of the Canvas element itself, not of the context object. You normally invoke toDataURL() with no arguments, and it returns the content of the canvas as a PNG image, encoded as a string using a data: URL. The returned URL is suitable for use with an ` ` element, and you can make a static snapshot of a canvas with code like this:
```js
let img = document.createElement("img"); // Create an element
img.src = canvas.toDataURL(); // Set its src attribute
document.body.appendChild(img); // Append it to the document
```
### 15.8.5 Coordinate System Transforms
As we’ve noted, the default coordinate system of a canvas places the origin in the upper-left corner, has x coordinates increasing to the right, and has y coordinates increasing downward. In this default system, the coordinates of a point map directly to a CSS pixel (which then maps directly to one or more device pixels). Certain canvas operations and attributes (such as extracting raw pixel values and setting shadow offsets) always use this default coordinate system. In addition to the default coordinate system, however, every canvas has a “current transformation matrix” as part of its graphics state. This matrix defines the current coordinate system of the canvas. In most canvas operations, when you specify the coordinates of a point, it is taken to be a point in the current coordinate system, not in the default coordinate system. The current transformation matrix is used to convert the coordinates you specified to the equivalent coordinates in the default coordinate system.
The setTransform() method allows you to set a canvas’s transformation matrix directly, but coordinate system transformations are usually easier to specify as a sequence of translations, rotations, and scaling operations. Figure 15-11 illustrates these operations and their effect on the canvas coordinate system. The program that produced the figure drew the same set of axes seven times in a row. The only thing that changed each time was the current transform. Notice that the transforms affect the text as well as the lines that are drawn.
Coordinate system transformations
The translate() method simply moves the origin of the coordinate system left, right, up, or down. The rotate() method rotates the axes clockwise by the specified angle. (The Canvas API always specifies angles in radians. To convert degrees to radians, divide by 180 and multiply by Math.PI.) The scale() method stretches or contracts distances along the x or y axes.
Passing a negative scale factor to the scale() method flips that axis across the origin, as if it were reflected in a mirror. This is what was done in the lower left of Figure 15-11: translate() was used to move the origin to the bottom-left corner of the canvas, then scale() was used to flip the y axis around so that y coordinates increase as we go up the page. A flipped coordinate system like this is familiar from algebra class and may be useful for plotting data points on charts. Note, however, that it makes text difficult to read!
UNDERSTANDING TRANSFORMATIONS MATHEMATICALLY
I find it easiest to understand transforms geometrically, thinking about translate(), rotate(), and scale() as transforming the axes of the coordinate system as illustrated in Figure 15-11. It is also possible to understand transforms algebraically as equations that map the coordinates of a point (x,y) in the transformed coordinate system back to the coordinates (x',y') of the same point in the previous coordinate system.
The method call c.translate(dx,dy) can be described with these equations:
```js
x' = x + dx; // An X coordinate of 0 in the new system is dx in the old
y' = y + dy;
```
Scaling operations have similarly simple equations. A call c.scale(sx,sy) can be described like this:
```js
x' = sx * x;
y' = sy * y;
```
Rotations are more complicated. The call c.rotate(a) is described by these trigonometric equations:
```js
x' = x * cos(a) - y * sin(a);
y' = y * cos(a) + x * sin(a);
```
Notice that the order of transformations matters. Suppose we start with the default coordinate system of a canvas, then translate it, and then scale it. In order to map the point (x,y) in the current coordinate system back to the point (x'',y'') in the default coordinate system, we must first apply the scaling equations to map the point to an intermediate point (x',y') in the translated but unscaled coordinate system, then use the translation equations to map from this intermediate point to (x'',y''). The result is this:
```js
x'' = sx*x + dx;
y'' = sy*y + dy;
```
If, on the other hand, we’d called scale() before calling translate(), the resulting equations would be different:
```js
x'' = sx*(x + dx);
y'' = sy*(y + dy);
```
The key thing to remember when thinking algebraically about sequences of transformations is that you must work backward from the last (most recent) transformation to the first. When thinking geometrically about transformed axes, however, you work forward from first transformation to last.
The transformations supported by the canvas are known as affine transforms. Affine transforms may modify the distances between points and the angles between lines, but parallel lines always remain parallel after an affine transformation—it is not possible, for example, to specify a fish-eye lens distortion with an affine transform. An arbitrary affine transform can be described by the six parameters a through f in these equations:
```js
x' = ax + cy + e
y' = bx + dy + f
```
You can apply an arbitrary transformation to the current coordinate system by passing those six parameters to the transform() method. Figure 15-11 illustrates two types of transformations—shears and rotations about a specified point—that you can implement with the transform() method like this:
```js
// Shear transform:
// x' = x + kx*y;
// y' = ky*x + y;
function shear(c, kx, ky) { c.transform(1, ky, kx, 1, 0, 0); }
// Rotate theta radians counterclockwise around the point (x,y)
// This can also be accomplished with a translate, rotate, translate sequence
function rotateAbout(c, theta, x, y) {
let ct = Math.cos(theta);
let st = Math.sin(theta);
c.transform(ct, -st, st, ct, -x*ct-y*st+x, x*st-y*ct+y);
}
```
The setTransform() method takes the same arguments as transform(), but instead of transforming the current coordinate system, it ignores the current system, transforms the default coordinate system, and makes the result the new current coordinate system. setTransform() is useful to temporarily reset the canvas to its default coordinate system:
```js
c.save(); // Save current coordinate system
c.setTransform(1,0,0,1,0,0); // Revert to the default coordinate system
// Perform operations using default CSS pixel coordinates
c.restore(); // Restore the saved coordinate system
```
TRANSFORMATION EXAMPLE
Example 15-7 demonstrates the power of coordinate system transformations by using the translate(), rotate(), and scale() methods recursively to draw a Koch snowflake fractal. The output of this example appears in Figure 15-12, which shows Koch snowflakes with 0, 1, 2, 3, and 4 levels of recursion.
Koch snowflakes
The code that produces these figures is elegant, but its use of recursive coordinate system transformations makes it somewhat difficult to understand. Even if you don’t follow all the nuances, note that the code includes only a single invocation of the lineTo() method. Every single line segment in Figure 15-12 is drawn like this:
```js
c.lineTo(len, 0);
```
The value of the variable len does not change during the execution of the program, so the position, orientation, and length of each of the line segments is determined by translations, rotations, and scaling operations.
Example 15-7. A Koch snowflake with transformations
```js
let deg = Math.PI/180; // For converting degrees to radians
// Draw a level-n Koch snowflake fractal on the canvas context c,
// with lower-left corner at (x,y) and side length len.
function snowflake(c, n, x, y, len) {
c.save(); // Save current transformation
c.translate(x,y); // Translate origin to starting point
c.moveTo(0,0); // Begin a new subpath at the new origin
leg(n); // Draw the first leg of the snowflake
c.rotate(-120*deg); // Now rotate 120 degrees counterclockwise
leg(n); // Draw the second leg
c.rotate(-120*deg); // Rotate again
leg(n); // Draw the final leg
c.closePath(); // Close the subpath
c.restore(); // And restore original transformation
// Draw a single leg of a level-n Koch snowflake.
// This function leaves the current point at the end of the leg it has
// drawn and translates the coordinate system so the current point is (0,0).
// This means you can easily call rotate() after drawing a leg.
function leg(n) {
c.save(); // Save the current transformation
if (n === 0) { // Nonrecursive case:
c.lineTo(len, 0); // Just draw a horizontal line
} // _ _
else { // Recursive case: draw 4 sub-legs like: \/
c.scale(1/3,1/3); // Sub-legs are 1/3 the size of this leg
leg(n-1); // Recurse for the first sub-leg
c.rotate(60*deg); // Turn 60 degrees clockwise
leg(n-1); // Second sub-leg
c.rotate(-120*deg); // Rotate 120 degrees back
leg(n-1); // Third sub-leg
c.rotate(60*deg); // Rotate back to our original heading
leg(n-1); // Final sub-leg
}
c.restore(); // Restore the transformation
c.translate(len, 0); // But translate to make end of leg (0,0)
}
}
let c = document.querySelector("canvas").getContext("2d");
snowflake(c, 0, 25, 125, 125); // A level-0 snowflake is a triangle
snowflake(c, 1, 175, 125, 125); // A level-1 snowflake is a 6-sided star
snowflake(c, 2, 325, 125, 125); // etc.
snowflake(c, 3, 475, 125, 125);
snowflake(c, 4, 625, 125, 125); // A level-4 snowflake looks like a snowflake!
c.stroke(); // Stroke this very complicated path
```
### 15.8.6 Clipping
After defining a path, you usually call stroke() or fill() (or both). You can also call the clip() method to define a clipping region. Once a clipping region is defined, nothing will be drawn outside of it. Figure 15-13 shows a complex drawing produced using clipping regions. The vertical stripe running down the middle and the text along the bottom of the figure were stroked with no clipping region and then filled after the triangular clipping region was defined.
Unclipped strokes and clipped fills
Figure 15-13 was generated using the polygon() method of Example 15-5 and the following code:
```js
// Define some drawing attributes
c.font = "bold 60pt sans-serif"; // Big font
c.lineWidth = 2; // Narrow lines
c.strokeStyle = "#000"; // Black lines
// Outline a rectangle and some text
c.strokeRect(175, 25, 50, 325); // A vertical stripe down the middle
c.strokeText("", 15, 330); // Note strokeText() instead of fillText()
// Define a complex path with an interior that is outside.
polygon(c,3,200,225,200); // Large triangle
polygon(c,3,200,225,100,0,true); // Smaller reverse triangle inside
// Make that path the clipping region.
c.clip();
// Stroke the path with a 5 pixel line, entirely inside the clipping region.
c.lineWidth = 10; // Half of this 10 pixel line will be clipped away
c.stroke();
// Fill the parts of the rectangle and text that are inside the clipping region
c.fillStyle = "#aaa"; // Light gray
c.fillRect(175, 25, 50, 325); // Fill the vertical stripe
c.fillStyle = "#888"; // Darker gray
c.fillText("", 15, 330); // Fill the text
```
It is important to note that when you call clip(), the current path is itself clipped to the current clipping region, then that clipped path becomes the new clipping region. This means that the clip() method can shrink the clipping region but can never enlarge it. There is no method to reset the clipping region, so before calling clip(), you should typically call save() so that you can later restore() the unclipped region.
### 15.8.7 Pixel Manipulation
The getImageData() method returns an ImageData object that represents the raw pixels (as R, G, B, and A components) from a rectangular region of your canvas. You can create empty ImageData objects with createImageData(). The pixels in an ImageData object are writable, so you can set them any way you want, then copy those pixels back onto the canvas with putImageData().
These pixel manipulation methods provide very low-level access to the canvas. The rectangle you pass to getImageData() is in the default coordinate system: its dimensions are measured in CSS pixels, and it is not affected by the current transformation. When you call putImageData(), the position you specify is also measured in the default coordinate system. Furthermore, putImageData() ignores all graphics attributes. It does not perform any compositing, it does not multiply pixels by globalAlpha, and it does not draw shadows.
Pixel manipulation methods are useful for implementing image processing. Example 15-8 shows how to create a simple motion blur or “smear” effect like that shown in Figure 15-14.
A motion blur effect created by image processing
The following code demonstrates getImageData() and putImageData() and shows how to iterate through and modify the pixel values in an ImageData object.
Example 15-8. Motion blur with ImageData
```js
// Smear the pixels of the rectangle to the right, producing a
// sort of motion blur as if objects are moving from right to left.
// n must be 2 or larger. Larger values produce bigger smears.
// The rectangle is specified in the default coordinate system.
function smear(c, n, x, y, w, h) {
// Get the ImageData object that represents the rectangle of pixels to smear
let pixels = c.getImageData(x, y, w, h);
// This smear is done in-place and requires only the source ImageData.
// Some image processing algorithms require an additional ImageData to
// store transformed pixel values. If we needed an output buffer, we could
// create a new ImageData with the same dimensions like this:
// let output_pixels = c.createImageData(pixels);
// Get the dimensions of the grid of pixels in the ImageData object
let width = pixels.width, height = pixels.height;
// This is the byte array that holds the raw pixel data, left-to-right and
// top-to-bottom. Each pixel occupies 4 consecutive bytes in R,G,B,A order.
let data = pixels.data;
// Each pixel after the first in each row is smeared by replacing it with
// 1/nth of its own value plus m/nths of the previous pixel's value
let m = n-1;
for(let row = 0; row < height; row++) { // For each row
let i = row*width*4 + 4; // The offset of the second pixel of the row
for(let col = 1; col < width; col++, i += 4) { // For each column
data[i] = (data[i] + data[i-4]*m)/n; // Red pixel component
data[i+1] = (data[i+1] + data[i-3]*m)/n; // Green
data[i+2] = (data[i+2] + data[i-2]*m)/n; // Blue
data[i+3] = (data[i+3] + data[i-1]*m)/n; // Alpha component
}
}
// Now copy the smeared image data back to the same position on the canvas
c.putImageData(pixels, x, y);
}
```
## 15.9 Audio APIs
The HTML `` and `` tags allow you to easily include sound and videos in your web pages. These are complex elements with significant APIs and nontrivial user interfaces. You can control media playback with the play() and pause() methods. You can set the volume and playbackRate properties to control the audio volume and speed of playback. And you can skip to a particular time within the media by setting the currentTime property.
We will not cover `` and `` tags in any further detail here, however. The following subsections demonstrate two ways to add scripted sound effects to your web pages.
### 15.9.1 The Audio() Constructor
You don’t have to include an `` tag in your HTML document in order to include sound effects in your web pages. You can dynamically create `` elements with the normal DOM document.createElement() method, or, as a shortcut, you can simply use the Audio() constructor. You do not have to add the created element to your document in order to play it. You can simply call its play() method:
```js
// Load the sound effect in advance so it is ready for use
let soundeffect = new Audio("soundeffect.mp3");
// Play the sound effect whenever the user clicks the mouse button
document.addEventListener("click", () => {
soundeffect.cloneNode().play(); // Load and play the sound
});
```
Note the use of cloneNode() here. If the user clicks the mouse rapidly, we want to be able to have multiple overlapping copies of the sound effect playing at the same time. To do that, we need multiple Audio elements. Because the Audio elements are not added to the document, they will be garbage collected when they are done playing.
### 15.9.2 The WebAudio API
In addition to playback of recorded sounds with Audio elements, web browsers also allow the generation and playback of synthesized sounds with the WebAudio API. Using the WebAudio API is like hooking up an old-style electronic synthesizer with patch cords. With WebAudio, you create a set of AudioNode objects, which represents sources, transformations, or destinations of waveforms, and then connect these nodes together into a network to produce sounds. The API is not particularly complex, but a full explanation requires an understanding of electronic music and signal processing concepts that are beyond the scope of this book.
The following code below uses the WebAudio API to synthesize a short chord that fades out over about a second. This example demonstrates the basics of the WebAudio API. If this is interesting to you, you can find much more about this API online:
```js
// Begin by creating an audioContext object. Safari still requires
// us to use webkitAudioContext instead of AudioContext.
let audioContext = new (this.AudioContext||this.webkitAudioContext)();
// Define the base sound as a combination of three pure sine waves
let notes = [ 293.7, 370.0, 440.0 ]; // D major chord: D, F# and A
// Create oscillator nodes for each of the notes we want to play
let oscillators = notes.map(note => {
let o = audioContext.createOscillator();
o.frequency.value = note;
return o;
});
// Shape the sound by controlling its volume over time.
// Starting at time 0 quickly ramp up to full volume.
// Then starting at time 0.1 slowly ramp down to 0.
let volumeControl = audioContext.createGain();
volumeControl.gain.setTargetAtTime(1, 0.0, 0.02);
volumeControl.gain.setTargetAtTime(0, 0.1, 0.2);
// We're going to send the sound to the default destination:
// the user's speakers
let speakers = audioContext.destination;
// Connect each of the source notes to the volume control
oscillators.forEach(o => o.connect(volumeControl));
// And connect the output of the volume control to the speakers.
volumeControl.connect(speakers);
// Now start playing the sounds and let them run for 1.25 seconds.
let startTime = audioContext.currentTime;
let stopTime = startTime + 1.25;
oscillators.forEach(o => {
o.start(startTime);
o.stop(stopTime);
});
// If we want to create a sequence of sounds we can use event handlers
oscillators[0].addEventListener("ended", () => {
// This event handler is invoked when the note stops playing
});
```
## 15.10 Location, Navigation, and History
The location property of both the Window and Document objects refers to the Location object, which represents the current URL of the document displayed in the window, and which also provides an API for loading new documents into the window.
The Location object is very much like a URL object (§11.9), and you can use properties like protocol, hostname, port, and path to access the various parts of the URL of the current document. The href property returns the entire URL as a string, as does the toString() method.
The hash and search properties of the Location object are interesting ones. The hash property returns the “fragment identifier” portion of the URL, if there is one: a hash mark (#) followed by an element ID. The search property is similar. It returns the portion of the URL that starts with a question mark: often some sort of query string. In general, this portion of a URL is used to parameterize the URL and provides a way to embed arguments in it. While these arguments are usually intended for scripts run on a server, there is no reason why they cannot also be used in JavaScript-enabled pages.
URL objects have a searchParams property that is a parsed representation of the search property. The Location object does not have a searchParams property, but if you want to parse window.location.search, you can simply create a URL object from the Location object and then use the URL’s searchParams:
```js
let url = new URL(window.location);
let query = url.searchParams.get("q");
let numResults = parseInt(url.searchParams.get("n") || "10");
```
In addition to the Location object that you can refer to as window.location or document.location, and the URL() constructor that we used earlier, browsers also define a document.URL property. Surprisingly, the value of this property is not a URL object, but just a string. The string holds the URL of the current document.
### 15.10.1 Loading New Documents
If you assign a string to window.location or to document.location, that string is interpreted as a URL and the browser loads it, replacing the current document with a new one:
```js
window.location = "http://www.oreilly.com"; // Go buy some books!
```
You can also assign relative URLs to location. They are resolved relative to the current URL:
```js
document.location = "page2.html"; // Load the next page
```
A bare fragment identifier is a special kind of relative URL that does not cause the browser to load a new document but simply to scroll so that the document element with id or name that matches the fragment is visible at the top of the browser window. As a special case, the fragment identifier #top makes the browser jump to the start of the document (assuming no element has an id="top" attribute):
```js
location = "#top"; // Jump to the top of the document
```
The individual properties of the Location object are writable, and setting them changes the location URL and also causes the browser to load a new document (or, in the case of the hash property, to navigate within the current document):
```js
document.location.path = "pages/3.html"; // Load a new page
document.location.hash = "TOC"; // Scroll to the table of contents
location.search = "?page=" + (page+1); // Reload with new query string
```
You can also load a new page by passing a new string to the assign() method of the Location object. This is the same as assigning the string to the location property, however, so it’s not particularly interesting.
The replace() method of the Location object, on the other hand, is quite useful. When you pass a string to replace(), it is interpreted as a URL and causes the browser to load a new page, just as assign() does. The difference is that replace() replaces the current document in the browser’s history. If a script in document A sets the location property or calls assign() to load document B and then the user clicks the Back button, the browser will go back to document A. If you use replace() instead, then document A is erased from the browser’s history, and when the user clicks the Back button, the browser returns to whatever document was displayed before document A.
When a script unconditionally loads a new document, the replace() method is a better choice than assign(). Otherwise, the Back button would take the browser back to the original document, and the same script would again load the new document. Suppose you have a JavaScript-enhanced version of your page and a static version that does not use JavaScript. If you determine that the user’s browser does not support the web platform APIs that you want to use, you could use location.replace() to load the static version:
```js
// If the browser does not support the JavaScript APIs we need,
// redirect to a static page that does not use JavaScript.
if (!isBrowserSupported()) location.replace("staticpage.html");
```
Notice that the URL passed to replace() is a relative one. Relative URLs are interpreted relative to the page in which they appear, just as they would be if they were used in a hyperlink.
In addition to the assign() and replace() methods, the Location object also defines reload(), which simply makes the browser reload the document.
### 15.10.2 Browsing History
The history property of the Window object refers to the History object for the window. The History object models the browsing history of a window as a list of documents and document states. The length property of the History object specifies the number of elements in the browsing history list, but for security reasons, scripts are not allowed to access the stored URLs. (If they could, any scripts could snoop through your browsing history.)
The History object has back() and forward() methods that behave like the browser’s Back and Forward buttons do: they make the browser go backward or forward one step in its browsing history. A third method, go(), takes an integer argument and can skip any number of pages forward (for positive arguments) or backward (for negative arguments) in the history list:
```js
history.go(-2); // Go back 2, like clicking the Back button twice
history.go(0); // Another way to reload the current page
```
If a window contains child windows (such as `