feat: reusable image viewer

This commit is contained in:
Ivan R. 2024-03-06 20:32:32 +05:00
parent 5684129873
commit 8bc1493821
No known key found for this signature in database
GPG key ID: 56C7BAAE859B302C
5 changed files with 68 additions and 51 deletions

View file

@ -1,51 +1,22 @@
--- ---
interface Image { // A component that allows you to open images in full screen.
src: string // All you need to do is place it anywhere on the page.
alt: string
}
interface Props {
images: Image[]
}
const { images } = Astro.props
const rowCount = Math.ceil(images.length/3)
--- ---
<div> <div id="fullscreenGallery" style="opacity: 0;">
<div id="gallery" style={`grid-template-rows: repeat(${rowCount}, 263px);`}> <img id="fullscreenImage" />
{images.map(img => ( <div id="fullscreenAlt"></div>
<img src={img.src} alt={img.alt} loading="lazy" />
))}
</div>
<div id="fullscreenGallery" style="opacity: 0;">
<img id="fullscreenImage" />
<div id="fullscreenAlt"></div>
</div>
</div> </div>
<style> <style>
#gallery {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 5px;
width: 100%;
}
#gallery img {
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
}
#fullscreenGallery { #fullscreenGallery {
width: 100%; width: 100vw;
height: 100%; height: 100vh;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
display: flex; display: flex;
background-color: #0008; background-color: #0008;
padding: 1em;
backdrop-filter: blur(42px); backdrop-filter: blur(42px);
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -55,24 +26,23 @@ const rowCount = Math.ceil(images.length/3)
transition: opacity 0.1s ease-in-out; transition: opacity 0.1s ease-in-out;
} }
#fullscreenAlt {
font-style: italic;
text-shadow: 0 0 4px rgba(0,0,0,.5);
}
#fullscreenImage { #fullscreenImage {
width: 100%; width: 100%;
max-width: 1200px; max-width: 1200px;
max-height: calc(100% - 22px - 3em);
display: block; display: block;
object-fit: cover; object-fit: contain;
}
#fullscreenAlt {
font-style: italic;
text-shadow: 0 0 4px rgba(0,0,0,.5);
height: 22px;
} }
</style> </style>
<script> <script>
window.onload = () => { window.addEventListener("load", () => {
const gallery = document.getElementById("gallery")
if (!gallery) return
const fsContainer = document.getElementById("fullscreenGallery") const fsContainer = document.getElementById("fullscreenGallery")
if (!fsContainer) return if (!fsContainer) return
@ -87,7 +57,8 @@ const rowCount = Math.ceil(images.length/3)
}) })
const isOpen = () => fsContainer.style.opacity !== "0" const isOpen = () => fsContainer.style.opacity !== "0"
const images = Array.from(gallery.children) as HTMLImageElement[] const images = (Array.from(document.querySelectorAll("main img")) as HTMLImageElement[])
.filter(i => !i.src.endsWith("svg") && i.id != "fullscreenImage")
for (const img of images) { for (const img of images) {
img.addEventListener("click", () => { img.addEventListener("click", () => {
@ -127,5 +98,5 @@ const rowCount = Math.ceil(images.length/3)
break break
} }
}) })
} })
</script> </script>

View file

@ -0,0 +1,35 @@
---
interface Image {
src: string
alt: string
}
interface Props {
images: Image[]
}
const { images } = Astro.props
const rowCount = Math.ceil(images.length/3)
---
<div>
<div class="gallery" style={`grid-template-rows: repeat(${rowCount}, 263px);`}>
{images.map(img => (
<img src={img.src} alt={img.alt} loading="lazy" />
))}
</div>
</div>
<style>
.gallery {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 5px;
width: 100%;
}
.gallery img {
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
}
</style>

View file

@ -28,7 +28,9 @@ const { title, description } = Astro.props
<body> <body>
<div class="limiter"> <div class="limiter">
<Navbar /> <Navbar />
<slot /> <main>
<slot />
</main>
<Footer /> <Footer />
</div> </div>
</body> </body>

View file

@ -1,6 +1,7 @@
--- ---
import Layout from '../layouts/Layout.astro' import Layout from '../layouts/Layout.astro'
import Gallery from '../components/Gallery.astro' import PhotoGrid from '../components/PhotoGrid.astro'
import ImageViewer from '../components/ImageViewer.astro'
--- ---
<Layout <Layout
@ -9,7 +10,7 @@ import Gallery from '../components/Gallery.astro'
> >
<h1>Кинотеатр</h1> <h1>Кинотеатр</h1>
<p>Мои любимые кадры из кино, сериалов и игр.</p> <p>Мои любимые кадры из кино, сериалов и игр.</p>
<Gallery images={[ <PhotoGrid images={[
{src: "/cinema/scott-pilgrim-takes-off/1.avif", alt: "Scott Pilgrim Takes Off (2023)"}, {src: "/cinema/scott-pilgrim-takes-off/1.avif", alt: "Scott Pilgrim Takes Off (2023)"},
{src: "/cinema/angels-egg/1.avif", alt: "Яйцо ангела (1985)"}, {src: "/cinema/angels-egg/1.avif", alt: "Яйцо ангела (1985)"},
{src: "/cinema/spider-man-across-the-spider-verse/1.avif", alt: "Человек-паук: Паутина вселенных (2023)"}, {src: "/cinema/spider-man-across-the-spider-verse/1.avif", alt: "Человек-паук: Паутина вселенных (2023)"},
@ -27,4 +28,5 @@ import Gallery from '../components/Gallery.astro'
{src: "/cinema/night-in-the-woods/1.avif", alt: "Night in the Woods (2017)"}, {src: "/cinema/night-in-the-woods/1.avif", alt: "Night in the Woods (2017)"},
{src: "/cinema/night-in-the-woods/2.avif", alt: "Night in the Woods (2017)"}, {src: "/cinema/night-in-the-woods/2.avif", alt: "Night in the Woods (2017)"},
]} /> ]} />
<ImageViewer />
</Layout> </Layout>

View file

@ -2,6 +2,7 @@
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import Layout from '../../layouts/Layout.astro' import Layout from '../../layouts/Layout.astro'
import Tag from '../../components/Tag.astro' import Tag from '../../components/Tag.astro'
import ImageViewer from '../../components/ImageViewer.astro'
export async function getStaticPaths() { export async function getStaticPaths() {
const blogEntries = await getCollection('notes') const blogEntries = await getCollection('notes')
@ -32,6 +33,8 @@ const formatDate = (date: Date) => {
<div class="content"> <div class="content">
<Content /> <Content />
</div> </div>
<ImageViewer />
</Layout> </Layout>
<style> <style>
@ -51,6 +54,10 @@ const formatDate = (date: Date) => {
.content { .content {
img { img {
width: 100%; width: 100%;
max-width: 700px;
margin-left: calc((100% - min(100%, 700px)) / 2);
margin-bottom: 20px;
cursor: pointer;
} }
th, td { th, td {
border: 1px solid #444; border: 1px solid #444;