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 796ce0a584985e2cc4e9997875b2267944315fed 147 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 = document.querySelector('.prose') || document.querySelector('.content') || document.querySelector('article') 90 if (contentArea) { 91 const observer = new MutationObserver(() => { 92 bindImageClickEvents() 93 }) 94 95 observer.observe(contentArea, { 96 childList: true, 97 subtree: true 98 }) 99 } 100 101 // Hide initially 102 viewer!.style.visibility = 'hidden' 103 } 104 105 // Use only astro:page-load as it fires on initial load and navigation 106 document.addEventListener('astro:page-load', initImageViewer) 107</script> 108 109<style> 110 .image-viewer { 111 position: fixed; 112 top: 0; 113 left: 0; 114 width: 100%; 115 height: 100%; 116 z-index: 9999; 117 display: flex; 118 align-items: center; 119 justify-content: center; 120 opacity: 0; 121 transition: opacity 0.15s ease-in-out; 122 background: color-mix(in srgb, var(--bg) 90%, transparent); 123 cursor: zoom-out; 124 } 125 126 .image-viewer.active { 127 opacity: 1; 128 } 129 130 .image-viewer img { 131 min-width: 45rem; 132 max-width: 60vw; 133 max-height: 80vh; 134 object-fit: contain; 135 cursor: zoom-out; 136 } 137 138 @media (max-width: 768px) { 139 .image-viewer img { 140 min-width: 100vw; 141 } 142 } 143 144 body.image-viewer-open { 145 overflow: hidden; 146 } 147</style>