WordPress主題 PJAX 無(wú)刷新以及渲染問(wèn)題的修復(fù)
近期為自己的wp主題配置了PJAX~此前使用的是Turbolinks插件,因?yàn)椴寮儆诓豢煽卦兀a(chǎn)生過(guò)多BUG后還是決定手動(dòng)配置更為先進(jìn)的PJAX(其實(shí)BUG更多了,但至少可以逐一排查…)

PJAX原理
PJAX(Pushstate + Ajax)是一種用于加快網(wǎng)頁(yè)加載速度的技術(shù)。它結(jié)合了HTML5的pushState API和Ajax技術(shù),使得在不刷新整個(gè)頁(yè)面的情況下,可以實(shí)現(xiàn)局部頁(yè)面內(nèi)容的更新。PJAX的主要原理可以分為以下幾個(gè)步驟:
用戶點(diǎn)擊鏈接:當(dāng)用戶點(diǎn)擊一個(gè)鏈接時(shí),PJAX會(huì)攔截該鏈接的點(diǎn)擊事件。
阻止默認(rèn)行為:PJAX會(huì)阻止瀏覽器執(zhí)行默認(rèn)的鏈接跳轉(zhuǎn)行為,避免整個(gè)頁(yè)面的刷新。
發(fā)送Ajax請(qǐng)求:接著,PJAX會(huì)通過(guò)Ajax發(fā)送一個(gè)請(qǐng)求到服務(wù)器,請(qǐng)求目標(biāo)鏈接的內(nèi)容。這個(gè)請(qǐng)求通常會(huì)包含一個(gè)特殊的請(qǐng)求頭,以便服務(wù)器能夠識(shí)別這是一個(gè)PJAX請(qǐng)求,從而僅返回局部更新所需的內(nèi)容,而非整個(gè)頁(yè)面的HTML代碼。
更新URL和頁(yè)面內(nèi)容:當(dāng)服務(wù)器返回局部?jī)?nèi)容后,PJAX會(huì)利用HTML5的pushState API來(lái)更新瀏覽器地址欄中的URL,然后用返回的內(nèi)容替換頁(yè)面中指定的容器元素。這樣一來(lái),用戶就會(huì)感覺(jué)像是進(jìn)行了一次正常的頁(yè)面跳轉(zhuǎn),但實(shí)際上只是局部?jī)?nèi)容發(fā)生了更新。
更新瀏覽器歷史記錄:通過(guò)pushState API更新URL之后,瀏覽器的歷史記錄也會(huì)相應(yīng)地更新。這樣,用戶在點(diǎn)擊瀏覽器的前進(jìn)和后退按鈕時(shí),可以按照預(yù)期的方式導(dǎo)航。
PJAX的優(yōu)勢(shì)在于減少了不必要的頁(yè)面刷新,提高了用戶體驗(yàn)。同時(shí),由于只需要加載局部?jī)?nèi)容,它還有助于降低服務(wù)器負(fù)載。然而,PJAX也有一些局限性,例如對(duì)于不支持HTML5 pushState API的瀏覽器,PJAX可能無(wú)法正常工作。
引入PJAX
引入JS文件
前往主題的function.php,添加如下代碼入隊(duì):
function add_pjax() {
? wp_register_script('pjax', 'https://cdn.bootcss.com/jquery.pjax/2.0.1/jquery.pjax.min.js', array('jquery'), '2.0.1', true);
? wp_enqueue_script('pjax');
}
add_action('wp_enqueue_scripts', 'add_pjax');
PHP
包裹PJAX容器
本段部分參考至https://alpha.skywt.cn/post/typecho-pjax-and-some-problems
F12檢查自己博客的html結(jié)構(gòu),找到需要PJAX動(dòng)態(tài)刷新的類,并將它包裹在PJAX容器中:

