前端学习笔记(七)

275 阅读9分钟

今天把 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 画路径

有时候要画的并不是规则形状,或者自定义形状,就可以用路径。

  1. 画路径前也可以用 fillStyle 和 strokeStyle 定义颜色。
  2. beginPath() 表示绘制开始。
  3. moveTo() 表示移动到某点但不绘制,单纯瞬移,起始点就用这个写出来。
  4. LineTo() 表示从现在所在点画一条线到某点。
  5. 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条线全声明出来。可以用来画有起点和终点的折线。

以上代码画出的图:

  1. 实心没什么好说的。
  2. 蓝色边框可以看到只有右边和下边有,因为只画了两条 lineTo(),那么自然也只会画两条线。

2.4 画弧形/圆形

  1. 弧形的 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 其他学到的东西

  1. ctx.translate() 可以改变画布原点。如果想从左上角画背景,fillRect 的前两个参数设置为负值到原点即可。
  2. 和 canvas 无关,不过 input[type="range"]有一个 oninput 监听函数,可以用来实时显示 range 的数字。

3. 视频和音频 API

3.1 图标字体

这是一种字体。图标字体里所有的字符都是常用的图标.
用图标字体的好处是:

  1. 不用下载图片,可以减少 http 请求。
  2. 可拓展性高,字体可以用 colortext-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 播放/暂停

  1. media.paused 为布尔值表示是否暂停。
  2. 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 进度条

  1. 显示时间的话用 currentTime 返回秒数进行计算分钟和小时即可
  2. 显示进度条用 media.currentTime/media.duration 计算进度再显示

4. 客户端存储

旧流派:cookies
新流派:web storage 和 IndexedDB
未来:cache

4.1 web storage API

web storage 主要用于存储小型的键值对数据,可以用来存储简单数据。
这是通过 window.sessionStoragewindow.localStorage 来实现的。

  1. sessionStorage 会在浏览器关闭的时候被清除。
  2. localStorage 会在浏览器关闭后保留。

4.1.1 使用

  1. 通过 setItem 存储数据,两个参数为键和值
  2. 通过 getItem 获取数据,参数为 key
  3. 通过 removeItem 删除数据,参数为 key
  4. 通过 clear 清空数据
  5. 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 属性读取
};
  1. 使用 onupgradeneeded 监听事件,当数据库被创建或者升级的时候触发(因此为 request 的事件而非 db)
  2. 使用 objectStore 对象来存储数据(可以理解为 table)
  3. 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})
};
  1. 这个 table 里数据长这样:
{
  title: "balabala",
  body: "balabala"
}
  1. 这里会有一个问题,很坑,就是如果你写这个之前或写的时候已经刷新过页面,把 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 一起工作。
这里有个例子,第一次打开后,断网,仍然可以访问。