# JavaScript 权威指南第七版（GPT 重译）（六）(3)

### 15.8.4 画布绘图操作

#### 矩形

CanvasRenderingContext2D 定义了四种绘制矩形的方法。这四种矩形方法都需要两个参数，指定矩形的一个角，然后是矩形的宽度和高度。通常，您指定左上角，然后传递正宽度和正高度，但也可以指定其他角并传递负尺寸。

fillRect() 使用当前的fillStyle填充指定的矩形。strokeRect() 使用当前的strokeStyle和其他线条属性描绘指定矩形的轮廓。clearRect() 类似于fillRect()，但它忽略当前的填充样式，并用透明黑色像素（所有空画布的默认颜色）填充矩形。这三种方法的重要之处在于它们不会影响当前路径或路径中的当前点。

#### 曲线

arc()

ellipse()

arcTo()

bezierCurveTo()

quadraticCurveTo()

###### 示例 15-6。向路径添加曲线
// 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
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
// Similar wedge, offset a bit, and in the opposite direction
c.moveTo(340, 92);
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();

#### 文本

fillText()strokeText()接受一个可选的第四个参数。如果提供了这个参数，则指定要显示的文本的最大宽度。如果使用font属性绘制的文本宽度超过指定值，画布将通过缩放或使用更窄或更小的字体来适应它。

let width = c.measureText(text).width;

#### 图像

drawImage()可以使用三、五或九个参数调用。在所有情况下，第一个参数都是要复制像素的源图像。这个图像参数通常是一个元素，但也可以是另一个元素，甚至是一个 元素（从中将复制一帧）。如果指定的 元素仍在加载数据，则drawImage()调用将不起作用。

drawImage()的三参数版本中，第二个和第三个参数指定要绘制图像左上角的xy坐标。在此方法的版本中，整个源图像都会被复制到画布上。xy坐标在当前坐标系中解释，并且根据当前生效的画布变换，必要时会对图像进行缩放和旋转。

drawImage()的五参数版本在前述的xy参数中添加了widthheight参数。这四个参数定义了画布内的目标矩形。源图像的左上角位于(x,y)，右下角位于(x+width, y+height)。同样，整个源图像都会被复制。使用此方法的版本，源图像将被缩放以适应目标矩形。

drawImage()的九参数版本同时指定源矩形和目标矩形，并仅复制源矩形内的像素。第二至第五个参数指定源矩形，它们以 CSS 像素为单位。如果源图像是另一个画布，则源矩形使用该画布的默认坐标系，并忽略已指定的任何变换。第六至第九个参数指定将绘制图像的目标矩形，并且以画布的当前坐标系而不是默认坐标系为准。

let img = document.createElement("img");  // Create an <img> element
img.src = canvas.toDataURL();             // Set its src attribute
document.body.appendChild(img);           // Append it to the document

### 15.8.5 坐标系变换

setTransform() 方法允许您直接设置画布的变换矩阵，但坐标系变换通常更容易指定为一系列平移、旋转和缩放操作。图 15-11 说明了这些操作及其对画布坐标系的影响。生成该图的程序连续七次绘制了相同的坐标轴。每次变化的唯一事物是当前变换。请注意，变换不仅影响绘制的线条，还影响文本。

###### 图 15-11. 坐标系变换

translate() 方法简单地将坐标系的原点向左、向右、向上或向下移动。rotate() 方法按指定角度顺时针旋转坐标轴。（Canvas API 总是用弧度指定角度。要将度数转换为弧度，除以 180 并乘以 Math.PI。）scale() 方法沿着 xy 轴拉伸或收缩距离。

#### 数学上理解变换

x' = x + dx;  // An X coordinate of 0 in the new system is dx in the old
y' = y + dy;

x' = sx * x;
y' = sy * y;

x' = x * cos(a) - y * sin(a);
y' = y * cos(a) + x * sin(a);

x'' = sx*x + dx;
y'' = sy*y + dy;

x'' = sx*(x + dx);
y'' = sy*(y + dy);

x' = ax + cy + e
y' = bx + dy + f

// 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);
}

setTransform()方法接受与transform()相同的参数，但是不是转换当前坐标系，而是忽略当前系统，转换默认坐标系，并使结果成为新的当前坐标系。 setTransform()对于临时将画布重置为其默认坐标系很有用：

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

#### 变换示例

###### 图 15-12. 科赫雪花

c.lineTo(len, 0);

###### 示例 15-7. 具有变换的科赫雪花
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 裁剪

###### 图 15-13. 未裁剪的笔画和裁剪的填充

// 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("<canvas>", 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("<canvas>", 15, 330);  // Fill the text

### 15.8.7 像素处理

