那些被忽略但很好用的 Web API / History

历史是现在与过去之间永无休止的对话。

我们都知道浏览器提供了上一页、下一页,甚至可以让你回到前两页、前三页...,但其实我们也可以借助 History API 的力量,在网页中自己实践这样的功能。


History

History 是一个浏览器提供的历史纪录操作介面,你可以透过 window.history 来取得该物件,当中有一些属性跟方法可以获取来使用,下面我们就一一来认识一下。

# History.length

length 是一个唯读属性,可以取得目前浏览器分页的历史纪录总数,当你开启一个新分页时,它会是 1,而每当你浏览一个新的网址,它便会不断增加。

console.log(history.length);

 

# History.scrollRestoration

scrollRestoration 这个属性是可以修改的,它影响的是浏览器对於「使用者在历史纪录的页面中移动时」的卷动行为,可以设定的值有两个:

  • "auto": 当使用者离开某个页面时,浏览器会纪录离开时的卷动距离,并在使用者到该页时,自动卷动到记录的位置。
  • "manual": 浏览器不会纪录卷动距离,每次页面更换时都会回到顶部。
if (history.scrollRestoration === "manual") {
  history.scrollRestoration = "auto";
}

虽然 MDN 上说,scrollRestoration 还是一个实验中的属性,但其实除了 IE,其他的浏览器都已经实践这个功能了,且预设会是 auto

 

# History.back

back 是 History 的其中一个 method,它相当於浏览器介面上的「上一页」,所以如果你希望你的页面中也有按钮可以让使用者返回上一页,就可以适用它:

document.querySelector("button").addEventListener("click", function () {
  history.back();
});

 

# History.forward

没错,有上一页,自然也有下一页,对应的 method 就是 forward

document.querySelector("button").addEventListener("click", function () {
  history.forward();
});

 

# History.go

比起 backforward 来说,go 就比较灵活了,它可以传入一个数字来代表要往前或往後至相对於目前页面的哪个历史位置,例如传入 -1 就相当於「上一页」,传入 1 则相当於「下一页」。

要注意的是,如果传入的数字超出了历史纪录的范围,那将不会有任何效果。

history.go(-1); // 等於 history.back()
history.go(1); // 等於 history.forward()
history.go(-3); // 回到三页之前
history.go(0); // 浏览器会重新整理目前的页面

 

pushState / replaceState

上面介绍的 backforwardgo,其实都算是蛮简单的,而且可能很多人都已经用过了,所以我们今天要重点介绍的其实是 pushStatereplaceState,它们是 HTML5 中新增加的 API,让我们可以添加或修改历史纪录。

# History.pushState

pushState 可以让我们在不移动页面的情况下,添加一笔历史纪录,它一共有三个参数

  • state: 这个参数可以接受一个物件,该物件里可以存放任何资料,至於有什麽实际用途,後面会再介绍。
  • title: 这是一个被暂时保留的参数,实际上没有任何用途,且会被浏览器忽略。
  • URL: 这个参数是用来设定我们添加的这笔历史纪录的网址,可传可不传。
history.pushState({ name: "max" }, null, "newPage.html");

假设我们原本所在页面的网址是 https://maxleebk.com/index.html,那当我们执行上面这段程序码时,网址就会被改成 https://maxleebk.com/newPage.html,并且历史纪录会被加上一笔,所以如果点击上一页,又会回到 /index.html

更有趣的是,当网址被改为 /newPage.html 时,浏览器不会真的去读取 newPage.html 这个文件,而是维持在 index.html,直到使用者进行重新整理。

 

# History.replaceState

replaceStatepushState 的参数和效果都一样,唯一不同的是 replaceState 并不是「添加」历史纪录,而是修改最新一笔的历史纪录。

history.replaceState({ name: "max" }, null, "newPage.html");

以同样的例子来说,在 https://maxleebk.com/index.html 执行上面这段程序码,网址一样会被改成 /newPage.html,但当你按上一页时,并不会回到 /index.html,而是回到更往前的一次的页面,因为 /index.html 其实是被 /newPage.html 取代了。

 

# Window:popstate Event

再来要介绍的这个 WindowEvent 与 pushStatereplaceState 息息相关,这个事件会在使用者进行历史纪录操作(例如上一页、下一页)时触发。

还记得前面讲到 pushStatereplaceStatestate 参数吗?popstate 事件的回呼函示所拿到的 Event 物件会有一个 state 属性,它存放的就会是当初设定的 state 参数副本。

window.addEventListener("popstate", function (event) {
  console.log(event.state);
});
history.pushState({ name: "max" }, null); // 不指定URL,所以网址不会变
history.pushState({ name: "tom" }, null); // 不指定URL,所以网址不会变
//此时按下「上一页」,console 会印出 { name: "max" }
//接着按下「下一页」,console 会印出 { name: "tom" }

 

# 复原 / 重做小应用

那学会 pushStatereplaceStatepopstate 能做什麽呢?其实我们可以利用 state 的设定来把使用者的一些操作记录在 History 中:

<div id="editable" contenteditable="true"></div>

<script>
  window.addEventListener("popstate", function (e) {
    editable.textContent = "";
    if (e.state && e.state.text) {
      // 每次有历史纪录的「移动」时,便将当下纪录的 state 丢到可编辑元素中
      editable.textContent = e.state.text;
    }
  });

  const editableDiv = document.querySelector("#editable");
  editableDiv.addEventListener("input", function () {
    // 每次输入时新增一笔历史纪录,且会利用 state 储存当下的输入内容
    window.history.pushState({ text: editable.textContent }, null);
  });
</script>

我们一样拿 contenteditable 的可编辑元素来示范,每当使用者输入文字时我们就 pushState 一次,并把元素当中的文字内容纪录在 state 中。而因为我们有监听了 popstate 事件,使得使用者每次在进行「上一页」或「下一页」时,便能取得每一次输入的内容。

这样的好处就是,使用者可以透过浏览器的上一页或下一页来模拟「复原」和「重做」功能。

如果想要玩玩看的话,这边是我已经写好的 CodePen

 

其实大家如果有使用过 VueRouter 的话,它底层就是透过 pushStatereplaceState,来实践 SPA 的网址变换的,所以才说其实很多 Web API 都是非常好用,甚至很多有名的套件都会采用,只是我们通常会忽视这些 Web API 的强大功能。


<<:  第二十二天:为测试产生覆盖率报告

>>:  JavaScript This

谈谈JSON

由wiki(https://zh.wikipedia.org/wiki/JSON) 可以知道JSON...

触价单

触价单的设定是,先决定「触发价」,这是交易人要动作(停损)的价格,再设定「取价」,这是指当市场价格触...

【Day27】Figma篇 : 设计到切版

对於设计师来说使用UI设计软件,除了可以善用之前提到的那些设计工具来增加效率和提升设计方法以外,还有...

Day 16 生命周期

我们常常在新增一个专案後会看到下面有个叫做viewDidLoad()的东西,如下图 viewDidL...

Azure Database for MySQL 手把手基础教学

葛瑞部落格欢迎光顾 Azure Database for MySQL 前置作业 一组有效的Azure...