Area 51

Sporadic links and brief articles on #design, #CSS, #iOS, #productivity and the things we have #shipped.

Return of the Contact Form

— 2 Min Read

Two years ago, I removed the contact form on this site and published my rationale shortly afterwards. Yesterday, I reversed that decision and restored the form, tackling it from a different angle. Here is how and why.

There were several reasons for removing the form in the first place. The lack of server code and reduced UI appealed to me and I figured most people preferred to use their own email client anyway.

The advantages of plain email:

  • A copy of the message is stored in the Sent mailbox
  • It's possible to save a draft while working
  • People are familiar with their email client interfaces
  • Attachments can be included

Turns out, it might not be that black and white. I noticed a clear drop in leads through the site after removing the form. I also struggled to find an ultimate 'call to action' for the site, because having the most prominent button on a page open an email client is a little odd.

My conclusion is now that, while some people enjoy the freedom and familiarity of email, others like a bit of hand-holding. Filling out a form is more subconscious and requires less thinking than an empty white screen. So why not offer both options? The main roadblock was that I really didn't want to write any server code.

I found a solution in one of my favourite products, Campaign Monitor, which offered all of the features I needed to make this work without a server:

  • A simple database
  • Email address validation
  • Instant Notifications when a form is submitted
  • The ability to send a copy to the user, for their records

All I had to do was write the HTML, CSS and XHR function to communicate with Campaign Monitor's service. This matched up with most of the advantages email had over my previous form, but for those it didn't, I've actually found the restrictions—250-character cap on inputs and no file attachments—to be beneficial; they force brevity, which invokes conversation.

In the end, email is still available and having several methods of contact on offer can't hurt.

Handling Images in JavaScript

— 18 Min Read

When I started redesigning this site, I wanted a simple way to process images, covering a few basic needs. While there are various methods being formed for handling responsive images and display density, there is currently no prescribed approach. This site has no server code, so JavaScript was the obvious choice.

My requirements were:

  • swap in @2x or @3x versions for high-DPI displays, ideally, without downloading the original @1x
  • lazy-load some images in defined contexts
  • display a fallback colour while images are loading in the background to reduce perceived load time
  • embrace responsive design and keep markup simple

I wrote a function which passes one or more imgs through a switch and creates a new Image object for each, appending the desired attributes, before replacing the original with a new img.

I'll go through each stage of imageHandler() in detail before linking to the full source but, first, let's take a look at the markup. This is what an image looks like:

<img src="image.png">

To have that image lazy-load, do this instead:

<img src="{{ site.lazy }}" data-src="image.png">

site.lazy is a Liquid variable I have defined in Jekyll's config.yml, which outputs the smallest possible Base64-encoded .gif, preventing the image from returning a 404 before JavaScript runs:

data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

If you are not using a site generator or CMS, you may have to manually enter this string each time.

While the image loads in the background, it is nice to display a fallback colour:

<img data-fallback="#f33">

Lastly, if @2x or @3x versions of the image are available, we can flag that too. I've called them retina and plus:

<img data-retina data-plus>

Any combination of these attributes and properties can be used. The end result might look something like this:

<img src="{{ site.lazy }}" data-src="image.png" data-retina data-plus data-fallback="#f33">

The imageHandler() function contains a switch with two cases; 'fallback'—called on the DOMContentLoaded—and 'parse', which is called on window.onload.

function imageHandler(action, image, callback) {
    switch (action) {
        case 'fallback':
            
        break;
        case 'parse':
            
        break;
    }
};

'fallback' is simple; it conditions the existence of data-fallback on an img and, if present, sets its value as the background-color:

var fallback = image.hasAttribute('data-fallback');

if (fallback) {
    image.style.backgroundColor = fallback;
}

There is a little more to case 'parse'. First, we prepare a new attributes object and a new image object:

var attrs = new Object();
var newImage = new Image();

Then, we take the original images' attributes and place them into the attrs object we just defined:

for (var i = 0, nodes = image.attributes; i < nodes.length; i++) {
    attrs[nodes[i].nodeName] = nodes[i].nodeValue;
}

Now we have everything we need for JavaScript to do its work. First, lazy-loading:

