HOWTO: Wall of photographs in jQuery/HTML/CSS

Gallery in action

The other half of the Funcan theme was an insertable photo gallery. Funcan was due at the start of March, and the gallery now at the end, and while I take a break for food I want to jot down some quick notes on progress so far:

I kicked around a few different looks, given the simple brief that

  1. The gallery’s appearance must fit with the Funcan theme.
  2. The gallery should preferably be insertable into any given container, and Just Work.

and truth be told, most of them looked awful, either trite, slow, or overwrought, so I decided for steal from those who have done better. I am-very unsurprisingly-not the first person who had had the idea to steal Google or Flickr’s wall of photographs, and (equally surprisingly) the algorithm is simple. There are a bunch of other well-indexed blog posts that cover the algorithm in greater or lesser depth, but those posts all turn into so much wankery about either how elegant their code, or how smart they are for for their understanding of the deeper math involved.

My gripes aside, I do appreciate the posts, but I have never been able to grep that much detail in one go. I just don’t work that way. I sat down with pen and paper, and realized that for each given row of images:

  1. First make each image the same height, while you preserve proportions. This can be done via CSS.
  2. Find the total length of the row (add the width of each image together).
  3. Divide the length of the row by the desired width (in this case the .gallery div) to get the ratio.
  4. Change the height of the each individual image by this ratio.

Gallery HTML:
The base HTML for the gallery is simple. Images are arbitrary. You do not need to determine sizes before you begin. A simple RNG function allows you to get a varying number of images per row.

<div class="gallery">
    <div class="row">
        <img src="img/six_six.jpg" />
        <img src="img/six_nine.jpg" />
        <img src="img/nine_six.jpg" />
        <img src="img/nine_nine.jpg" />
        <img src="img/nine_nine.jpg" />
    </div>
    <div class="row">
        <img src="img/six_six.jpg" />
        <img src="img/nine_nine.jpg" />
    </div>
    <div class="row">
        <img src="img/six_nine.jpg" />
        <img src="img/six_nine.jpg" />
        <img src="img/six_six.jpg" />
    </div>
    <div class="row">
        <img src="img/six_nine.jpg" />
        <img src="img/six_nine.jpg" />
        <img src="img/six_six.jpg" />
        <img src="img/nine_nine.jpg" />
        <img src="img/six_nine.jpg" />
        <img src="img/nine_nine.jpg" />
    </div>
</div>

Gallery CSS:
The CSS is likewise simple. Space between images is set via padding, and they are giving a box-sizing to stop the div from growing.

.gallery {
    margin: 0 auto;
    margin-bottom: 6px;
    margin-top: 6px;
    width: 99%;
}

.row {
    margin-bottom: 6px;
    overflow: auto;
}

.row img {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    float: left;
    height: 100px;
    padding: 0 3px 0 3px;
    width: auto;
}

.row img:first-child {
    padding-left: 0;
}

.row img:last-child {
    padding-right: 0;
}

Gallery JavaScript:
This algorithm needs to be tweaked slightly-depending on how the widths round, each div will be a varying few pixels short of the .gallery div. A simple way to pad sizes would be to find the difference (how short you are), round up to the nearest whole number, and divide it between each image as padding.

var gal = 'uber-gallery';
var row = 'uber-row';
var rowMin = 3;
var rowMax = 6;

function addGalleryID(obj) {
    var count = 0;

    $(obj).each(function () {
        $(this).attr('id', 'ug-' + count);
        count++;
    }); 
}

function addRow(obj) {
    $(obj).append('<div class="' + row + '"></div>');
}

function sRandom(min,max) {
    return Math.floor(Math.random() * (max - min) + min);
}

function addGalleryRows(obj) {
    var imgArr = $(obj).children('img').toArray();
    addRow(obj);

    $(imgArr).each(function (i) {
        $('.' + row).last().append(imgArr[i]); 
        var rowLength = 4;

        if ($('.' + row).last().children().size() >= rowLength && (imgArr.length - 1 - i) >= 2) {
            addRow(obj);
        }
    });
}

function changeObjHeight(obj, amount) {
    $(obj).css('height', amount + 'px');
}

function changeObjWidth(obj, amount) {
    $(obj).css('width', amount + 'px');
}

function vertCenterObj(obj) {
    $(obj).css('margin-top', $(window).height() * 0.5 - $(obj).height()  * 0.5);
}

function getRowWidth(obj) {
    var sum = 0;

    $(obj).children('img').each(function () {
        sum += $(this).outerWidth();
    });

    return sum;
}

