Fork of Chiri for Astro for my blog
6
fork

Configure Feed

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

perf: optimize event listeners and async operations

the3ash 15e93841 6ada59ae

+100 -81
+2 -6
src/components/ui/BackButton.astro
··· 62 62 63 63 adjustBackButtonPosition() 64 64 65 - document.addEventListener('astro:page-load', () => { 66 - adjustBackButtonPosition() 67 - }) 68 - document.addEventListener('DOMContentLoaded', () => { 69 - adjustBackButtonPosition() 70 - }) 65 + // Use only astro:page-load as it fires on initial load and navigation 66 + document.addEventListener('astro:page-load', adjustBackButtonPosition) 71 67 window.addEventListener('resize', adjustBackButtonPosition) 72 68 })() 73 69 </script>
+1 -1
src/components/ui/CopyCode.astro
··· 134 134 }) 135 135 } 136 136 137 + // Use only astro:page-load as it fires on initial load and navigation 137 138 document.addEventListener('astro:page-load', initCopyCode) 138 - document.addEventListener('DOMContentLoaded', initCopyCode) 139 139 </script> 140 140 141 141 <style is:inline>
+5 -8
src/components/ui/FaviconThemeSwitcher.astro
··· 84 84 } 85 85 86 86 // Initialize favicon theme switcher 87 + let faviconSwitcherInitialized = false 87 88 function init() { 89 + if (faviconSwitcherInitialized) return 90 + faviconSwitcherInitialized = true 91 + 88 92 try { 89 93 new FaviconThemeSwitcher() 90 94 } catch (error) { ··· 92 96 } 93 97 } 94 98 95 - // Initialize when DOM is ready 96 - if (document.readyState === 'loading') { 97 - document.addEventListener('DOMContentLoaded', init) 98 - } else { 99 - init() 100 - } 101 - 102 - // Re-initialize on Astro page loads 99 + // Use only astro:page-load as it fires on initial load and navigation 103 100 document.addEventListener('astro:page-load', init) 104 101 </script>
+24 -4
src/components/ui/GradientMask.astro
··· 3 3 </div> 4 4 5 5 <script> 6 + let mask: HTMLElement | null = null 7 + let ticking = false 8 + 6 9 function updateMask() { 7 - const mask = document.querySelector('.gradient-mask') as HTMLElement 10 + if (!mask) { 11 + mask = document.querySelector('.gradient-mask') as HTMLElement 12 + } 13 + if (!mask) return 14 + 8 15 const threshold = 64 9 16 const scrollY = window.scrollY 10 17 ··· 15 22 } 16 23 } 17 24 18 - document.addEventListener('DOMContentLoaded', () => { 25 + function onScroll() { 26 + if (ticking) return 27 + ticking = true 28 + requestAnimationFrame(() => { 29 + updateMask() 30 + ticking = false 31 + }) 32 + } 33 + 34 + function initGradientMask() { 35 + mask = document.querySelector('.gradient-mask') as HTMLElement 19 36 updateMask() 20 - window.addEventListener('scroll', updateMask) 21 - }) 37 + } 38 + 39 + // Use only astro:page-load as it fires on initial load and navigation 40 + document.addEventListener('astro:page-load', initGradientMask) 41 + window.addEventListener('scroll', onScroll, { passive: true }) 22 42 </script> 23 43 24 44 <style>
+6 -5
src/components/ui/ImageOptimizer.astro
··· 5 5 if (img.complete) { 6 6 img.classList.remove('img-placeholder') 7 7 } else { 8 + // Skip if already bound 9 + if (img.dataset.placeholderBound) return 10 + img.dataset.placeholderBound = 'true' 11 + 8 12 img.addEventListener('load', function () { 9 13 img.classList.remove('img-placeholder') 10 14 }) 11 15 } 12 16 }) 13 17 } 14 - if (document.readyState === 'loading') { 15 - document.addEventListener('DOMContentLoaded', removeImgPlaceholder) 16 - } else { 17 - removeImgPlaceholder() 18 - } 18 + 19 + // Use only astro:page-load as it fires on initial load and navigation 19 20 document.addEventListener('astro:page-load', removeImgPlaceholder) 20 21 </script>
+12 -13
src/components/ui/ImageViewer.astro
··· 85 85 86 86 bindImageClickEvents() 87 87 88 - const observer = new MutationObserver(() => { 89 - bindImageClickEvents() 90 - }) 88 + // Observe only the content area instead of the entire body 89 + const contentArea = document.querySelector('.prose') || document.querySelector('.content') || document.querySelector('article') 90 + if (contentArea) { 91 + const observer = new MutationObserver(() => { 92 + bindImageClickEvents() 93 + }) 91 94 92 - observer.observe(document.body, { 93 - childList: true, 94 - subtree: true 95 - }) 95 + observer.observe(contentArea, { 96 + childList: true, 97 + subtree: true 98 + }) 99 + } 96 100 97 101 // Hide initially 98 102 viewer!.style.visibility = 'hidden' 99 103 } 100 104 101 - if (document.readyState === 'loading') { 102 - document.addEventListener('DOMContentLoaded', initImageViewer) 103 - } else { 104 - initImageViewer() 105 - } 106 - 105 + // Use only astro:page-load as it fires on initial load and navigation 107 106 document.addEventListener('astro:page-load', initImageViewer) 108 107 </script> 109 108
+18 -17
src/components/ui/NeoDBCard.astro
··· 151 151 ` 152 152 } 153 153 154 - containers.forEach((container) => { 154 + // Build fetch promises for parallel execution 155 + const fetchPromises = Array.from(containers).map(async (container) => { 155 156 const url = container.getAttribute('data-url') 156 157 157 158 if (!url) return ··· 178 179 } 179 180 180 181 if (fetchUrl) { 181 - fetch(fetchUrl, { 182 - mode: 'cors', 183 - headers: { 184 - Accept: 'application/json' 185 - } 186 - }) 187 - .then((res) => { 188 - if (!res.ok) throw new Error(`Response ${res.status}`) 189 - return res.json() 190 - }) 191 - .then((json) => { 192 - const data = json.data ? json.data : json 193 - renderCard(container, data, url) 194 - }) 195 - .catch(() => { 196 - renderError(container) 182 + try { 183 + const res = await fetch(fetchUrl, { 184 + mode: 'cors', 185 + headers: { 186 + Accept: 'application/json' 187 + } 197 188 }) 189 + if (!res.ok) throw new Error(`Response ${res.status}`) 190 + const json = await res.json() 191 + const data = json.data ? json.data : json 192 + renderCard(container, data, url) 193 + } catch { 194 + renderError(container) 195 + } 198 196 } else { 199 197 renderError(container) 200 198 } 201 199 }) 200 + 201 + // Execute all fetches in parallel 202 + Promise.allSettled(fetchPromises) 202 203 } 203 204 204 205 loadNeoDBCards()
+11 -14
src/components/ui/ThemeToggle.astro
··· 14 14 <script is:inline> 15 15 function bindThemeToggle() { 16 16 const themeToggle = document.getElementById('theme-toggle') 17 - if (themeToggle && window.ThemeManager) { 18 - // Remove existing event listeners to prevent duplicates 19 - const newToggle = themeToggle.cloneNode(true) 20 - themeToggle.parentNode.replaceChild(newToggle, themeToggle) 17 + if (!themeToggle || !window.ThemeManager) return 18 + 19 + // Skip if already bound 20 + if (themeToggle.dataset.themeBound) return 21 + themeToggle.dataset.themeBound = 'true' 21 22 22 - newToggle.addEventListener('click', function (e) { 23 - e.preventDefault() 24 - e.stopPropagation() 25 - window.ThemeManager.toggle() 26 - }) 27 - } 23 + themeToggle.addEventListener('click', function (e) { 24 + e.preventDefault() 25 + e.stopPropagation() 26 + window.ThemeManager.toggle() 27 + }) 28 28 } 29 29 30 - // Bind on initial load 31 - window.addEventListener('DOMContentLoaded', bindThemeToggle) 32 - 33 - // Bind on Astro page transitions 30 + // Use only astro:page-load as it fires on initial load and navigation 34 31 document.addEventListener('astro:page-load', bindThemeToggle) 35 32 </script> 36 33
+15 -6
src/components/ui/XPOST.astro
··· 12 12 element.setAttribute('data-theme', isDark ? 'dark' : 'light') 13 13 }) 14 14 15 - const script = document.createElement('script') 16 - script.src = 'https://platform.twitter.com/widgets.js' 17 - script.async = true 18 - document.head.appendChild(script) 15 + // Defer loading of Twitter script to avoid blocking main thread 16 + const loadTwitterScript = () => { 17 + const script = document.createElement('script') 18 + script.src = 'https://platform.twitter.com/widgets.js' 19 + script.async = true 20 + document.head.appendChild(script) 21 + } 22 + 23 + // Use requestIdleCallback if available, otherwise use setTimeout 24 + if ('requestIdleCallback' in window) { 25 + requestIdleCallback(loadTwitterScript, { timeout: 2000 }) 26 + } else { 27 + setTimeout(loadTwitterScript, 100) 28 + } 19 29 } 20 30 21 - document.addEventListener('DOMContentLoaded', loadXCards) 22 - 31 + // Use only astro:page-load as it fires on initial load and navigation 23 32 document.addEventListener('astro:page-load', loadXCards) 24 33 </script> 25 34
+6 -7
src/components/widgets/FootnoteScroll.astro
··· 3 3 const footnoteLinks = document.querySelectorAll('[data-footnote-ref], [data-footnote-backref]') 4 4 5 5 footnoteLinks.forEach((link) => { 6 + // Skip if already bound 7 + if (link.dataset.footnoteBound) return 8 + link.dataset.footnoteBound = 'true' 9 + 6 10 link.addEventListener('click', (e) => { 7 11 e.preventDefault() 8 12 const href = link.getAttribute('href') ··· 34 38 }) 35 39 } 36 40 37 - document.addEventListener('astro:page-load', () => { 38 - bindFootnoteEvents() 39 - }) 40 - 41 - document.addEventListener('DOMContentLoaded', () => { 42 - bindFootnoteEvents() 43 - }) 41 + // Use only astro:page-load as it fires on initial load and navigation 42 + document.addEventListener('astro:page-load', bindFootnoteEvents) 44 43 </script>