if ('data-src' in attrs) {
    attrs.src = attrs['data-src'];
}

This will replace the src value with the original data-src attribute we defined in markup.

Next, condition the display's pixel density and define a src for the new image:

var path = attrs.src.substring(0, attrs.src.lastIndexOf('.'));
var extension = attrs.src.split('.').pop();

if (window.devicePixelRatio > 2 && 'data-plus' in attrs) {
    newImage.src = path + '@3x.' + extension;
} else if (window.devicePixelRatio > 1 && 'data-retina' in attrs) {
    newImage.src = path + '@2x.' + extension;
} else {
    newImage.src = attrs.src;
}

Note: this assumes your images are named with the following convention:

  • image.png
  • image@2x.png
  • image@3x.png

As you can see, if window.devicePixelRatio is not greater than 1, we simply use the src value which may or may not have been lazy-loaded.

After that, it is important to remove the attributes that are no longer needed so that anything else (such as the alt attribute) can be preserved and restored:

var garbage = ['src', 'data-src', 'data-retina', 'data-plus', 'data-fallback', 'style'];

for (var i = 0; i < garbage.length; i++) {
    delete attrs[garbage[i]];
}

for (var attr in attrs) {
    newImage.setAttribute(attr, attrs[attr]);
}

The new Image is now ready. Once it has loaded, we can swap it in for the original:

newImage.onload = function() {
    image.parentNode.insertBefore(newImage, image);
    image.parentNode.removeChild(image);

    if (callback) {
        callback();
    }
};

The reason for going through the process of building a new image rather than simply manipulating the attributes of the original is that, with this approach, it is possible to fire a callback once the image loads. For instance, you may want to display some form of activity-indication while the image is loading and remove it onload.

Lastly, we need to call the function on the relevant events:

document.addEventListener('DOMContentLoaded', function() {

    forEach(document.querySelectorAll('img[data-fallback]'), function(index, image) {
        imageHandler('fallback', image);
    });

}, false);

window.addEventListener('load', function() {

    forEach(document.querySelectorAll('img'), function(index, image) {
        imageHandler('parse', image);
    });

}, false);

Note: in this example I am using a custom forEach method to avoid NodeList hacks. More on that here. Feel free to loop over imgs in whichever way you choose.

The full source of the imageHandler() function is available as a Gist here.

This is how every image on 51bits.com is handled. Some images lazy-load, others do not. Some have @3x support, others maybe only @2x. It is not perfect but as there is yet to be a silver bullet for handling images on the web, this fits my needs so I feel it is worth sharing. I'm no JavaScript master so there could be better ways to do this, but it works for me.

Known issues and room for improvement:

  • Stricter validation such as checking that the value of data-fallback is a string before setting it as a background-color
  • Since the images are handled by JavaScript, when the raw source is interpreted by something other than a browser (RSS, print, Safari Reader etc.) the lazy-loaded images will not render
  • During garbage collection, the style attribute is removed which may or may not be acceptable if your images have other inline styling

51bits 3.0

— 1 Min Read

Ten years ago, I registered a business name and decided I would work for myself. With the anniversary looming and the last major redesign of this website being in 2013, an evolution was in order. Today, I'm launching a new mark and website to complement the milestone.

51bits Marks 1-3
Marks 1–3

2.0 did away with a traditional visual portfolio in favour of showcasing products. The problem was that some projects take months or years to complete so parts of the site rarely received fresh content. Apps have now been sectioned off and there are plans to display work-in-progress with a future release.

Writing

I've tried on many occasions to find a way to write more often; sometimes succeeding for short periods of time before letting the frequency taper off. By sharing links supported by shorter, opinionated commentary as well as the occasional article, perhaps I can keep things moving at a steadier pace.

As I did with 2.0, a fair amount of content that I deem obsolete or even incorrect has been archived.

Going Vanilla

I forced myself to take the time and do things right. Every line of code is bespoke. There is no JavaScript framework, no server code and nothing that doesn't need to be here. As a result, I'm seeing significant performance improvements.

There will be many more updates from here. I've already logged over 30 bug and improvement tickets, but it's great to have a relatively fresh slate once again.