{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
{{- $has_headers := ge (len $headers) 1 -}}
{{- if $has_headers -}}
<asideid="toc-container"class="toc-container wide"><divclass="toc"><details{{if(.Param"TocOpen")}}open{{end}}><summaryaccesskey="c"title="(Alt + C)"><spanclass="details">{{- i18n "toc" | default "Table of Contents" }}</span></summary><divclass="inner"> {{- $largest := 6 -}}
{{- range $headers -}}
{{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{- if lt $headerLevel $largest -}}
{{- $largest = $headerLevel -}}
{{- end -}}
{{- end -}}
{{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
{{- $.Scratch.Set "bareul" slice -}}
<ul> {{- range seq (sub $firstHeaderLevel $largest) -}}
<ul> {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
{{- end -}}
{{- range $i, $header := $headers -}}
{{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
{{- $headerLevel := len (seq $headerLevel) -}}
{{/* get id="xyz" */}}
{{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
{{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
{{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
{{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
{{- if ne $i 0 -}}
{{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
{{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
{{- if gt $headerLevel $prevHeaderLevel -}}
{{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
<ul> {{/* the first should not be recorded */}}
{{- if ne $prevHeaderLevel . -}}
{{- $.Scratch.Add "bareul" . -}}
{{- end -}}
{{- end -}}
{{- else -}}
</li> {{- if lt $headerLevel $prevHeaderLevel -}}
{{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
{{- if in ($.Scratch.Get "bareul") . -}}
</ul> {{/* manually do pop item */}}
{{- $tmp := $.Scratch.Get "bareul" -}}
{{- $.Scratch.Delete "bareul" -}}
{{- $.Scratch.Set "bareul" slice}}
{{- range seq (sub (len $tmp) 1) -}}
{{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
{{- end -}}
{{- else -}}
</ul></li> {{- end -}}
{{- end -}}
{{- end -}}
{{- end }}
<li><ahref="#{{- $cleanedID -}}"aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{- else }}
<li><ahref="#{{- $cleanedID -}}"aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{- end -}}
{{- end -}}
<!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} --> {{- $firstHeaderLevel := $largest }}
{{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
</li> {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
{{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
</ul> {{- else }}
</ul></li> {{- end -}}
{{- end }}
</ul></div></details></div></aside><script>letactiveElement;letelements;document.addEventListener('DOMContentLoaded',function(event){checkTocPosition();elements=document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');if(elements.length>0){// Make the first header active
activeElement=elements[0];constid=encodeURI(activeElement.getAttribute('id')).toLowerCase();document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');}// Add event listener for the "back to top" link
consttopLink=document.getElementById('top-link');if(topLink){topLink.addEventListener('click',(event)=>{// Prevent the default action
event.preventDefault();// Smooth scroll to the top
window.scrollTo({top:0,behavior:'smooth'});});}},false);window.addEventListener('resize',function(event){checkTocPosition();},false);window.addEventListener('scroll',()=>{// Get the current scroll position
constscrollPosition=window.pageYOffset||document.documentElement.scrollTop;// Check if the scroll position is at the top of the page
if(scrollPosition===0){return;}// Ensure elements is a valid NodeList
if(elements&&elements.length>0){// Check if there is an object in the top half of the screen or keep the last item active
activeElement=Array.from(elements).find((element)=>{if((getOffsetTop(element)-scrollPosition)>0&&(getOffsetTop(element)-scrollPosition)<window.innerHeight/2){returnelement;}})||activeElement;elements.forEach(element=>{constid=encodeURI(element.getAttribute('id')).toLowerCase();consttocLink=document.querySelector(`.inner ul li a[href="#${id}"]`);if(element===activeElement){tocLink.classList.add('active');// Ensure the active element is in view within the .inner container
consttocContainer=document.querySelector('.toc .inner');constlinkOffsetTop=tocLink.offsetTop;constcontainerHeight=tocContainer.clientHeight;constlinkHeight=tocLink.clientHeight;// Calculate the scroll position to center the active link
constscrollPosition=linkOffsetTop-(containerHeight/2)+(linkHeight/2);tocContainer.scrollTo({top:scrollPosition,behavior:'smooth'});}else{tocLink.classList.remove('active');}});}},false);constmain=parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'),10);consttoc=parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'),10);constgap=parseInt(getComputedStyle(document.body).getPropertyValue('--gap'),10);functioncheckTocPosition(){constwidth=document.body.scrollWidth;if(width-main-(toc*2)-(gap*4)>0){document.getElementById("toc-container").classList.add("wide");}else{document.getElementById("toc-container").classList.remove("wide");}}functiongetOffsetTop(element){if(!element.getClientRects().length){return0;}letrect=element.getBoundingClientRect();letwin=element.ownerDocument.defaultView;returnrect.top+win.pageYOffset;}</script>{{- end }}
阅读百分比实现的核心思想就是每当发生滚动事件时,根据滚动条高度计算当前阅读进度。这里我们将进度的数字显示在 TOP 按钮上,TOP 按钮定义在 footer.html 中,因此我们要创建 <your_hugo_site>/layouts/partials/footer.html,将主题中对应位置的 footer.html 内容拷贝进去,然后修改 TOP 按钮相关的代码,原代码为:
1
2
3
4
5
6
7
{{- if (not site.Params.disableScrollToTop) }}
<ahref="#top"aria-label="go to top"title="Go to Top (Alt + G)"class="top-link"id="top-link"accesskey="g"><svgxmlns="http://www.w3.org/2000/svg"viewBox="0 0 12 6"fill="currentColor"><pathd="M12 6H0l6-6z"/></svg></a>{{- end }}
{{- if (not .Site.Params.disableScrollToTop) }}
<ahref="#top"aria-label="go to top"title="Go to Top (Alt + G)"class="top-link"id="top-link"accesskey="g"><spanclass="topInner"><svgclass="topSvg"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 12 6"fill="currentColor"><pathd="M12 6H0l6-6z"/></svg><spanid="read_progress"></span></span></a><script>document.addEventListener('scroll',function(e){constreadProgress=document.getElementById("read_progress");constscrollHeight=document.documentElement.scrollHeight;constclientHeight=document.documentElement.clientHeight;constscrollTop=document.documentElement.scrollTop||document.body.scrollTop;readProgress.innerText=((scrollTop/(scrollHeight-clientHeight)).toFixed(2)*100).toFixed(0);})</script>{{- end }}