Hexo 开启 pjax 后 APlayer 样式丢失或脚本失效: 讲透 pjax 的三个坑
前言
最近排查了一个很典型的现象: 音乐页的 APlayer 播放器, 直接打开页面一切正常; 从首页点导航跳进来, 播放按钮变成一个巨大的裸图标 (样式丢了), 有时播放器干脆不初始化。刷新一下又好了。
这种”直开正常、跳转进来就坏”的毛病, 十有八九和 pjax 有关。这篇把背后的机制讲透 —— 不只是 APlayer, 任何”只在特定页面加载的第三方组件”在 pjax 站点里都会遇到同样的问题。
一、先弄清 pjax 到底替换了什么
pjax = pushState + ajax。点击站内链接时, 它做的是:
- 拦截这次点击, 用 fetch 拉取目标页的完整 HTML
- 从返回的 HTML 里只取出主容器 (Butterfly 主题里是
#content-inner这一层), 替换当前页的对应容器 pushState修改地址栏, 手动更新<title>- 重新执行容器内新出现的
script
关键就在这里: head 不替换, 页面尾部那些常驻脚本也不会重新执行。
下面三个坑, 全部由此推出。
坑一: 只在某页注入的样式, pjax 进入后会缺失
很多组件的 CSS 是”页面级”注入的 —— 比如通过页面 front-matter 控制、或主题模板判断”当前是音乐页才输出这条 link”。
- 直开音乐页:
head里有这条 CSS, 正常 - pjax 跳进来:
head还是上一页的, 这条 link 根本不存在 → 播放按钮的样式类全部失效, 图标按原始大小渲染, 看起来就是”巨大裸图标”
验证方法很简单: pjax 进入页面后, 在 DevTools 的 Elements 里搜组件的 CSS 类名, 看 head 里到底有没有对应的 link。
解法两层:
- 首选: 把组件 CSS 提升为全站注入。 一条几 KB 的 CSS 对全站加载影响可以忽略, 换来的省心是实打实的
- 或者用 JS 兜底补链 (见第三节)
坑二: 相对路径按”来源页”解析
pjax 拿到目标页 HTML 后, 是把它塞进当前文档的。此时目标页里写的相对路径引用, 会按当前页 (来源页) 的地址去解析。
举例: 从 /posts/abc.html 点击导航跳到 /music/, 目标页里如果写了 js/aplayer.js 这样的相对路径, 浏览器实际请求的是:
1 | /posts/js/aplayer.js ← 拼到了来源页的目录下, 404 |
规则: 凡是 pjax 可达页面里的静态资源引用, 一律用根绝对路径 (/js/aplayer.js), 不要用相对路径。
一个容易困惑的现象: “以前引用走 CDN (跨域完整 URL) 一直没事, 后来改成同域相对路径反而 404 了”。原因就是跨域完整 URL 不存在解析歧义, 而相对路径会被 pjax 拿来源页地址去拼。
坑三: exclude 拦不住”跳进该页”
pjax 配置里通常有 exclude 列表, 本意是”这些页面不要被 pjax 接管”。但它有两个盲区:
- exclude 是对链接 href 的精确匹配。 像
viewer.html?file=a.pdf这种带 query 的链接, 往往匹配不上, 照样被拦截 - exclude 只影响”从 A 页点出去”的判断。 站内搜索、脚本跳转等其他方式进入目标页时, 该拦还是拦
想稳定绕过 pjax, 最可靠的方式是给链接加 target="_blank":
1 | <a href="/pdf.js/web/viewer.html?file=/files/a.pdf" target="_blank" rel="noopener">查看</a> |
pjax 不接管带 target 的链接, 这是规范行为, 比 exclude 靠谱得多。
二、通用兜底脚本: 补链 + 补初始化 + 销毁
对于”必须留在当前页打开”的组件 (比如音乐页), 写一段兜底脚本, 三段逻辑:
1 | <script> |
几点说明:
.aplayer-tag-marker和window.options是hexo-tag-aplayer这类标签插件渲染出来的钩子。用别的接入方式时, 换成对应的容器选择器和配置来源, 模式不变: 补链 + 补初始化 + 离开时销毁- 轮询间隔 50ms 对初始化场景足够, 上限 10 秒兜底
btf.addGlobalFn('pjaxSend', ...)是 Butterfly 提供的 pjax 钩子; 其他主题换用自己的 pjax 事件即可
三、怎么确认问题是 pjax 引起的
按这个顺序验证, 两分钟出结论:
1 | # 1. 临时关掉 pjax (主题配置 pjax.enable: false), 重新构建 |
四、一劳永逸的检查清单
pjax 站点接入任何第三方组件前, 过一遍这张表:
| 检查项 | 做法 |
|---|---|
| 资源引用路径 | pjax 可达页面里一律根绝对路径 (/js/xx.js) |
| 页面级 CSS | 提升为全站注入, 或 JS 兜底补链 |
| 组件初始化 | 轮询兜底, 兼容”直开已初始化”和”pjax 进入未初始化”两种情况 |
| 实例清理 | pjax 离开事件里 destroy, 防止音乐/视频切页后继续播 |
| 需要独立环境的页面 | 链接加 target="_blank", 别指望 exclude |
小结
pjax 的三个坑本质上是同一件事: **pjax 是”局部替换”, 而组件生态默认的是”整页加载”**。
head不替换 → 样式会缺- 内容塞进当前文档 → 相对路径错乱
- 拦截的是”点击” → exclude 管不住其他进入方式
理解了这一点, 排查思路就清晰了: 先分清资源是”没加载”还是”加载错了路径”, 再分清脚本是”没执行”还是”执行了但没初始化”。
相关阅读: Hexo 里用 pdf.js 预览 PDF 页面塌陷成一条线: viewer.html 必须独立渲染 —— 同样是 pjax 引发的连锁问题, 附带一个”量高度而不是数 DOM”的调试方法论。
💡 准备购买云服务器 / 域名来搭建或部署站点?
通过下面的链接下单,通常能领到新用户专属优惠,也能小额支持本站持续更新:
阿里云(返现) | 腾讯云 2核2G 99元/年 | 腾讯云 云产品特惠