一般的網(wǎng)頁(yè)結(jié)構(gòu):
<html>
? <head>
? ? ? <!-- ... -->
? </head>
? <body>
? ? ? <header><!-- 頁(yè)眉,標(biāo)題什么的 --></header>
? ? ? <nav><!-- 導(dǎo)航欄什么的 --></nav>
? ? ? <main id="pjax-container">
? ? ? ? ? <!-- 網(wǎng)站主體內(nèi)容,文章列表 / 文章內(nèi)容 / 評(píng)論什么的 -->
? ? ? </main>
? ? ? <script>
? ? ? ? ? // 下面要加上的代碼
? ? ? </script>
? ? ? <footer><!-- 頁(yè)腳什么的 --></footer>
? </body>
</html>
HTML
只需前往header.php以及footer.php中找到相應(yīng)部分即可,在PJAX容器后方添加初始化代碼(這里是Wordpress版本):
<script>
jQuery(document).ready(function($) {
? $(document).pjax('a[href^="<?php echo home_url(); ?>"]:not(a[target="_blank"])', {
? ? ? container: '#pjax-container',
? ? ? fragment: '#pjax-container'
? });
? $(document).on('pjax:send', function() {
? ? ? $('#pjax-container').fadeTo(700,0.0); //這里添加了一個(gè)淡入動(dòng)畫
? ? ? // alert('開始加載');
? // 開始加載時(shí)要運(yùn)行的代碼(如顯示加載動(dòng)畫)
? });
? $(document).on('pjax:complete', function() {
? ? ? $('#pjax-container').fadeTo(700,1);
? ? ? // 完成加載時(shí)要運(yùn)行的代碼
? });
});
</script>
HTML
保存并清除緩存后,刷新后打開開發(fā)者工具,網(wǎng)絡(luò)選項(xiàng)搜索PJAX,看見(jiàn)?_pjax=#pjax-container意味著PJAX配置成功。