function orderRowImages(obj) {
    $(obj).children('.' + row).each(function () {
        var sum = getRowWidth(this);
        var ratio = parseFloat($(obj).width() / sum);

        $(this).children('img').each(function () {
            var newHeight = Math.round($(this).height() * ratio);
            changeObjHeight(this, newHeight);
        });

        var diff = getRowWidth(this) - ($(obj).width() - 1);

        $(this).children('img:last-child').css(
            'width',
            $(this).children('img:last-child').width() - diff + 'px'
        );
    });
}

$(window).load(function() {
    $('.' + gal).each(function() { 
        addGalleryID(this);
        addGalleryRows(this);
        orderRowImages(this);
    });
});

$(window).resize(function() {
    $('.' + gal).each(function() { 
        orderRowImages(this);
    });
});

You can find a live version on Peppermint.


Funcan

Although his site is offline at this exact moment in time, 091 Labs’s Duncan Thomas asked me to write the new theme for his site. “Funcan” is a dark, responsive theme inspired by terminal output on a Linux system. There are strong contrasts between the dark background and bright text, modern aesthetics in the header and layout, a fluid and responsive design. You can play with the theme temporarily on Peppermint, fork the repo on Github, or just gaze slackly in astonishment at the screenshots:

Funcan theme on the desktop
Funcan theme on iOS


void ValentinesDay()

Color roseFlower = Color.FromArgb(246,74,138);
Color violetFlower = Color.FromArgb(111,111,255);
bool isSugarSweet = true;
int you = 2;

Linux course post-mortem

CHANGE ME

There was a dearth of classes going on at 091 Labs over the summer, so I stepped up to the plate at the start of August and advertised a Linux programming class, spread over four weeks: Learn the fundamentals of programming through the Bourne Again Shell, and all its queerness and oddities of syntax. The first class was held on August 19 in the hackerspace with the three successive classes each held on the following Monday. The crowd was small, and only shrunk, but compared to some classes I’ve attended and run, they were pretty attentive.

I, me, myself, I too learned a lot from this class, both in and out of Bash: Why certain things were handled by Bash in a certain manner (subshells comes to mind-I’ve been using them since day one, but never really thought about how they worked); how to perform tasks I thought difficult or weird (advanced awk and sed operations); how to parse the material for the class in an understandable manner; and odd as it is to admit, how to make eye contact and just interact in a relaxed and confident manner, given how long I’ve been avoiding just this.

The Bash part of the learning was fun, and I can comfortably say that I came out of each week with a better understanding than I did going in. The case structure, variables, oddities, efficiently testing items, parsing integers, math operations, and even some binary operations too were all items I picked up from basically scratch.

I’ve also built up a good body of material that I can just turn around and give to someone who wants to learn the Bourne shell, as simply as just sharing the Google Drive folder with them. The slides are concise if sparse, and cover the fundamental operations without delving into anything Linux-specific: You can follow through the slides and examples through Terminal on OS X, or Cygwin on Windows.

On the classes themselves, I learned some things: A Facebook event is where workshops go to die; Facebook users are as conditioned to ignore event invitations as they are blinking advertisements. The 091labs.com blog post, word of mouth through members and friends, and repeated postings on the 091 Labs twitter and Facebook accounts were what brought people in the door.

I did some things right, and others wrong:

  • I should have had printed handouts with exercises and solutions available.
  • A split-screen terminal (Terminator) was excellent: I could simultaneously show creation and execution without having to either tab away or change desktop.
  • Offering Cygwin and SSH access was a fantastic idea. It saved me from having to mess with people installing Linux for the first time.
  • Problems with Windows’ line-endings cost me time and frustration. Everything seems coded okay, but then you run it and whoops. Ditto this and people deciding to try vim despite my stern warnings away from it.
  • RE the above: Next time I need to mandate a good text editor for Windows and OSX, either Sublime Text. If you use Notepad or Wordpad or TextEdit-or any kind of rich text editor for that matter, you deserve to be shunned.
  • Relying on slides for the first week of solutions limited me and only served to fluff out the slideshow. Coding and uploading solutions separately allowed me to append notes at length.

The final workshop is tomorrow, and instead of a segue into C#, I will probably stick with more real-world examples of scripts, give more exercises, and invite my students to offer up problems for me to handle programmatically.

In the end it was fun, the money brought in helped the hackerspace, and I can port this material to other programming languages (C#, probably) with minimal effort. B++, would run again.