今天把 MDN 最后的一点 JS 教程看完
1. 第三方 API
没什么好讲的,看文档
2. canvas
2.1 画矩形
先用fillStyle确定颜色,再用fillRect画矩形。其中fill是实心的意思,对应的是strokeRect为边框矩形。
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fillRect(1, 2, 1000, 1000); // 从左上坐标(1, 2)开始,画一个宽1000px,高1000px的矩形。
// 宽方向往右,高方向向下。
// 如果为负数,则方向相反
// 颜色为fillStyle指定的颜色,不会受到strokeStyle影响。
ctx.lineWidth = 10; // 指定 stroke 的边框的厚度
ctx.strokeRect(1, 2, -200, -200) // 和 fillRect 一样,只是只有边框
2.2 画文字
文字也分 fillText 和 strokeText,指定颜色用的属性也和矩形一样。
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.font = "50px arial" // 指定大小和字体
ctx.fillText("你是谁", 100, 200) // 第一个参数为文字内容,后两个为左上顶点坐标
ctx.strokeText("你是谁", 100, 200) // 空心文字
2.3 画路径
有时候要画的并不是规则形状,或者自定义形状,就可以用路径。
- 画路径前也可以用 fillStyle 和 strokeStyle 定义颜色。
- beginPath() 表示绘制开始。
- moveTo() 表示移动到某点但不绘制,单纯瞬移,起始点就用这个写出来。
- LineTo() 表示从现在所在点画一条线到某点。
- fill() 或者 stroke() 填充路径
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.moveTo(0, 0); // 起始点
ctx.lineTo(100, 0); // 画一条到(100, 0)的线
ctx.lineTo(100, 100);
ctx.lineTo(0, 100);
ctx.fill(); // 填充,画了一个正方形
// 填充画过的图形,如果最后一个点不在起点,就会自动在起点和终点间连线再填充。
ctx.beginPath(); // 如果不声明,那么下次绘制会包含前面的路径。
// 比如假设上次画了一个实心矩形,下次想画个空心三角形。
// 即使你在别的地方画三角,当你 stroke 的时候,前面的矩形周边也会被边框包围。
// 这样倒是一个画带边框图形的技巧(
ctx.moveTo(100, 0);
ctx.lineTo(100, 100);
ctx.lineTo(0, 100);
ctx.stroke(); // 虽然是画边框,但是和 fill 区别比较大,可以理解为相当于把之前 lineTo 声明的线画出来
// 不会自动连接起点和终点,想要画正方形就得把4条线全声明出来。可以用来画有起点和终点的折线。
以上代码画出的图:
- 实心没什么好说的。
- 蓝色边框可以看到只有右边和下边有,因为只画了两条 lineTo(),那么自然也只会画两条线。
2.4 画弧形/圆形
- 弧形的 arc 方法其实属于路径,因此需要 beginPath()
ctx.beginPath();
ctx.arc(100, 100, 30, 0, Math.PI, true); // 前三个参数表示圆心和半径,接着两个参数表示弧形跨越的角度
// (用弧度表示,π 表示180°也就是半圆)
// 最后一个参数表示是否逆时针,默认为 false,即顺时针
ctx.fill() // 填充路径,形成半圆
不过当弧度非半圆和整圆的时候,画的图形和想当然的并不一样,看下面的例子
ctx.beginPath();
ctx.arc(100, 100, 30, 0, Math.PI*0.5); // 想当然的会觉得这个会画1/4的圆,但是并不是,而是下面这样
为了画出 1/4 圆,应该在声明终点后,画一条移动到圆心的线,这样子终点就会在圆心,因此canvas自动连接起点和终点就会画出一个 1/4 圆。
ctx.beginPath();
ctx.arc(100, 100, 30, 0, Math.PI*0.5);
ctx.lineTo(100, 100); // 画一条这个线
ctx.fill();
如果此时用 stroke() ,会变成下面这样
因为 stroke() 不会连接起点和终点,想要画空心 1/4 圆,应该在圆心和起点处画一条线(或者说此时起点就是圆心了)
ctx.beginPath();
ctx.arc(100, 100, 30, 0, Math.PI*0.5);
ctx.lineTo(100, 100);
ctx.lineTo(130, 100); // 手动封闭起点和终点
ctx.stroke();
2.5 画图片
使用 drawImage,用法在下面,有三种参数表示方式
let img = new Image();
img.src = "./balabala.jpg"
img.onload = () => {
// 无法裁剪,无法改变大小,只能声明位置
ctx.drawImage(img, dx, dy);
// 无法裁剪,可以改变大小(拉伸)
ctx.drawImage(img, dx, dy, dWidth, dHeight);
// 可以裁剪
ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeigh)
}
2.6 其他学到的东西
ctx.translate()可以改变画布原点。如果想从左上角画背景,fillRect 的前两个参数设置为负值到原点即可。- 和 canvas 无关,不过
input[type="range"]有一个 oninput 监听函数,可以用来实时显示 range 的数字。
3. 视频和音频 API
3.1 图标字体
这是一种字体。图标字体里所有的字符都是常用的图标.
用图标字体的好处是:
- 不用下载图片,可以减少 http 请求。
- 可拓展性高,字体可以用
color,text-shadow等属性 可以在 html 元素里添加data-icon属性,然后在 css 中使用伪元素里的content: attr(data-icon);获得值,然后声明 font-family 为图标字体。
3.2 API 介绍
HTMLVideoElement 统一用 media 表示
audio 和 video 用法十分相似。
let media = document.querySelector("video");
3.2.1 播放/暂停
- media.paused 为布尔值表示是否暂停。
- media.play() 和 media.pause(), 顾名思义。
if (media.paused) {
media.play(); // 会导致 paused 为 false,因此也不需要专门自定义变量来确认状态了
} else {
media.pause();
}
3.2.2 停止
并没有原生的 stop() 方法,通过以下 trick 完成。
// 暂停并设置时间为 0
media.pause();
media.currentTime = 0;
一般来说,除了停止按钮的点击事件,media 的结束事件也要调用
stop.addEventListener("click", stopMedia);
media.addEventListener("ended", stopMedia);
3.2.3 进度条
- 显示时间的话用 currentTime 返回秒数进行计算分钟和小时即可
- 显示进度条用
media.currentTime/media.duration计算进度再显示
4. 客户端存储
旧流派:cookies
新流派:web storage 和 IndexedDB
未来:cache
4.1 web storage API
web storage 主要用于存储小型的键值对数据,可以用来存储简单数据。
这是通过 window.sessionStorage 和 window.localStorage 来实现的。
sessionStorage会在浏览器关闭的时候被清除。localStorage会在浏览器关闭后保留。
4.1.1 使用
- 通过 setItem 存储数据,两个参数为键和值
- 通过 getItem 获取数据,参数为 key
- 通过 removeItem 删除数据,参数为 key
- 通过 clear 清空数据
window上有个 storage 事件监听器,当数据变化的时候(以上4个函数调用)会触发
localStorage.setItem("name", "no one");
console.log( localStorage.getItem("name") ); // 输出 no one
localStorage.removeItem("name");
console.log( localStorage.getItem("name") ); // 输出 null
localStorage.clear(); // 清除所有数据
window.addEventListener("storage", () => {/*做一些什么*/});
4.2 IndexedDB API
IndexedDB 是一个可以在浏览器中访问的数据库系统,用于存储复杂的数据,如视频音频图像等
4.2.1 数据库和 table 的创建
let db; // 声明一个变量用于存储数据库
let request = window.indexedDB.open("notes", 1); // 意思为打开 notes 数据库的 1 版本。
// 如果没有这个数据库,就会创建一个 notes 数据库
// 如果版本号更大,则为数据库升级
数据库操作耗时大,因此是异步的。(所以上面代码中的变量才命名为 request)
request.onerror = () => {
console.log("数据库无法打开");
};
request.onsuccess = () => {
db = request.result; // 数据库用 result 属性读取
};
- 使用 onupgradeneeded 监听事件,当数据库被创建或者升级的时候触发(因此为 request 的事件而非 db)
- 使用 objectStore 对象来存储数据(可以理解为 table)
- keyPath 表示主键
request.onupgradeneeded = (e) => {
let db = e.target.result; // 不用全局变量
// 新创建的时候也没关系,因此事件触发时候数据库已经被创建了
// 当然,数据库被新建的时候这个也会运行,onsuccess 也会运行
// onsuccess 只是用来存储 db
let objectStore = db.createObjectStore("notes", { // 这里表格和数据库同名
keyPath: "id", // 设置 id 为主键
autoIncrement: true // 自增长
});
// 向表格添加索引(列)
// 三个参数分别为:索引名称 索引所在属性 配置对象
objectStore.createIndex("title", "title", {unique: false});
objectStore.createIndex("body", "body", {unique:false})
};
- 这个 table 里数据长这样:
{
title: "balabala",
body: "balabala"
}
- 这里会有一个问题,很坑,就是如果你写这个之前或写的时候已经刷新过页面,把 open 运行过一次后,你会发现 onupgradeneed 一直不运行,因为浏览器在你之前运行的时候已经记住了这个数据库,因此之后重新刷新的时候并不会新建数据库,因此 upgradeneeded 也不会运行,onsuccess 倒是会运行。
因为在写代码的时候,经常要刷新看输出,然后写完的时候还以为是第一次运行。就会有这个疑惑。
当然这不应该叫问题,确实应该是这样,只是刚学的时候有疑惑,MDN 也不解释一下
用以下命令删除数据库
window.indexedDB.deleteDatabase("notes");
4.2.2 添加数据
之后的一切数据操作都是通过事务(transaction)完成的
// 新建一个要储存的对象
let newItem = {
title: "人",
body: "出生"
}
// 这里用不上数据库的名字
let transaction = db.transaction(["notes"], "readwrite"); // 第一个参数为 objectStore 的数组。
// 这里写出你所有想访问的 objectStore
// 第二个参数为对这些 objectStore 的事务操作(默认只读)
let objectStore = transaction.objectStore("notes"); // 参数还是 objectStore,这里是对单独数据库的操作
/////////////////////
// 开始事务操作
/////////////////////
let addRequest = objectStore.add(newItem); // 添加数据,也是个异步操作
// 请求成功后触发,这里可以假设我们有一个输入框,那么这里可以清空这个输入框
addRequest.onsuccess = function () {
console.log("数据1添加完成,清空输入框");
titleInput.value = '';
bodyInput.value = '';
};
// 事务完成后出发(**注意这里的监听对象和上面不一样**)
// 因为上面的事件可以有很多,比如说你想添加100个数据,你不会想每个成功后都提示你,你就可以只用这个
transaction.oncomplete = function () {
console.log("事务成功完成,transaction completed");
displayData();
}
// 错误处理
transaction.onerror = function() {
console.log('事务操作失败');
};
4.2.3 访问数据
使用 objectStore 创建自身的游标来访问
let objectStore = db.transaction("notes").objectStore("notes"); // transaction 第二个属性用默认值
let cursor = objectStore.openCursor();
cursor.onsuccess = function (e) { // 要等游标创建成功
// 游标到最后一个数据就不会满足条件了
while (cursor) {
/*处理数据*/
cursor.continue(); // 转到下一个数据
}
};
4.2.4 删除数据
操作和添加数据一模一样,只是 add 换成 delete。
也是先选取 transaction,再选取 objectStore。
然后通过每个单独操作 request 的 onsuccess 事件或者事务的 oncomplete 事件。
4.2.5 复杂数据储存
比如先通过 fetch 获取视频 blob 文件,再储存到数据库中。
4.3 Cache API
用来储存 http 响应,让离线时也能打开网页。与 service worker 一起工作。
这里有个例子,第一次打开后,断网,仍然可以访问。