加載指示器
引入PJAX時(shí),用戶點(diǎn)擊超鏈后瀏覽器將不作回應(yīng),因此需要使用加載指示器顯示頁(yè)面正在加載中,HTML中加入
<div id="loading-spinner" class="loader" style="display:none;"></div>
HTML
對(duì)應(yīng)CSS(這里使用CSS矢量動(dòng)畫作為加載指示)
@keyframes spin {
0% {
? transform: rotate(0deg);
}
100% {
? transform: rotate(360deg);
}
}
#loading-spinner {
position: fixed;
top: 20px;
right: 20px;
width: 24px;
height: 24px;
border: 2px solid #ccc;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
z-index: 9999;
}
CSS
PJAX發(fā)送時(shí),加載動(dòng)畫出現(xiàn),完成時(shí)隱藏,這是我的完整JS
:
jQuery(document).ready(function ($) {
? $(document).pjax('a[href^="<?php echo home_url(); ?>"]:not(a[target="_blank"])', {
? ? ? container: '#pjax-container',
? ? ? fragment: '#pjax-container'
? });
? $(document).on('pjax:send', function () {
? ? ? // 顯示加載指示器
? ? ? $('#loading-spinner').show();
? ? ? window.scrollTo({ top: 0, behavior: 'smooth' });
? ? ? $('#pjax-container').fadeTo(400, 0.0);
? ? ? $('#toc').empty();
? });
? $(document).on('pjax:complete', function () {
? ? ? // 隱藏加載指示器
? ? ? $('#loading-spinner').hide();
? ? ? // 初始化左側(cè)小組件
? ? ? Toc.init({
? ? ? ? ? $nav: $('#toc')
? ? ? });
? ? ? $('body').scrollspy({
? ? ? ? ? target: '#toc'
? ? ? });
? ? ? ?
? ? ? trackPjaxPageView();
? ? ? ?
? ? ? $('#pjax-container').fadeTo(400, 1);
? });
});
JavaScript
遇到的一些問(wèn)題
PJAX意味著隨之而來(lái)的無(wú)數(shù)BUG…
Matomo追蹤代碼
如果啟用了Matomo或其他的一些追蹤代碼(它們一般放在Header內(nèi)),意味著用戶通過(guò)PJAX進(jìn)入的界面無(wú)法被記入統(tǒng)計(jì)數(shù)據(jù),因此必須在PJAX完成后重新載入這段JS,首先定義一個(gè)新函數(shù)
function trackPjaxPageView() {
? if (typeof _paq !== 'undefined') {
? ? ? _paq.push(['setReferrerUrl', document.referrer]);
? ? ? _paq.push(['setDocumentTitle', document.title]);
? ? ? _paq.push(['setCustomUrl', window.location.href]);
? ? ? _paq.push(['trackPageView']);
? }
}
JavaScript
PJAX完成后調(diào)用它:
$(document).on('pjax:complete', function() {
trackPjaxPageView();
? ? ? $('#pjax-container').fadeTo(700,1);
? });
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
? var u="//analy.hiripple.com/";//這是我的matomo地址
? _paq.push(['setTrackerUrl', u+'matomo.php']);
? _paq.push(['setSiteId', '1']);
? var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
? g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
JavaScript
左側(cè)目錄刷新
將PJAX容器包裹在正文,意味著左側(cè)小工具欄所有元素都不會(huì)刷新(例如這個(gè)使用了bootstrap-toc.js的小目錄)
$(document).on('pjax:complete', function() {
? ? ? // 初始化左側(cè)小組件,查詢官方的說(shuō)明文檔
? ? ? Toc.init({
? ? ? ? ? $nav: $('#toc'),
? ? ? ? ? $scope: $(document.body)
? ? ? });
? ? ? $('body').scrollspy({
? ? ? ? ? target: '#toc'
? ? ? });
? ? ? $('#pjax-container').fadeTo(700,1);
? });
JavaScript
即使重新初始化,也存在前一界面目錄未清除的問(wèn)題,在發(fā)送PJAX時(shí)清除目錄:
$(document).on('pjax:send', function() {
? ? ? $('#pjax-container').fadeTo(700,0.0);
$('#toc').empty(); //清除目錄
? });
JavaScript
Lazyload/lightbox
PJAX可能會(huì)導(dǎo)致Lazyload無(wú)法加載的情況:
function pjax_reload() {
var imgs = document.querySelectorAll("#main img.lazyload");
lazyload(imgs);
}
jQuery(document).ready(function($) {
? // 初始化fancybox
? jQuery('[data-fancybox="gallery"]').fancybox();
? // 初始化lazyload
? pjax_reload();
? $(document).pjax('a[href^="<?php echo home_url(); ?>"]:not(a[target="_blank"])', {
? ? ? container: '#pjax-container',
? ? ? fragment: '#pjax-container'
? });
? $(document).on('pjax:send', function() {
? ? ? $('#pjax-container').fadeTo(700,0.0);
? });
? $(document).on('pjax:complete', function() {
? ? ? jQuery('[data-fancybox="gallery"]').fancybox();
? ? ? // 重新初始化lazyload
? ? ? pjax_reload();
? ? ? $('#pjax-container').fadeTo(700,1);
? });
});
JavaScript
或者直接使用圖片loading='lazy'屬性,這是HTML5中的原生懶加載。原生懶加載不需要額外的處理,瀏覽器會(huì)自動(dòng)處理懶加載。
點(diǎn)擊事件
這里需要切換夜間模式
function initDarkModeToggle() {
const toggleBtnMoon = document.getElementById("moon-icon");
const toggleBtnSun = document.getElementById("sun-icon");
function applyDarkMode() {
? document.body.classList.add("dark-mode");
? localStorage.setItem("theme", "dark-mode");
? toggleBtnMoon.style.display = "none";
? toggleBtnSun.style.display = "block";
}
function removeDarkMode() {
? document.body.classList.remove("dark-mode");
? localStorage.setItem("theme", "light-mode");
? toggleBtnMoon.style.display = "block";
? toggleBtnSun.style.display = "none";
}
if (localStorage.getItem("theme") === "dark-mode") {
? applyDarkMode();
} else {
? removeDarkMode();
}
toggleBtnMoon.addEventListener("click", applyDarkMode);
toggleBtnSun.addEventListener("click", removeDarkMode);
}
$(document).on('pjax:complete', function() {
initDarkModeToggle();
});
最初發(fā)表于https://hiripple.com/archives/2982