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 =
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>