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}