Sunday, August 5, 2018

idiosyncratic css hacks for idiosyncratic blogs (photogallery previews)

I've been running my blog for almost two decades. It's a good example of the old DIY web, powered by a bunch of flatfiles in folders and with a mix of Perl (later PHP) to keep it all together. I love the control handcoding everything offers me and what a deep archive of my life online and off it represents (it's awfully cool to be able to dredge up a photo or half-remembered quote from any browser at any time) but I have to build all its features more or less from scratch, including image uploads, its by-month archive, its tag cloud, and its "On This Day" view.

I've grown to like having my photo galleries in-line as blog content, vs making up special pages for them - for instance I did a series of best digital photos of the year going all the way back to 1996. (One lesson I learned too late is that since its emerged as my main archive, I should have always been putting fullsize versions of photos as links behind the resized-for-web versions. I do that now but stuff from the old days is often hard to find in its full pixel glory...)

So I have these pages, but sometimes when a big gallery page shows up in an archive view, it's disruptive, you have to scroll and scroll and scroll to get through it all and onto the next thing.

I didn't want to have to do a lot of work to update the existing galleries, so I made up the following HTML to paste into each set of photos (followed by the closing div)
  <h2 style="cursor:pointer;" class="hidden-gallery-clicker" onclick="var gal = this.nextElementSibling; gal.style.display = gal.style.display == '' ? 'none' : '';">Click for Photo Gallery</h2>
<div class="hidden-gallery" style="display:none;">

That should be pretty clear to any web coder, the gallery was hidden, and then there was a "Click for Photo Gallery" header that would toggle the visibility of the Gallery.

But... that kind of stinks from a UX standpoint, and general visible appeal. Having a little <h2> tag is not much of an affordance at all the terrific photo content awaits.

I realized I'd prefer tiny thumbnails of all the photos, hiding any extra caption information, but when you open the gallery, all the images show up at their browsable size.

And like I said, I didn't want to have to go hand edit a lot of HTML to do this, so whatever trick I used needed to be pure CSS and maybe a smidge of JS that could live in a single place. But my HTML for the photos was sloppy, I had lots of text nodes outside of separate divs or span tags.

Here's the CSS I came up:
.minishot-gallery {
  font-size:0px;
  border:1px dashed #999;
  cursor:pointer;
  padding:8px;
}
.minishot-gallery * {
  display:none;
}

.minishot-gallery a, .minishot-gallery figure {
  display:inline;
}
.minishot-gallery img {
  display: inline-block;
  width:auto;
  height:100px;
  margin:4px;
}
.minishot-gallery .minishot-gallery-caption {
  display: block;
  font-size:20px;
}

.minishot-gallery-caption {
    display: none;   
}
.minishot-gallery .minishot-gallery-caption {
    display: block;   

}

The "display:none" for * was the first step, and changing the font size to zero does a good job of hiding the freefloating text. Images are loaded but displayed at 100px high with width to match. I had to let <a> tags display since most of my images are wrapped in those (the link to the true fullsize versions) and then I was also messing around with using the oddball <figure> tag.

The HTML snippet is then:
<div class="minishot-gallery" onclick="if(this.classList.contains('minishot-gallery')) { this.classList.remove('minishot-gallery'); return false; }">
<h2 class="minishot-gallery-caption">Open Photo Gallery</h2>

That's just a bit of JS saying to remove the minishot-gallery class (which in a neat step gets rid of all the weirdness) I had to be careful with the return value so the first click would open it, but clicks on the images afterwards wouldn't be eaten. There were a lot of nooks and crannies to this kind of hackery, but I'm pleased with the end result.


Of course, I'm mighty pleased with my "best of" photo galleries anyway! Maybe it's too bad I now spend most of my photographic creativity on "One Second Everyday"...

UPDATE: I realized for large galleries this was poor UI - if you click on an image later in the set, you expect to see the image you just clicked on, not the top of the gallery.  So the script now uses "scrollIntoView()" (and I got rid of the inline onclick)

<div class="minishot-gallery">
<h2 class="minishot-gallery-caption">Open Photo Gallery</h2>
<script>
document.addEventListener("DOMContentLoaded", function(event) { 
    var galleries = document.querySelectorAll(".minishot-gallery img");
    Array.prototype.forEach.call(galleries, function(el, i){
        el.addEventListener("click", function(event) {
            if(el.classList.contains('minishot-gallery')) { el.classList.remove('minishot-gallery'); return false; } return true;
        });    
    });

    var elements = document.querySelectorAll(".minishot-gallery img");
    Array.prototype.forEach.call(elements, function(el, i){
        el.addEventListener("click", function(event) {
            let gallery = el.closest('.minishot-gallery');
            if (gallery) {
                el.closest('.minishot-gallery').classList.remove('minishot-gallery');
                el.scrollIntoView({behavior:'smooth'});
                event.preventDefault();
            }
        });
    });
});
</script>
And obviously you might consider putting that script tag somewhere so it's just called once, even if there are multiple galleries open.

No comments:

Post a Comment