Gallery 2020 - MK III

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 (this one!) then back to two images, but lazy loading for bandwidth preservation. Phew.

Received feedback on Version 2 from a Discord server I frequent:

    [04:16] fluffy: Ooh, neat
    [04:18] fluffy: you could also use img srcset to serve up a smaller rendition for the thumbnail
    [04:19] fluffy: also CSS transitions might make it a bit less jarring :slight_smile:

I thought that was a great solution to having the images load as full images, but hidden, before the user got a chance to decide whether or not to use them. What a bandwidth saver! I started playing with the code in my mind, enjoying the exercise and went to bed.

Woke up at 5:30am realising I'd missed a key point. I'm not using the Thumbnails as a navigation tool bar anymore, so only one version of the image is shown at any one time. That means I can use srcset to combine the Thumbnail and the Full version of the images into the one tag. THAT is a bandwidth saver. A small burst of excited coding later and I have this new version.

Gallery

How it works

Using cascading stylesheets you can walk down the tree (>) or look at 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 + input + label is the Current image.
  • input:checked + label + input + label + 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!

 1<section class="gallery-2020-3">
 2<!-- Holds the gallery -->
 3    <input type="radio" name="gallery-2020-3" id="gallery-2020-3-0" />
 4    <!-- A Previous button placeholder for the first image. 
 5    So the CSS hooks onto something for previous -->
 6    <label></label>
 7    <input type="radio" name="gallery-2020-3" id="gallery-2020-3-1" /> 
 8    <!-- An image -->
 9    <label for="gallery-2020-3-0">
10    <!-- Label to link this thumbnail with the _previous_ radio,
11    so CSS cascade can find Previous, Current and Next images -->
12        <figure>
13            <img sizes="100vw" srcset="
14                /blog/media/2020/05/undeadweightfinal.jpg.jpg 400w,
15                /blog/media/2020/05/undeadweightfinal.jpg-228x300.jpg 228w,
16                /blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg 120w                
17                " src="/blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg"
18                alt="Undead Weight" />
19            <!-- srcset combines thumbnail and big images in a delightful package -->
20            <figcaption>
21            <!-- Semantic captioning! Also a link to download or visit a page -->
22                <a href="/blog/media/2020/05/undeadweightfinal.jpg-120x0-c-default.jpg">
23                    <em>Undead Weight</em>
24                </a>
25            </figcaption>
26        </figure>
27    </label>
28
29    <!-- ... images repeat ... -->
30
31    <input type="radio" name="gallery-2020-3" id="gallery-2020-3-close" />
32    <!-- The Next button also functions as the Close hook for the last element! -->
33    <label for="gallery-2020-3-close">
34        X
35    </label>
36</section>
  1    .gallery-2020-3 {
  2        /* Gallery holder. Position relative to provide anchor, flex to fit */
  3        position: relative;
  4        width: 100%;
  5        display: flex;
  6    }
  7
  8    .gallery-2020-3 label:last-child {
  9        /* Don't show that last image, it's a secret close image */
 10        display: none;
 11    }
 12
 13    .gallery-2020-3 input {
 14        /* display:none inputs can't be interacted with, so shrink it _really_ small */
 15        transform: scaleX(0.00001);
 16    }
 17
 18    .gallery-2020-3 label {
 19        /* Attractive thumbnails */
 20        height: 100px;
 21        width: 100px;
 22        max-width: var(--gallery-thumbnail-height);
 23        max-height: var(--gallery-thumbnail-height);
 24        /* the next three centre the image in the thumbnail l<->r and t<->b */
 25        display: flex;
 26        align-items: center;
 27        justify-content: center;
 28        background: url(/theme/images/Leather.png) repeat #1f0b02;
 29        /* stitches */
 30        border: 2px dashed #EADAC2;
 31        box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5);
 32        border-radius: 10px;
 33    }
 34
 35    .gallery-2020-3 label figure {
 36        /* Reinforce those sizes */
 37        width: var(--gallery-thumbnail-height);
 38        height: var(--gallery-thumbnail-height);
 39    }
 40
 41    .gallery-2020-3 label img {
 42        /* _REINFORCE THOSE SIZES. Also display centred */
 43        display: inline;
 44        vertical-align: middle;
 45        max-width: var(--gallery-thumbnail-height);
 46        max-height: var(--gallery-thumbnail-height);
 47    }
 48
 49    .gallery-2020-3 label img+figcaption {
 50        /* Hide caption for thumbnail */
 51        display: none;
 52    }
 53
 54    .gallery-2020-3 input:checked+label {
 55        /** Previous button is the first Label after a checked input **/
 56        /** Put it on the left, over the image */
 57        position: fixed;
 58        left: 0;
 59        top: calc(50% - 50px);
 60        padding: auto 0;
 61        z-index: 3;
 62        display: flex;
 63        align-items: center;
 64        justify-content: center;
 65        background: url(/theme/images/Leather.png) repeat #1f0b02;
 66        border: 2px dashed #EADAC2;
 67        width: calc(100% - 12px);
 68        margin-left: 4px;
 69        box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5);
 70        border-top-left-radius: 0;
 71        border-bottom-left-radius: 0;
 72        border-top-right-radius: 10px;
 73        border-bottom-right-radius: 10px;
 74    }
 75
 76    .gallery-2020-3 input:checked+label+input+label {
 77        /** Focused image is the second label sibling from the selected input **/
 78        position: fixed;
 79        left: 0;
 80        top: 0;
 81        text-align: center;
 82        height: calc(100% - 20px);
 83        z-index: 2;
 84        background: white;
 85        padding: 0;
 86        margin: 10px;
 87        display: flex;
 88        align-items: center;
 89        justify-content: center;
 90        border: 2px dashed #EADAC2;
 91        width: calc(100% - 20px);
 92        max-width: initial;
 93        max-height: initial;
 94        margin-left: 4px;
 95        box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5);
 96        border-radius: 10px;
 97        /** Beautiful cushiony background **/
 98        /** from https://leaverou.github.io/css3patterns/#upholstery */
 99        background:
