Fork of Chiri for Astro for my blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(toc): improve rendering efficiency

the3ash 77a43154 a16c0429

+80 -59
+80 -59
src/components/ui/TableOfContents.astro
··· 44 44 }} 45 45 > 46 46 ;(function () { 47 + let tocContainer = null 48 + let tocLinks = null 49 + let headings = null 50 + let titleLink = null 51 + 52 + function initDOMCache() { 53 + tocContainer = document.querySelector('.toc-container') 54 + tocLinks = document.querySelectorAll('.toc-link') 55 + headings = document.querySelectorAll('h1, h2, h3') 56 + titleLink = document.querySelector('.toc-link.toc-title') 57 + } 58 + 47 59 // TOC positioning logic (similar to BackButton) 48 60 function adjustTOCPosition() { 49 - const tocContainer = document.querySelector('.toc-container') 50 61 if (!tocContainer) return 51 62 52 63 // If not using centered layout, hide TOC ··· 87 98 88 99 // Hide TOC if there are no headings (only title) 89 100 function checkTOCVisibility() { 90 - const tocContainer = document.querySelector('.toc-container') 91 101 const tocItems = document.querySelectorAll('.toc-item:not(.toc-level-0)') 92 102 93 103 if (tocContainer && tocItems.length === 0) { ··· 97 107 98 108 // Add click handlers to TOC links using event delegation 99 109 function addTOCEventListeners() { 100 - const tocContainer = document.querySelector('.toc-container') 101 110 if (!tocContainer) return 102 111 112 + // Remove existing event listener to prevent duplicates 113 + tocContainer.removeEventListener('click', handleTOCClick) 114 + 103 115 // Use event delegation for better performance and reliability 104 - tocContainer.addEventListener('click', function (e) { 105 - const link = e.target.closest('.toc-link') 106 - if (!link) return 116 + tocContainer.addEventListener('click', handleTOCClick) 117 + } 107 118 108 - e.preventDefault() 119 + // Handle TOC click events 120 + function handleTOCClick(e) { 121 + const link = e.target.closest('.toc-link') 122 + if (!link) return 109 123 110 - if (link.classList.contains('toc-title')) { 111 - // Back to top 112 - window.scrollTo({ top: 0, behavior: 'smooth' }) 113 - history.pushState(null, null, '#') 114 - } else { 115 - // TOC item 116 - const href = link.getAttribute('href') 117 - if (href && href.startsWith('#')) { 118 - const targetId = href.substring(1) 119 - const target = document.getElementById(targetId) 120 - if (target) { 121 - const rect = target.getBoundingClientRect() 122 - const scrollTop = window.pageYOffset || document.documentElement.scrollTop 123 - const offset = rect.top + scrollTop - 96 // 6rem = 96px 124 - window.scrollTo({ top: offset, behavior: 'smooth' }) 125 - history.pushState(null, null, href) 126 - } 124 + e.preventDefault() 125 + 126 + if (link.classList.contains('toc-title')) { 127 + // Back to top 128 + window.scrollTo({ top: 0, behavior: 'smooth' }) 129 + history.pushState(null, null, '#') 130 + } else { 131 + // TOC item 132 + const href = link.getAttribute('href') 133 + if (href && href.startsWith('#')) { 134 + const targetId = href.substring(1) 135 + const target = document.getElementById(targetId) 136 + if (target) { 137 + const rect = target.getBoundingClientRect() 138 + const scrollTop = window.pageYOffset || document.documentElement.scrollTop 139 + const offset = rect.top + scrollTop - 96 // 6rem = 96px 140 + window.scrollTo({ top: offset, behavior: 'smooth' }) 141 + history.pushState(null, null, href) 127 142 } 128 143 } 129 - }) 144 + } 130 145 } 131 146 132 147 // Update active TOC item based on scroll position 133 148 function updateActiveTOCItem() { 134 - const tocLinks = document.querySelectorAll('.toc-link') 135 - const headings = document.querySelectorAll('h1, h2, h3') 149 + // Check if cache is valid, if not, re-fetch 150 + if (!tocLinks || !headings || tocLinks.length === 0 || headings.length === 0) { 151 + tocLinks = document.querySelectorAll('.toc-link') 152 + headings = document.querySelectorAll('h1, h2, h3') 153 + titleLink = document.querySelector('.toc-link.toc-title') 154 + } 155 + 156 + if (!tocLinks || !headings) return 136 157 137 158 let currentActive = null 138 159 const scrollTop = window.pageYOffset + 100 // Offset for better detection ··· 156 177 } 157 178 }) 158 179 159 - // Always highlight the first item ("Back to top") unless another TOC item is active 160 - if (!currentActive) { 161 - const titleLink = document.querySelector('.toc-link.toc-title') 162 - if (titleLink) { 163 - titleLink.classList.add('active') 164 - } 180 + // Highlight "Back to top" if no other item is active 181 + if (!currentActive && titleLink) { 182 + titleLink.classList.add('active') 165 183 } 166 184 } 167 185 168 186 // Initialize immediately to prevent layout shift 187 + initDOMCache() 169 188 adjustTOCPosition() 170 189 checkTOCVisibility() 171 190 addTOCEventListeners() ··· 173 192 174 193 // Listen for various page load events 175 194 document.addEventListener('astro:page-load', () => { 195 + initDOMCache() 176 196 adjustTOCPosition() 177 197 addTOCEventListeners() 178 198 updateActiveTOCItem() // Update active state after page load 179 199 }) 180 200 document.addEventListener('DOMContentLoaded', () => { 181 - adjustTOCPosition() 182 - addTOCEventListeners() 183 - updateActiveTOCItem() // Update active state after DOM ready 201 + // Only initialize if not already done by astro:page-load 202 + if (!tocContainer) { 203 + initDOMCache() 204 + adjustTOCPosition() 205 + addTOCEventListeners() 206 + updateActiveTOCItem() // Update active state after DOM ready 207 + } 184 208 }) 185 209 186 210 // Re-initialize on resize and scroll 187 211 window.addEventListener('resize', adjustTOCPosition) 188 - window.addEventListener('scroll', updateActiveTOCItem) 212 + window.addEventListener('scroll', () => { 213 + updateActiveTOCItem() 214 + }) 189 215 })() 190 216 </script> 191 217 ··· 200 226 201 227 .toc-container.fixed-position { 202 228 opacity: 1; 229 + position: fixed; 230 + top: 12rem; /* Position below BackButton */ 231 + margin-top: 0; 232 + padding-left: 1rem; 233 + z-index: 10; 234 + left: auto; 203 235 } 204 236 205 237 .toc-nav { ··· 208 240 line-height: 1.5; 209 241 } 210 242 211 - .toc-list { 243 + .toc-list, 244 + .toc-list li, 245 + .toc-item { 212 246 list-style: none; 213 - list-style-type: none; 214 - margin: 0 !important; 215 - padding: 0 !important; 247 + margin: 0; 248 + padding: 0; 216 249 } 217 250 218 - .toc-list li { 251 + .prose .toc-container .toc-list { 252 + margin-left: 0 !important; 253 + padding-left: 0 !important; 254 + } 255 + .prose .toc-container .toc-list li { 219 256 margin: 0 !important; 220 257 padding: 0 !important; 221 258 } 222 259 223 - .toc-item { 224 - list-style: none; 225 - list-style-type: none; 226 - } 227 - 228 - .toc-item::before { 229 - display: none; 230 - } 231 - 260 + .toc-item::before, 232 261 .toc-item::marker { 233 262 display: none; 234 263 } ··· 317 346 .toc-link.active::before { 318 347 opacity: 0.8; 319 348 background-color: var(--text-primary); 320 - } 321 - 322 - .toc-container.fixed-position { 323 - position: fixed; 324 - top: 12rem; /* Position below BackButton */ 325 - margin-top: 0; 326 - padding-left: 1rem; 327 - z-index: 10; 328 349 } 329 350 330 351 /* Hide on mobile and when space is limited */