VuePress 部落格優化之中文錨點跳轉問題

冴羽發表於2022-02-01

前言

《一篇帶你用 VuePress + Github Pages 搭建部落格》中,我們使用 VuePress 搭建了一個部落格,最終的效果檢視:TypeScript 中文文件

VuePress 會在每個標題左側都加一個錨點連結:

點選後連結變為:

http://ts.yayujs.com/learn-typescript/handbook/TheBasics.html#降級-downleveling

此時重新整理下頁面,會發現,頁面並不能正確的定位到錨點位置,而且開啟開發者工具,還會看到報錯:

那這個問題該怎麼解決呢?

錯誤定位

線上上由於程式碼都是壓縮混淆後的,不好排查,我們在本地執行專案,然後檢視報錯資訊:

可以看到錯誤是來自於 vuerepss-plugin-smooth-scroll 這個外掛中,我們點選檢視具體的原始碼:


可以看到,報錯是來自於 document.querySelector(to.hash) 這句,為什麼會在這裡報錯呢?

這是因為對於每一個標題,VuePress 都會生成這樣一個 DOM 結構:

可以看到h2標籤的id是以 hash 值命名的,如果我們的 hash 是純英文,並沒有什麼問題,但是現在我們的 hash 中有中文,由於中文被編碼,document.querySelector報錯。

官方方案

這個問題這麼容易復現,且這麼明顯,我想肯定有人提了 issue,檢視 VuePress 的 issue,可以看到在 2020 年 10 月 3 號就有人提了 PR,並且 merge 了。

那為什麼還會報錯呢?

如果我們去掉我們目前在用的 reco主題,我們就會發現,頁面並不會有報錯,只不過依然不能定位到錨點而已。

實際上,我們的報錯是來自於我們使用的 reco 主題使用的 vuerepss-plugin-smooth-scroll 外掛。

社群方案

那成吧,那搜尋看下其他人是否也遇到過這個問題吧,可以查到這樣一個方案

新建一個 docs/.vuepress/theme/layouts/Layout.vue檔案,程式碼寫入:

<script>
export default {
  methods: {
    scrollTo(selector) {
      if (!selector || selector === '#') return
      const el = document.querySelector(decodeURIComponent(selector))
      if (el && el.offsetTop) {
        window.scrollTo(0, el.offsetTop)
      }
    }
  },
  mounted() {
    this.scrollTo(location.hash)
  }
}
</script>

使用後,頁面是不報錯了,但會發現樣式全部丟失了,因為使用這種方式相當於新建了一個主題,vuePress 已經改用我們的自定義主題了。

不過 VuePress 提供了主題繼承的功能,新建一個 docs/.vuepress/theme/index.js,程式碼寫入:

module.exports = {
  extend: '@vuepress/theme-default'
}

樣式是恢復了一些,但是因為我們本來使用的是 reco 主題,現在改成了繼承 vuepress 預設主題,reco 主題帶來的一些功能就全丟失了,那我們能繼承 reco 主題嗎?

答案是不能,這在 VuePress 官方文件的「主題的繼承 」章節裡有講到:

主題繼承暫時不支援高階繼承,也就是說,一個派生主題無法被繼承。

所以為了解決這個方案,就要放棄 reco 主題,而放棄了 reco 主題,本來也不會有報錯了……

那我們換個方案吧。

自己動手

成吧成吧,我自己解決。

梳理下當下的問題,當訪問帶中文錨點的連結時:

  1. 頁面有報錯,而報錯來自於 reco主題使用的 vuepress-plugin-smooth-scroll主題。
  2. 不能正常跳轉到錨點位置

頁面報錯這個問題最簡單粗暴的方式就是修改原始碼了,我們開啟 node_modules/vuepress-plugin-smooth-scroll/lib/enhanceApp.js,直接修改:

const enhanceApp = ({ Vue, router }) => {
    router.options.scrollBehavior = (to, from, savedPosition) => {
                // ...
        else if (to.hash) {
            //...
          
              // 加上 decodeURIComponent
            const targetElement = document.querySelector(decodeURIComponent(to.hash));
            
                        //...
        }
                // ...
    };
};

因為我們並不會提交原始碼,而是提交編譯後的檔案到伺服器上,所以改了依賴的原始碼也沒有什麼影響。

接下來是頁面載入完跳轉到錨點位置,之前我們在 《VuePress 部落格優化之新增資料統計功能》,在 enhanceApp.js 中監聽路由的改變,同樣我們可以監聽路由的 ready 事件,然後跳轉到直接的位置,我們修改下 .vuepress/enhanceApp.js中的程式碼:

export default ({ router }) => {
    // ...
 
  router.onReady(() => {
    const { hash } = document.location;
    setTimeout(() => {
      if (hash.length > 1) {
        const id = decodeURIComponent(hash);
        const el = document.querySelector(`.reco-side-${decodeURIComponent(id).substring(1)}`);
        el.click();
      }
    }, 1000);
  });
};

在這裡並沒有直接獲取標題 DOM 元素的位置,然後使用 window.scrollTo 跳轉,這是因為在 reco 主題下,隨著使用者不斷的瀏覽,其實路由也在不停的切換,右側的目錄才會隨之改變,這裡直接模擬了右側目錄的點選操作,也是為了將具體跳轉的行為交給 reco 來實現。

系列文章

部落格搭建系列是我至今寫的唯一一個偏實戰的系列教程,預計 20 篇左右,講解如何使用 VuePress 搭建、優化部落格,並部署到 GitHub、Gitee、私有伺服器等平臺。本篇為第 20 篇,全系列文章地址:https://github.com/mqyqingfeng/Blog

微信:「mqyqingfeng」,加冴羽好友,拉你進前端學習交流群。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

相關文章