页面跳转的两种方式:从锚点到路由的完全对比
最近在写官网页面,想到一个问题:URL 里的 # 到底是锚点还是路由?看着浏览器地址栏里的 #/home 、#/about ,又看看传统网页里的 #section1 、#top ,感觉都带 # ,但用法完全不一样。
- 为什么有的 # 后面是页面片段,有的是完整路径?
- 锚点跳转和路由跳转有啥本质区别?
- 什么时候用锚点,什么时候用路由?
- 两者能不能混用?会有什么问题?
锚点:最原始的页内跳转
什么是锚点?
锚点(Anchor)是 HTML 最古老的特性之一,用 #技术分享来在同一个页面内快速跳转到指定位置。它的原理特别简单:
<h2 id="section1">第一章</h2>
<div id="top">顶部内容</div>
<a href="#section1">跳到第一章</a>
<a href="#top">回到顶部</a>
点击链接后,浏览器会自动滚动到对应 id 的元素位置。URL 也会变成
https://example.com/page.html#section1 。
锚点的工作原理
graph TD
A["用户点击锚点链接"] --> B["浏览器解析 href='#xxx'"]
B --> C["查找 id='xxx' 的元素"]
C --> D{"是否找到元素?"}
D -->|是| E["滚动到元素位置"]
D -->|否| F["不做任何操作"]
E --> G["更新 location.hash"]
G --> H["触发 hashchange 事件"]
锚点跳转的几个特点:
- 不刷新页面 – 纯客户端操作,不发送请求
- 自动滚动 – 浏览器原生支持,不需要 JS
- 支持后退 – 浏览器历史记录会保存每次跳转
- 可以书签 – URL 带锚点可以直接分享和收藏
锚点的现代用法
除了传统的 id 跳转,现代浏览器还支持更灵活的滚动控制:
html {
scroll-behavior: smooth;
}
h2[id] { scroll-margin-top: 60px; }
JavaScript 也能更准确地控制:
document.getElementById('section1').scrollIntoView({
behavior: 'smooth',
block: 'start'
});
window.addEventListener('hashchange', (e) => { console.log('从', e.oldURL, '跳到', e.newURL); });
location.hash = '#section1';
路由:单页应用的核心机制
什么是前端路由?
前端路由(Client-side Routing)是 SPA(单页应用)的核心概念,用来在不刷新页面的情况下切换不同的”页面”内容。说是页面,实则都在同一个 HTML 里,通过 JavaScript 动态切换显示的组件。
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User }
];
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user/:id" element={<User />} />
</Routes>
路由的两种模式
现代前端路由主要有两种实现方式:
1. Hash 路由(Hash Mode)
利用 URL 的 hash 部分( # 后面)来实现:
class HashRouter {
constructor(routes) {
this.routes = routes;
window.addEventListener('hashchange', this.handleRoute.bind(this));
this.handleRoute();
}
handleRoute() {
const hash = location.hash.slice(1) || '/';
const route = this.routes.find(r => r.path === hash);
if (route) {
document.getElementById('app').innerHTML = route.component();
}
}
push(path) {
location.hash = path;
}
}
2. History 路由(History Mode)
利用 HTML5 的 History API 实现:
class HistoryRouter {
constructor(routes) {
this.routes = routes;
window.addEventListener('popstate', this.handleRoute.bind(this));
this.handleRoute();
}
handleRoute() {
const path = location.pathname;
const route = this.routes.find(r => r.path === path);
if (route) {
document.getElementById('app').innerHTML = route.component();
}
}
push(path) {
history.pushState({}, '', path);
this.handleRoute();
}
}
两种路由模式对比
graph LR
A[Hash路由] --> B[优点]
B --> B1[无需服务器配置]
B --> B2[兼容性好]
B --> B3[部署简单]
A --> C[缺点]
C --> C1[URL不够美观]
C --> C2[SEO不友善]
C --> C3[和锚点冲突]
D[History路由] --> E[优点]
E --> E1[URL美观]
E --> E2[更像传统网站]
E --> E3[SEO友善]
D --> F[缺点]
F --> F1[需要服务器配置]
F --> F2[IE9及以下不支持]
F --> F3[刷新会404]
锚点 vs 路由:详细对比
让我们从多个维度对比两者的区别:
| 特性 | 锚点(Anchor) | Hash 路由 | History 路由 | | —
| URL 格式 | #section1 | #/page/123 | /page/123 | | 主要用途 | 页内定位跳转 | SPA 页面切换 | SPA 页面切换 | | 触发事件 | hashchange | hashchange | popstate | | 浏览器行为 | 自动滚动到元素 | 不滚动(除非手动) | 不滚动 | | 服务器感知 | 不发送给服务器 | 不发送给服务器 | 发送完整路径 | | SEO 友善度 | 一般 | 差 | 好 | | 部署配置 | 无需配置 | 无需配置 | 需要服务器配置 | | 浏览器兼容 | 所有浏览器 | 所有浏览器 | IE10+
| 刷新页面 | 保持位置 | 保持路由 | 可能 404 | | 实现复杂度 | 简单(原生支持) | 中等 | 较复杂 |
总结
研究完锚点和路由,我的理解是:
原理层面 :
- 锚点是 HTML 原生特性,用于页内定位
- Hash 路由劫持了锚点机制,用于 SPA 页面切换
- History 路由使用 History API,不依赖 hash
- 两者解决的是不同层次的问题
实用层面 :
- 简单的页内跳转用锚点就够了
- 复杂应用必须用路由系统
- Hash 路由和锚点有冲突,需要特殊处理
- History 路由更优雅,但需要服务器配置
参考资源
- MDN – HTML Anchors – 锚点的官方文档
- MDN – History API – History API 详细说明
- Vue Router 文档 – Vue 官方路由实现
- React Router 文档 – React 官方路由实现
- Can I Use – History API – 浏览器兼容性查询