getImageData()方法返回一个表示画布矩形区域的原始像素（作为 R、G、B 和 A 分量）的 ImageData 对象。您可以使用createImageData()创建空的ImageData对象。ImageData 对象中的像素是可写的，因此您可以按照自己的方式设置它们，然后使用putImageData()将这些像素复制回画布。

###### 示例 15-8. 使用 ImageData 进行运动模糊
// 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 音频 API

HTML  标签允许您轻松地在网页中包含声音和视频。这些是具有重要 API 和复杂用户界面的复杂元素。您可以使用play()pause()方法控制媒体播放。您可以设置volumeplaybackRate属性来控制音频音量和播放速度。您可以通过设置currentTime属性跳转到媒体中的特定时间。

### 15.9.1 Audio()构造函数

// 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
soundeffect.cloneNode().play(); // Load and play the sound
});

### 15.9.2 WebAudio API

// 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
// This event handler is invoked when the note stops playing
});

## 15.10 位置、导航和历史

location属性既可以用于 Window 对象，也可以用于 Document 对象，它指的是 Location 对象，代表着窗口中显示的文档的当前 URL，并提供了一个 API 用于在窗口中加载新文档。

Location 对象非常类似于 URL 对象（§11.9），你可以使用protocolhostnameportpath等属性来访问当前文档的 URL 的各个部分。href属性返回整个 URL 作为字符串，toString()方法也是如此。

Location 对象的hashsearch属性是比较有趣的。hash属性返回 URL 中的“片段标识符”部分，如果有的话：一个井号(#)后跟一个元素 ID。search属性类似。它返回以问号开头的 URL 部分：通常是某种查询字符串。一般来说，URL 的这部分用于对 URL 进行参数化，并提供了一种在其中嵌入参数的方式。虽然这些参数通常是为在服务器上运行的脚本而设计的，但也可以在启用 JavaScript 的页面中使用。

URL 对象有一个searchParams属性，它是search属性的解析表示。Location 对象没有searchParams属性，但如果你想解析window.location.search，你可以简单地从 Location 对象创建一个 URL 对象，然后使用 URL 的searchParams

let url = new URL(window.location);
let query = url.searchParams.get("q");
let numResults = parseInt(url.searchParams.get("n") || "10");

### 15.10.1 加载新文档

window.location = "http://www.oreilly.com"; // Go buy some books!

document.location = "page2.html";           // Load the next page

location = "#top";                          // Jump to the top of the document

Location 对象的各个属性都是可写的，设置它们会改变位置 URL，并导致浏览器加载一个新文档（或者在hash属性的情况下，在当前文档内导航）：

document.location.path = "pages/3.html"; // Load a new page
location.search = "?page=" + (page+1);   // Reload with new query string

// 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");

### 15.10.2 浏览历史

Window 对象的 history 属性指的是窗口的 History 对象。History 对象将窗口的浏览历史建模为文档和文档状态的列表。History 对象的 length 属性指定浏览历史列表中的元素数量，但出于安全原因，脚本不允许访问存储的 URL。（如果允许，任何脚本都可以窥探您的浏览历史。）

History 对象有 back()forward() 方法，行为类似于浏览器的返回和前进按钮：它们使浏览器在其浏览历史中向后或向前移动一步。第三个方法 go() 接受一个整数参数，可以在历史记录列表中跳过任意数量的页面向前（对于正参数）或向后（对于负参数）：

history.go(-2);   // Go back 2, like clicking the Back button twice
history.go(0);    // Another way to reload the current page

JavaScript 权威指南第七版（GPT 重译）（六）(4)https://developer.aliyun.com/article/1485428

|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（七）(4)
JavaScript 权威指南第七版（GPT 重译）（七）
29 0
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（七）(3)
JavaScript 权威指南第七版（GPT 重译）（七）
43 0
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（七）(2)
JavaScript 权威指南第七版（GPT 重译）（七）
43 0
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（七）(1)
JavaScript 权威指南第七版（GPT 重译）（七）
70 0
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（六）(4)
JavaScript 权威指南第七版（GPT 重译）（六）
128 3
|
7天前
|
XML 前端开发 JavaScript
JavaScript 权威指南第七版（GPT 重译）（六）(2)
JavaScript 权威指南第七版（GPT 重译）（六）
69 4
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（六）(1)
JavaScript 权威指南第七版（GPT 重译）（六）
34 3
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（五）(4)
JavaScript 权威指南第七版（GPT 重译）（五）
43 9
|
7天前
|

JavaScript 权威指南第七版（GPT 重译）（五）(3)
JavaScript 权威指南第七版（GPT 重译）（五）
40 8
|
7天前
|
JSON 前端开发 JavaScript
JavaScript 权威指南第七版（GPT 重译）（五）(2)
JavaScript 权威指南第七版（GPT 重译）（五）
53 5