base 标签使 href="#section2" 被解析为 /myapp/#section2,导致整页重载而非锚点滚动;SPA 中需用 new URL("#section2", document.baseURI) 生成安全 href 或手动 scrollIntoView() 处理。

base 标签把 href="#section2" 解析成完整路径请求
浏览器看到 href="#section2" 时,会把它当作相对 URL 处理(因为它不以 / 或 http:// 开头)。一旦页面中存在 <base href="/myapp/">,就会把 #section2 拼成 /myapp/#section2。这个结果本身合法,但后续行为出问题:
- 当前 URL 是
https://example.com/myapp/page,点击后跳转到https://example.com/myapp/#section2—— 路径变了,触发整页重载 - Network 面板能看到一次对
/myapp/的空请求(状态 200,返回完整 HTML),这就是重载证据 - SPA 路由(如 Vue Router)监听的是
hashchange或popstate,但/myapp/#section2和原路径不匹配,既不滚动也不响应路由
SPA 中 href="#xxx" 不是“跳转”,而是“导航”
在有 <base> 的 SPA 页面里,href="#section2" 已经不是语义上的锚点定位,而是被浏览器当成一次新地址的导航请求。它和 href="/myapp/#section2" 在解析层没有区别。
- Vue/React 的
<router-link>或<Link>不受影响,因为它们内部已调用event.preventDefault() - 原生
<a>标签不会自动阻止默认行为,哪怕你只想要滚动 - 即使 DOM 已就绪、
id完全匹配,只要没拦截默认行为,照样重载
用 new URL("#section2", document.baseURI) 动态生成安全 href
手动拼 window.location.pathname + "#section2" 看似可行,但容易漏掉查询参数或 hash 变更;硬写 href="/myapp/page#section2" 又难维护。真正可靠的是浏览器原生 API:
-
new URL("#section2", document.baseURI)会正确忽略<base>对 hash 的干扰,返回当前页面 URL 拼上#section2 - 它不依赖当前
location,适用于服务端渲染或静态生成场景 - 不要用字符串拼接
"#" + id后直接赋给href,它仍会被 base 解析为相对路径
首次加载带 hash 时,scrollIntoView() 必须手动触发
比如用户直接访问 /myapp/page#faq,此时路由初始化可能早于 DOM 渲染,浏览器不会自动滚动 —— 这和 <base> 无关,但常被一并归因。
立即学习“前端免费学习笔记(深入)”;
- 需在组件挂载/页面就绪后检查
location.hash,再调用document.querySelector(location.hash)?.scrollIntoView() -
scroll-margin-top必须加在目标元素上(不是父容器),且仅在 Chrome/Firefox 生效;Safari 需额外兼容逻辑 - 如果目标元素是动态插入的(如异步加载内容),必须等 DOM 更新完成再执行,否则
querySelector返回null
<base> 存在时,它连触发滚动的机会都没有——第一步就被当成导航拦下了。



















