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!

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