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.
Deleted Gallery As Superceded
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 }