Fork of Chiri for Astro for my blog
0
fork

Configure Feed

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

at c1ddc22aeb5d413d714699c1f7d8cd84a225615e 148 lines 4.0 kB view raw
1<div id="image-viewer" class="image-viewer"> 2 <img id="image-viewer-img" src="" alt="" /> 3</div> 4 5<script> 6 function initImageViewer() { 7 const viewer = document.getElementById('image-viewer') 8 const viewerImg = document.getElementById('image-viewer-img') as HTMLImageElement 9 10 if (!viewer || !viewerImg || viewerImg.tagName !== 'IMG') return 11 12 // Display image in fullscreen viewer overlay 13 function showImage(src: string, alt?: string) { 14 if (viewerImg && src) { 15 viewerImg.src = src 16 viewerImg.alt = alt || '' 17 } 18 // Show visibility first 19 viewer!.style.visibility = 'visible' 20 void viewer!.offsetWidth 21 viewer!.classList.add('active') 22 document.body.classList.add('image-viewer-open') 23 document.body.style.overflow = 'hidden' 24 document.body.dataset.scrollY = window.scrollY.toString() 25 viewer!.style.cursor = 'auto' 26 setTimeout(() => { 27 viewer!.style.cursor = '' 28 }, 10) 29 } 30 31 // Hide the image viewer and restore page scroll 32 function hideImage() { 33 viewer!.classList.remove('active') 34 document.body.classList.remove('image-viewer-open') 35 document.body.style.overflow = '' 36 } 37 38 // Hide and clear image after transition ends 39 viewer!.addEventListener('transitionend', (e) => { 40 if (e.propertyName === 'opacity' && !viewer!.classList.contains('active')) { 41 viewer!.style.visibility = 'hidden' 42 if (viewerImg) { 43 viewerImg.src = '' 44 viewerImg.alt = '' 45 } 46 viewer!.style.cursor = 'auto' 47 setTimeout(() => { 48 viewer!.style.cursor = '' 49 }, 10) 50 } 51 }) 52 53 // Bind click events to images with data-preview="true" attribute 54 function bindImageClickEvents() { 55 const previewImages = document.querySelectorAll('img[data-preview="true"]') 56 previewImages.forEach((img) => { 57 const imgElement = img as HTMLImageElement 58 imgElement.style.cursor = 'pointer' 59 imgElement.addEventListener('click', (e) => { 60 e.preventDefault() 61 const target = e.target as HTMLImageElement 62 showImage(target.src, target.alt || '') 63 }) 64 }) 65 } 66 67 viewer?.addEventListener('click', hideImage) 68 69 // Prevent touch scroll in image viewer 70 viewer?.addEventListener( 71 'touchmove', 72 (e) => { 73 if (viewer.classList.contains('active')) { 74 e.preventDefault() 75 } 76 }, 77 { passive: false } 78 ) 79 80 document.addEventListener('keydown', (e) => { 81 if (e.key === 'Escape' && viewer.classList.contains('active')) { 82 hideImage() 83 } 84 }) 85 86 bindImageClickEvents() 87 88 // Observe only the content area instead of the entire body 89 const contentArea = 90 document.querySelector('.prose') || document.querySelector('.content') || document.querySelector('article') 91 if (contentArea) { 92 const observer = new MutationObserver(() => { 93 bindImageClickEvents() 94 }) 95 96 observer.observe(contentArea, { 97 childList: true, 98 subtree: true 99 }) 100 } 101 102 // Hide initially 103 viewer!.style.visibility = 'hidden' 104 } 105 106 // Use only astro:page-load as it fires on initial load and navigation 107 document.addEventListener('astro:page-load', initImageViewer) 108</script> 109 110<style> 111 .image-viewer { 112 position: fixed; 113 top: 0; 114 left: 0; 115 width: 100%; 116 height: 100%; 117 z-index: 9999; 118 display: flex; 119 align-items: center; 120 justify-content: center; 121 opacity: 0; 122 transition: opacity 0.15s ease-in-out; 123 background: color-mix(in srgb, var(--bg) 90%, transparent); 124 cursor: zoom-out; 125 } 126 127 .image-viewer.active { 128 opacity: 1; 129 } 130 131 .image-viewer img { 132 min-width: 45rem; 133 max-width: 60vw; 134 max-height: 80vh; 135 object-fit: contain; 136 cursor: zoom-out; 137 } 138 139 @media (max-width: 768px) { 140 .image-viewer img { 141 min-width: 100vw; 142 } 143 } 144 145 body.image-viewer-open { 146 overflow: hidden; 147 } 148</style>