Gallery 2020 - MK IV

Note: This work has been a journey. I went from this model, to a model less reliant on magic numbers, to a change using srcset then back to two images, but lazy loading for bandwidth preservation (you are here!). Phew.

After getting fluffy's feedback they went through the site deeper - and found that the srcset was still loading the largest image each time. This perplexed me so I read the specs better this time and found out the srcset size trigger is the viewport width, not the image display width. So it was always going for the largest. Or, I could be misunderstanding the spec and not combining srcset and sizes correctly.

Nuts.

So I rolled the consolidation of images back so I have the thumbnail as an img and a the big display as figure+img+figcaption. To not load this straight away they are display:none and loading="lazy" for twice the protection. Then everything works the same way. During the investigation, I also discovered the appearance tag, which I could apply to the radio buttons to remove all browser-supplied flashings.. that removed some border and padding meaning the thumbnails now aligned together without overspacing.

Version 3 was sacked, caught fire, fell over and sank into the swamp. Maybe version 4 will stay up?

Gallery

How it works

Using cascading stylesheets you can walk down the tree (> for immediate or "[space]" for anywhere down the tree from this joint) or look at subsequent siblings (+) but can't go backwards. How to reach back in the DOM tree to be able to grab the Previous image to turn it into a link (and snazzy thumbnail)? Without Javascript? Inputs!

With inputs you have the basic input and an optional attached label. Clicking the label, for a checkbox, checks the input. And they don't have to be next to each other, you can relate them with the id attribute of the input matching the for attribute of the label. So now I can have the action of clicking a thumbnail reach baaack in the DOM to the element just before the previous thumbnail image. So then:

  • input:checked + label is the Previous image.
  • input:checked + label + figure + input + label is the Current image.
  • input:checked + label + figure + input + label + figure + input + label is the Next image.

Wrapping the label's image in a figure gives me a nicely semantic figcaption element. Normally I just style an em in the comment of a the markdown Image inclusion; but since I'm scattering HTML everywhere to make the DOM match what the CSS will search for I can't use markdown image tags anyway.

The two gotcha cases are the First and Last image. So for the First image I make a dummy input,label pair for the cascading CSS to match. For the Last image I do the same, but I also turn that into the big X close button for the whole shebang. Clicking that image turns the whole thing off because its label is directly attached to it, so it cascades nowhere important and hides itself.

As an added bonus, browsers that have keyboard navigation for Radios/ Selects etc. can use the keyboard to navigate the thumbnails. Winning!

<section class="gallery-2020-4">
<!-- Holds the gallery -->
    <input type="radio" name="gallery-2020-4" id="gallery-2020-4-0" />
    <!-- A Previous button placeholder for the first image. 
    So the CSS hooks onto something for previous -->
    <label></label>
    <figure></figure>
    <input type="radio" name="gallery-2020-4" id="gallery-2020-4-1" /> 
    <label for="gallery-2020-4-0">
        <!-- thumbnail image -->
        <img
            src="https://vonexplaino.com/blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg" />
    </label>
    <figure>
        <!-- Full size image, lazy loaded, with options and a caption -->
        <img sizes="100vw" loading="lazy" srcset="
https://vonexplaino.com/blog/media/2020/05/undeadweightfinal.jpg.jpg 400w,
https://vonexplaino.com/blog/media/2020/05/undeadweightfinal.jpg-228x300.jpg 228w,
https://vonexplaino.com/blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg 120w                
" src="https://vonexplaino.com/blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg"
            alt="Undead Weight" />
        <figcaption>
            <a href="/blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg"><em>Undead
                    Weight</em></a>
        </figcaption>
    </figure>

    <!-- ... images repeat ... -->

    <input type="radio" name="gallery-2020-4" id="gallery-2020-4-close" />
    <!-- The Next button also functions as the Close hook for the last element! -->
    <label for="gallery-2020-4-close">
        X
    </label>
</section>