100            radial-gradient(hsl(0, 100%, 27%) 4%, hsl(0, 100%, 18%) 9%, hsla(0, 100%, 20%, 0) 9%) 0 0,
101            radial-gradient(hsl(0, 100%, 27%) 4%, hsl(0, 100%, 18%) 8%, hsla(0, 100%, 20%, 0) 10%) 50px 50px,
102            radial-gradient(hsla(0, 100%, 30%, 0.8) 20%, hsla(0, 100%, 20%, 0)) 50px 0,
103            radial-gradient(hsla(0, 100%, 30%, 0.8) 20%, hsla(0, 100%, 20%, 0)) 0 50px,
104            radial-gradient(hsla(0, 100%, 20%, 1) 35%, hsla(0, 100%, 20%, 0) 60%) 50px 0,
105            radial-gradient(hsla(0, 100%, 20%, 1) 35%, hsla(0, 100%, 20%, 0) 60%) 100px 50px,
106            radial-gradient(hsla(0, 100%, 15%, 0.7), hsla(0, 100%, 20%, 0)) 0 0,
107            radial-gradient(hsla(0, 100%, 15%, 0.7), hsla(0, 100%, 20%, 0)) 50px 50px,
108            linear-gradient(45deg, hsla(0, 100%, 20%, 0) 49%, hsla(0, 100%, 0%, 1) 50%, hsla(0, 100%, 20%, 0) 70%) 0 0,
109            linear-gradient(-45deg, hsla(0, 100%, 20%, 0) 49%, hsla(0, 100%, 0%, 1) 50%, hsla(0, 100%, 20%, 0) 70%) 0 0;
110        background-color: #300;
111        background-size: 100px 100px;
112    }
113
114    .gallery-2020-3 input:checked+label+input+label>figure {
115        /** Make sure it's big */
116        width: 100%;
117        height: 100%;
118        display: flex;
119        flex-direction: column;
120        align-items: center;
121        justify-content: center;
122    }
123
124    .gallery-2020-3 input:checked+label+input+label>figure>img {
125        /** Give it an image border, for niceness **/
126        /** Max width is the Viewport Width - 20px for margin and 10 px / 2 for border */
127        max-width: calc(100vw - 20px - 10px / 2);
128        max-height: initial;
129        padding: 2px;
130        background: var(--paper-image);
131    }
132
133    .gallery-2020-3 input:checked+label+input+label>figure>figcaption {
134        /** Match the caption background to the image border, and pretty **/
135        background: var(--paper-image) rgba(255, 255, 0, 0.1);
136        padding: 5px;
137        border-radius: 10px;
138        display: inherit;
139    }
140
141    .gallery-2020-3 input:checked+label+input+label+input+label {
142        /** Next button, like the Previous button, third from the selected item **/
143        position: fixed;
144        top: calc(50% - 50px);
145        right: 0;
146        z-index: 3;
147        display: flex;
148        align-items: center;
149        justify-content: center;
150        background: url(/theme/images/Leather.png) repeat #1f0b02;
151        border: 2px dashed #EADAC2;
152        width: calc(100% - 12px);
153        margin-left: 4px;
154        box-shadow: 0 0 0 4px #1f0b02, 2px 1px 6px 4px rgba(10, 10, 0, 0.5);
155        border-top-left-radius: 10px;
156        border-bottom-left-radius: 10px;
157        border-top-right-radius: 0;
158        border-bottom-right-radius: 0;
159    }
160
161    .gallery-2020-3 input:checked+label+input+label+input+label:last-child {
162        /** If Next is also Close, then over-ride the above with the close **/
163        position: fixed;
164        top: 0;
165        right: 0;
166        z-index: 3;
167        display: flex;
168        align-items: center;
169        justify-content: center;
170        background: none;
171        border: none;
172        box-shadow: none;
173        width: var(--gallery-thumbnail-height);
174        height: var(--gallery-thumbnail-height);
175        font-size: 95px;
176        font-weight: bold;
177    }
178
179    .gallery-2020-3 input:checked~label:last-child {
180        /** If Next is not also Close, close needs its own style **/
181        position: fixed;
182        top: 0;
183        right: 0;
184        z-index: 3;
185        display: flex;
186        align-items: center;
187        justify-content: center;
188        background: none;
189        border: none;
190        box-shadow: none;
191        width: var(--gallery-thumbnail-height);
192        height: var(--gallery-thumbnail-height);
193        font-size: 95px;
194        font-weight: bold;
195    }
196
197    .gallery-2020-3 input:checked+label:last-child {
198        /** If closed is selected then... just go away **/
199        display: none;
200    }
201
202    .gallery-2020-3 input:first-child+label {
203        display: none !important;
204    }