How I Wired Up Real Photos and Built a Lights Off Toggle

I have written a couple of these now, one about building the site itself, and another about adding this blog and the projects page. This one is smaller in scope, but it still took a surprising amount of fiddling. It covers two things, swapping the placeholder photos in the photography section for real ones, and building a toggle that turns the whole page dark, the same dark the footer at the bottom of the site already is.

Same audience as always. Someone who wants to build something like this themselves, not necessarily someone who already knows what a CSS variable is. I will explain the ideas as I go without skipping the actual detail, because the detail is usually where the useful bit is hiding.

Why the section needed real photos

The photography section had a scrolling strip of photos that looked fine, except every single tile was a placeholder, just a patterned box with the word photo written on it. It was a good way to test the layout, but it was starting to feel a bit silly sitting there pretending. I already had a folder of real shots sitting on my computer, so the obvious next step was to actually put them in.

Getting the file sizes under control

The photos came straight off the camera, and every single one of them was somewhere between twenty and twenty six megabytes. There were twenty seven of them, so wiring them all in as is would have meant loading something like six hundred and fifty megabytes of images just to see a scrolling strip of photos, which would have made the page painfully slow, especially on a phone.

I used a small command line tool that comes built into a Mac called sips to resize every photo down to a sensible width and compress it a bit, without touching the original files. That single step brought the whole set down from about six hundred and fifty megabytes to under ten, while still looking sharp at the size they actually display on the page.

Wiring up every photo

With the resized copies ready, I swapped every placeholder tile for an actual photo. Because the strip loops seamlessly, as covered in the last post, that meant duplicating the whole set of twenty seven once, so fifty four images sit in the page in total. To stop the browser from trying to load all fifty four at once, each image is marked as something called lazy loading, which just tells the browser to only fetch a photo once it is about to scroll into view rather than the moment the page opens.

A speed problem I caused myself

The moment the real photos went in, the strip suddenly felt like it was racing past. The scrolling speed had originally been tuned back when there were only ten placeholder photos, and swapping in twenty seven real ones made the strip nearly three times longer without me touching the timing to match. The fix was working out how much longer the strip had gotten and stretching the animation's duration by roughly the same amount, so the actual pace, how many pixels move past per second, stayed the same as before.

Making the tiles a bit bigger

Once the real photos were in, they felt a touch small, so I made each tile bigger. That sounds like a one line change, and mostly it was, but making the tiles bigger also makes the whole strip physically longer again, which meant nudging the timing a second time to keep the same pace rather than accidentally speeding it back up.

Photos that are not landscape

Every photo I have added so far happens to be landscape, but that will not always be true, so I wanted the layout to cope properly with a portrait photo whenever one turns up. Previously each tile had a fixed width and a fixed shape, landscape only, which would have squashed a portrait photo down into a thin horizontal sliver and lost most of the actual composition.

The fix was to flip which measurement is fixed. Instead of a fixed width with the height following, every tile now has a fixed height, and the width is worked out from whichever shape suits the photo, landscape or portrait. A small script checks each photo's real dimensions once it loads and tags the portrait ones automatically, so nothing needs to be set by hand whenever a new photo goes in later.

Swapping the link for a switch

The photography section used to end with a plain full gallery link that did not actually go anywhere yet. Rather than leave a dead link sitting there, I turned it into an actual working feature, a toggle labelled turn off the lights that, when switched on, recolours the entire page to match the same dark colours already used in the footer at the bottom of the site.

Making the dark mode readable

Just flipping the background colour on its own would have made most of the text unreadable, since the text colour would have stayed dark on a now dark background. The site already stores its colours as a set of named variables rather than typing the same colour value over and over, so flipping the whole look over meant redefining a handful of those variables together, background, text, card colours, borders, all at once, reusing colours that were already sitting in the footer's own palette rather than inventing new ones.

A couple of buttons slipped through the first pass though. A few of them had been built assuming the background would always stay light, so once the page flipped dark, their text landed on a background close enough in colour that it basically vanished. The fix was forcing those specific buttons to always use black text while the dark mode is switched on, regardless of what colour their background happens to be at the time.

Fixing the toggle itself

The toggle switch needed a few passes to feel right. First, the little sliding circle inside it would visibly shift sideways whenever the label text next to it changed length, since the switch and the text were sharing space in a way that let the text push the switch around. Reordering the two so the switch always sits in a fixed spot, with the text sitting beside it instead, fixed that.

Second, and slightly embarrassing, the switch's on and off states were backwards. A real light switch looks on when the lights are actually on, ours only looked switched on once the lights had been turned off, which is the wrong way around. I swapped which position looks lit up so the switch now matches the actual state of the lights, the same way a real switch on a wall would.

Third was just colour. The first version used a near black colour for the on position, which read as heavy rather than lit up, so I swapped it for a warmer colour already sitting in the site's palette. The little circle inside also used to change colour between the two states, which ended up looking busier than it needed to, so now it stays one consistent colour throughout and only its position moves, with that colour nudged closer to white so it properly reads as the bit that is lit up.

A stutter every few seconds

Once the real photos were in and the toggle was done, I noticed the strip had a small hitch in it, a barely there stutter every few seconds instead of moving perfectly smoothly. It was subtle enough to miss unless you were watching closely, but once I noticed it I could not unsee it.

The timing lined up with roughly how long it takes one tile to cross the screen at the strip's current pace, which was the clue. Each photo only gets fetched once it is about to scroll into view, since it uses lazy loading, but the browser was also decoding each photo the moment it needed to paint it, right there on the same thread that was busy running the scroll itself. That decode work is brief but heavy enough to occasionally get in the way, which showed up as a tiny stutter timed almost exactly to when a new photo entered the strip.

The fix was a single extra attribute on each photo telling the browser it is allowed to decode the image on its own schedule rather than forcing that work to happen exactly when the photo is about to be painted. It sounds like a tiny detail, and it was a one line change in the end, but it is a good example of how a smooth looking animation can still have an invisible bit of work quietly competing for the same resources underneath it.

That fix helped, but it turned out to only be half the story. The strip still had an intermittent jump right as a new photo was about to appear, sometimes there and sometimes not, which was the giveaway that it was tied to network timing rather than decode alone. The photos were still using lazy loading, which only starts fetching each one right as it nears the screen, so every so often a photo's pixels were still arriving at almost the exact moment it needed to show up in the strip. Since the whole set of photos is only a few megabytes now rather than hundreds, there was no real reason to hold them back, so I dropped the lazy loading entirely and let every photo start loading in the background as soon as the page opens. By the time any of them actually scrolls into view, they have had well over a minute to finish loading, so there is nothing left to arrive late.

What is next

The about section still has a placeholder portrait sitting in it, which is the last obvious placeholder left on the site, so that is next on the list. Past that, I am starting to think about whether the scrolling strip is enough on its own or whether a proper full gallery page, actually clickable this time, is worth building out.