.gallery-2020-4 { /* Gallery holder. Position relative to provide anchor, flex to fit */ position: relative; width: 100%; display: flex; flex-flow: row wrap; align-items: center; justify-content: center; } } .gallery-2020-4 label:last-of-type { /* Don't show that last image, it's a secret close image */ display: none; margin: 0; /* Remove browser-standard appearance to shrink further */ -webkit-appearance: none; -moz-appearance: none; -ms-appearance: none; -o-appearance: none; appearance: none; } .gallery-2020-4 input { /* display:none inputs can't be interacted with, so shrink it _really_ small */ transform: scaleX(0.00001); } .gallery-2020-4 label { /* Attractive thumbnails */ height: 100px; width: 100px; max-width: var(--gallery-thumbnail-height); max-height: var(--gallery-thumbnail-height); /* the next three centre the image in the thumbnail l<->r and t<->b */ display: flex; align-items: center; justify-content: center; background: url(/theme/images/Leather.png) repeat #1f0b02; /* stitches */ border: 2px dashed #EADAC2; box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5); border-radius: 10px; margin: 5px; } .gallery-2020-4 label + figure { /* Reinforce those sizes */ width: var(--gallery-thumbnail-height); height: var(--gallery-thumbnail-height); /* Initially don't show it so lazy loading avoids it */ display: none; } .gallery-2020-4 label img { /* _REINFORCE THOSE SIZES. Also display centred */ display: inline; vertical-align: middle; max-width: var(--gallery-thumbnail-height); max-height: var(--gallery-thumbnail-height); } .gallery-2020-4 label + figure figcaption { /* Hide caption for thumbnail */ display: none; } .gallery-2020-4 input:checked+label { /** Previous button is the first Label after a checked input **/ /** Put it on the left, over the image */ position: fixed; left: 0; top: calc(50% - 50px); padding: auto 0; z-index: 3; display: flex; align-items: center; justify-content: center; background: url(/theme/images/Leather.png) repeat #1f0b02; border: 2px dashed #EADAC2; width: calc(100% - 12px); margin-left: 4px; box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5); border-top-left-radius: 0; border-bottom-left-radius: 0; border-top-right-radius: 10px; border-bottom-right-radius: 10px; } .gallery-2020-4 input:checked+label+figure+input+label+figure { /** Focused image is the second label sibling from the selected input **/ position: fixed; left: 0; top: 0; text-align: center; height: calc(100% - 20px); z-index: 2; padding: 0; margin: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2px dashed #EADAC2; width: calc(100% - 20px); max-width: initial; max-height: initial; margin-left: 4px; box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5); border-radius: 10px; /** Beautiful cushiony background **/ /** from https://leaverou.github.io/css3patterns/#upholstery */ background: radial-gradient(hsl(0, 100%, 27%) 4%, hsl(0, 100%, 18%) 9%, hsla(0, 100%, 20%, 0) 9%) 0 0, radial-gradient(hsl(0, 100%, 27%) 4%, hsl(0, 100%, 18%) 8%, hsla(0, 100%, 20%, 0) 10%) 50px 50px, radial-gradient(hsla(0, 100%, 30%, 0.8) 20%, hsla(0, 100%, 20%, 0)) 50px 0, radial-gradient(hsla(0, 100%, 30%, 0.8) 20%, hsla(0, 100%, 20%, 0)) 0 50px, radial-gradient(hsla(0, 100%, 20%, 1) 35%, hsla(0, 100%, 20%, 0) 60%) 50px 0, radial-gradient(hsla(0, 100%, 20%, 1) 35%, hsla(0, 100%, 20%, 0) 60%) 100px 50px, radial-gradient(hsla(0, 100%, 15%, 0.7), hsla(0, 100%, 20%, 0)) 0 0, radial-gradient(hsla(0, 100%, 15%, 0.7), hsla(0, 100%, 20%, 0)) 50px 50px, linear-gradient(45deg, hsla(0, 100%, 20%, 0) 49%, hsla(0, 100%, 0%, 1) 50%, hsla(0, 100%, 20%, 0) 70%) 0 0, linear-gradient(-45deg, hsla(0, 100%, 20%, 0) 49%, hsla(0, 100%, 0%, 1) 50%, hsla(0, 100%, 20%, 0) 70%) 0 0; background-color: #300; background-size: 100px 100px; } .gallery-2020-4 input:checked+label+figure+input+label+figure img { /** Give it an image border, for niceness **/ /** Max width is the Viewport Width - 20px for margin and 10 px / 2 for border */ max-width: calc(100vw - 20px - 10px / 2); max-height: initial; padding: 2px; background: var(--paper-image); } .gallery-2020-4 input:checked+label+figure+input+label+figure figcaption { /** Match the caption background to the image border, and pretty **/ background: var(--paper-image) rgba(255, 255, 0, 0.1); padding: 5px; border-radius: 10px; display: inherit; } .gallery-2020-4 input:checked+label+figure+input+label+figure+input+label { /** Next button, like the Previous button, third from the selected item **/ position: fixed; top: calc(50% - 50px); right: 0; z-index: 3; display: flex; align-items: center; justify-content: center; background: url(/theme/images/Leather.png) repeat #1f0b02; border: 2px dashed #EADAC2; width: calc(100% - 12px); margin-left: 4px; box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5); border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 0; border-bottom-right-radius: 0; } .gallery-2020-4 input:checked+label+figure+input+label+figure+input+label:last-of-type { /** If Next is also Close, then over-ride the above with the close **/ position: fixed; top: 0; right: 0; z-index: 3; display: flex; align-items: center; justify-content: center; background: none; border: none; box-shadow: none; width: var(--gallery-thumbnail-height); height: var(--gallery-thumbnail-height); font-size: 95px; font-weight: bold; } .gallery-2020-4 input:checked~label:last-of-type { /** If Next is not also Close, close needs its own style **/ position: fixed; top: 0; right: 0; z-index: 3; display: flex; align-items: center; justify-content: center; background: none; border: none; box-shadow: none; width: var(--gallery-thumbnail-height); height: var(--gallery-thumbnail-height); font-size: 95px; font-weight: bold; } .gallery-2020-4 input:checked+label:last-of-type { /** If closed is selected then... just go away **/ display: none; } .gallery-2020-4 input:first-child+label { display: none !important; }