forked from
quillmatiq.com/augment
Fork of Chiri for Astro for my blog
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>