我报名参与金石计划1期应战——分割10万奖池,这是我的第3篇文章,点击检查活动概况。

布景

之前我发布了一篇文章,介绍自己做的小游戏《Dice Crush》。

本文共享一项技能计划,正是我开发上述游戏时用到的:不必React Vue,只用原生JS,怎样开发单页面运用?

什么是单页面运用

单页面运用(Single-Page Application)是个相对陈旧的传统的多页面运用(Multi-Page Application)的名词。

曾经咱们拜访网页,每个页面是一个html文件。点击某个超链接,就跳转到新的html页面。每次浏览器拜访html时,需求从头下载整个html文档、JS和CSS依赖,才干展现出整个页面。这个功率很低。

跟着异步恳求AJAX等技能的兴起、HTML5标准的出现,开发者有了更优异的页面加载计划:一个网站的所有页面,都是同一份html文档,用JS判断路由,并动态展现内容。经过预加载等方式,把整个网站的页面都下载到内存中。每逢用户点击超链接,准备切换页面时,经过history API使浏览器更新URL而不必从头下载html文档,然后JS只要把现有的页面卸载(躲藏),再把内存中的东西展现出来即可。这个进程完全避免了网络恳求,极大提高了网站用户体会。

选用上述计划完结的Web运用就是单页面运用。

React和Vue开发的基本都是单页面运用

现代Web开发,大多数网站是用React或Vue开发的,它们基本都是单页面运用。

开发者能够很便利的运用React、Vue开发单页面运用,是由于React Router和Vue Router帮开发者完结了单页面运用的中心逻辑。所以开发者不必关怀细节,只要会用React Router和Vue Router即可。

这就导致一个问题:假如咱们不必React或Vue(例如我的游戏《Dice Crush》是用原生JS完结),没有React Router和Vue Router的才能,该怎样开发单页面运用呢?

开发单页面运用,有哪些难题

在聊怎样完结之前,咱们要先想理解:开发单页面运用,需求处理哪些难题?

  1. 多个页面怎样界说?
  2. 页面切换时,不能够运用location.replace(‘新的网址’)或document.href = ‘新的网址’,由于它会使浏览器下载html文档。咱们需求用HTML5的History API,修正网址。
  3. <a>标签导航时,不能运用原生的href特点,由于它会使浏览器下载html文档。咱们需求监听onclick事情,在里面调用History API修正网址。
  4. 运用History API修正网址后,页面不会有任何变化,仅仅浏览器URL变了。咱们需求手动控制当时页面DOM的毁掉、新页面DOM的生成。
  5. 运用History API修正网址后,当用户点击浏览器的「回来」、「行进」时,页面不会刷新,仅仅浏览器URL变了。咱们需求监听事情onpopstate,即监听用户点击浏览器的「回来」、「行进」,然后控制当时页面DOM的毁掉、新页面DOM的生成。

以上是一些最基本的难题,假如你要追求极致用户体会,还需求处理下面的难题:

  1. <a>标签导航,需求借助href特点,给予用户在新窗口翻开链接的权力。
  2. 当用户切换路由时,假如发生了临界事情,要能够做好兼容。例如,用户点击了链接,准备烘托新页面,此时立马点击了旧页面某个按钮,要执行旧页面某个按钮的回调函数。这可能有超出预期的成果。咱们需求在切换路由后,就制止旧页面的全部事情回调。

1、界说多个页面

每个页面是由HTML+JS+CSS组成的。每个页面需求对应一个路由。

我说一下我在游戏《Dice Crush》中的做法。

它有3个页面:主页、挑选关卡页面、游戏页面。如下图:

不必React Vue,只用原生JS,怎样开发单页面运用(SPA)?

不必React Vue,只用原生JS,怎样开发单页面运用(SPA)?

不必React Vue,只用原生JS,怎样开发单页面运用(SPA)?

我给每个页面界说了一个template.js,用于存放html字符串。比方:

const template = `<div>
  <h1>Dice Crush</h1>
  <button>开端游戏</button>
</div>`;

之后烘托页面时,只需求document.body.innerHtml = template,就能够把该页面的模板烘托到html文档上了。当然,烘托页面时,还需求给button绑定click事情。

因而,咱们给每个页面声明一个template,再声明一个用于烘托该页面的函数(功用主要是给document.body.innerHtml赋值、给button增加click事情),就能够了。之后需求烘托哪个页面,就调用哪个页面的烘托办法。

2、页面切换,运用History API切换URL

需求切换页面时,咱们需求运用history.pushState(null, ”, ‘新的页面URL’)来修正浏览器URL,同时调用上述烘托页面办法,把页面烘托在浏览器中。

3、a标签的问题

咱们需求留意,假如给<a>标签增加了href,最好给它绑定这样的click事情:

linkElement.onclick = function (event) {
  if (event.button !== 0) return;
  if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
  event.preventDefault();
  window.history.pushState(null, '', 'new-page.html');
  // 手动烘托新的页面
};

event.button表示按下的是鼠标哪个按键(0是主按键,通常指鼠标左键或默认值)。假如用户是鼠标中键按下a标签、或者用户同时按下了Ctrl(Windos)、Command(Mac)、Shift,那么他应该期望是在新窗口翻开,咱们运用href原生行为即可。假如用户同时按下了Option,那么他应该期望是翻开菜单栏,咱们也执行原生行为。其它状况,都标明用户要在本页面点开那个网址,咱们拦截原生的href,经过history.pushState完结,并手动烘托新的页面。

4、手动加载新页面、卸载旧页面

由于咱们页面烘托是经过document.body.innerHtml完结的,所以会在加载新页面时自动卸载旧页面。

当然,假如你的旧页面在window上增加了一些事情监听器、计时器,也要记得手动卸载掉。做好清除作业,否则会出问题。

5、页面初度加载与监听事情onpopstate

页面初度加载时,咱们需求根据路由烘托一个页面,示例代码如下:

const init = () => {
  if (window.location.pathname.includes('play')) {
    renderGame();
  } else if (window.location.pathname.includes('select')) {
    renderSelect();
  } else {
    renderHome();
  }
};
init();

相同,当页面onpopstate时,即用户点击了「回来」、「行进」,仍然停留在本页面时,咱们也需求从头根据当时路由烘托一下页面。需求执行如下逻辑:

window.onpopstate = init;

至此,咱们手写的一个单页面运用就开发完结啦~这也是我在游戏《Dice Crush》中运用的计划,你学会了吗?

写在最后

我是HullQin,独立开发了《联机桌游合集》,是个网页,能够很便利的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《组成大西瓜重制版》。还开发了《Dice Crush》参与Game Jam 2022。喜爱能够关注我噢~我有空了会共享做游戏的相关技能,会在这2个专栏里共享:《教你做小游戏》、《极致用户体会》。