制作香草 SPA 至少有两种基本方法。
哈希路由器
策略是添加一个监听器window.onhashchange(或者听哈希变化事件),每当 URL 中的哈希值发生变化时(例如,https://www.example.com/#/foo
to https://www.example.com/#/bar
。您可以解析window.location.hashstring来判断路由并注入相关内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script>
const nav = `<a href="/#/">Home</a> |
<a href="/#/about">About</a> |
<a href="/#/contact">Contact</a>`;
const routes = {
"": `<h1>Home</h1>${nav}<p>Welcome home!</p>`,
"about": `<h1>About</h1>${nav}<p>This is a tiny SPA</p>`,
};
const render = path => {
document.querySelector("#app")
.innerHTML = routes[path.replace(/^#\//, "")] || `<h1>404</h1>${nav}`;
};
window.onhashchange = evt => render(window.location.hash);
render(window.location.hash);
</script>
</body>
</html>
历史API
现代方法使用历史API这对用户来说更自然,因为 URL 中不涉及哈希字符。
我使用的策略是为所有同域链接点击添加事件侦听器。监听者调用window.history.pushState使用目标 URL。
“后退”浏览器事件被捕获popstate解析的事件window.location.href
调用正确的路线。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script>
const nav = `<a href="/">Home</a> |
<a href="/about">About</a> |
<a href="/contact">Contact</a>`;
const routes = {
"/": `<h1>Home</h1>${nav}<p>Welcome home!</p>`,
"/about": `<h1>About</h1>${nav}<p>This is a tiny SPA</p>`,
};
const render = path => {
document.querySelector("#app")
.innerHTML = routes[path] || `<h1>404</h1>${nav}`
;
document.querySelectorAll('[href^="/"]').forEach(el =>
el.addEventListener("click", evt => {
evt.preventDefault();
const {pathname: path} = new URL(evt.target.href);
window.history.pushState({path}, path, path);
render(path);
})
);
};
window.addEventListener("popstate", e =>
render(new URL(window.location.href).pathname)
);
render("/");
</script>
</body>
</html>
上面的例子是尽可能少的。我有一个功能更全面的概念证明Glitch添加了基于组件的系统和模块。
如果你想处理更复杂的路线,route-parser包可以节省一些轮子的改造。
没有JS
顺便说一句,有一个技巧可以在没有 JS 的情况下制作基于哈希的 SPA,使用:target
用于切换的 CSS 伪选择器display: none
and display: block
重叠的全屏部分,如中所述单个 HTML 文件中的整个网站 and https://john-doe.neocities.org.
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
}
section {
padding: 1em;
padding-top: 2em;
display: none;
position: absolute;
width: 100%;
height: 100%;
background: #fff;
}
nav {
padding: 1em;
position: absolute;
z-index: 99;
}
section:target {
display: block;
}
#home {
display: block;
}
<nav>
<a href="#">Home</a> |
<a href="#about">About</a> |
<a href="#contact">Contact</a>
</nav>
<section id="home">
<h1>Home</h1>
<p>Welcome home!</p>
</section>
<section id="about">
<h1>About</h1>
<p>This is a tiny SPA</p>
</section>
<section id="contact">
<h1>Contact</h1>
<p>Contact page</p>
</section>