Is Translate() Is Better Than Pos:abs Top/left?

SOURCE: http://blog.tumult.com/2013/02/28/transform-translate-vs-top-left/

I recently came across Christian Heilmann’s Five things you can do to make HTML5 perform better article, where point number three is to use CSS Transforms instead of position and top/left when moving elements across a page. It is a practice increasingly championed, such as in the noted Paul Irish video, Why Moving Elements With Translate() Is Better Than Pos:abs Top/left. The central point is when animating you’ll achieve higher frame rates using this transform code:

element.style["-webkit-transform"] = "translate(xPos, yPos)";

than this code:

element.style.position = "absolute";
element.style.top = xPos;
element.style.left = yPos;

Both will change the displayed element position on a web page, though if performance is no object you may choose to semantically differentiate the techniques for different purposes. Translate also allows non-integral values which can result in smoother animations when moving an element slowly over a long period of time.

I still found the advice of always preferring to use transforms surprising; a while ago in developing Tumult Hype I had run some tests on the two different techniques and recalled transform not necessarily being faster. But, browsers have changed a lot since then. I couldn’t find any readily available benchmarks online, so I decided to make a new test and measure to see whether the advice of always using transform was valid.

The Benchmark Test

For my test, I wanted to compare various aspects involved in changing positions and compositing a page. These are:

  1. transform:translate vs. position:absolute and top/left
  2. CSS Transitions vs. custom JavaScript heartbeat animations using requestAnimationFrame()
  3. Graphics acceleration on vs. off (forced via rotateY hack)
  4. Opaque elements vs. partially transparent elements

The test itself is relatively simple, it generates n boxes and animates them from point A to point B. It runs the 16 combinations serially. I’ve posted the test online so others can run it and pick it apart if need be:

https://github.com/tumult/PerformanceBenchmarks/blob/master/MovingBoxes.html

My desktop testing system is a 27″ iMac (Mid 2011) with a 3.4 Ghz Intel Core i7 processor and AMD Radeon HD 6970M 1024 MB Graphics card. I also tested on an iPad Mini and iPhone 5. FPS on the desktop were taken using Quartz Debug and Instruments for iOS. I ran the test several times to make sure the results were repeatable. The FPS itself has some degree of eye-balling; the main point was to relatively compare various techniques.

Results

All numbers are in frames per second. Because Firefox’s performance was too slow to draw conclusions, I re-ran using only 500 boxes to help bring out results from the tests. I also used 500 boxes for testing Mobile Safari for the same reason. For fun, I ran IE 10 in VMWare to see how it performed (surprisingly well), but don’t count the results as valid since it isn’t a real world setup.

PerformanceTestingResults

Download as: Image | .numbers document

Primary Conclusions

  • Setting the top/left properties will be faster than using a transform if you are not using transitions
  • If the target is WebKit, the fastest frame rates will come from using transitions with the translate property, and forcing graphics acceleration for Safari/Mobile Safari (Chrome automatically does this)
  • If compositing non-opaque items, forcing graphics acceleration in WebKit will have a huge performance boost in Safari/Mobile Safari and a modest boost in Chrome
  • If compositing only opaque items, forcing graphics acceleration in WebKit will have a negative impact on performance

Other Notes

  • The rotateY(0deg) method to force graphics acceleration has no significant effect on Firefox
  • Safari is ~1.5x faster than Chrome across the tests
  • Firefox is ridiculously slow at this test, even compared to IE 10 running through VMWare — Update: see below.

Q: What does Tumult Hype use for its animations?

Unfortunately there’s no straightforward answer! Tumult Hype v1.6 prefers to use top/left positioning with requestAnimationFrame() and graphics acceleration enabled. In some cases we may automatically disable graphics acceleration or use transforms to workaround specific browser bugs. There’s a checkbox in the Document inspector to disable graphics acceleration if manual intervention is required. On iOS we cannot use requestAnimationFrame() at all due to a Mobile Safari bug*, and clearly cannot use it on older browsers because it’s not supported. Tumult Hype v1.0 used transitions when available for the best performance, however we encountered issues keeping animations synchronized, and this technique would not work with newer features such as our bounce effect and pausing/resumes/go-to-time features so it was abandoned in v1.5.

Q: Why shouldn’t I always use CSS Transitions?

  • They are difficult to programmatically manipulate.
  • They are missing many features which might be required for interactivity (getting current positions, synchronizing with other animations, pausing, etc.).

David Poyner has an elaboration I agree with.

Q: Why use transforms if not using CSS Transitions or CSS Animations?

  • You may encounter browser bugs*. They go in both directions and sometimes using top/left is the only workaround.

I recommend reading Zachary Johnson’s comment for more information.

Q: Why not always use the graphics acceleration hack?

  • It’s a hack! Browsers may be able to make more intelligent decisions, and in some cases are faster (opaque elements).
  • You may encounter browser bugs*.
  • It could have battery life implications.

Further Testing

This is admittedly a simple and singular test I hope accurately covers general performance characteristics which might be seen in the real world. Because the GPU is a factor, it would be interesting to test against different hardware. Completely missing is a test on CSS Animations, but my assumption is their performance characteristics are the same as transitions. With more time, I would vary box sizes, textures, and numbers. I would also test different effects known to cause performance issues such as box shadows and blurs. I’d also test against the latest WebKit nightlies and Chrome Canary builds, perhaps compare against older versions of the browsers, and run a native tests on Windows and Android. I’m also curious about scale performance vs. width/height.

End Note

There’s no single path to best performance where web browsers are concerned. The first step in achieving performance for your site is measurement.

Short URL → http://ednh.ca/ZOnrJn

jQuery Modal

SOURCE: http://www.jacklmoore.com/notes/jquery-modal-tutorial

A basic modal is actually easy to code, and is sometimes more appropriate than a feature-full modal or lightbox plugin. This guide will walk through writing the necessary CSS and JavaScript.


See The Demo

Drafting the HTML

I like to create a rough draft just using HTML and CSS, leaving the scripting for last. I’ll write the HTML first to structure the modal, even though the final HTML will be generated by the script.

<div id='overlay'></div>
<div id='modal'>
    <div id='content'>No JavaScript Yet!</div>
    <a href='#' id='close'>close</a>
</div>

This modal will have two root elements: #overlay and #modal. The overlay is going to be a semi-transparent layer that fills the visitor’s viewport. This turns the modal into a focal point, and reduces focus for the rest of the page.

The CSS

#overlay {
    position: fixed; 
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: #000;
    opacity: 0.5;
    filter: alpha(opacity=50);
}

The overlay has a fixed position that is relative to the visitor’s viewport. The 100% width and height refer to the viewport size, so we get complete coverage no matter what part of the page the visitor is actually viewing. Unfortunately, fixed position is not supported in iOS 4 or below. IE8 and IE7 do not support opacity, so an alpha filter is used instead.

#modal {
    position:absolute;
    background:url(tint20.png) 0 0 repeat;
    background:rgba(0,0,0,0.2);
    border-radius:14px;
    padding:8px;
}

#content {
    border-radius:8px;
    background:#fff;
    padding:20px;
}

The important thing here is that #modal will be absolutely positioned, the rest of the rules just provide a nice default appearance.

The RGBa background will give a black fill (0,0,0) at 20% opacity (0.2), which will overwrite the previous background property. IE8 and IE7 do not support RGBa, so they will continue to use the previous background property which uses a semi-transparent PNG background image.

I gave #modal a larger border-radius than #content. This made a border that is natural looking and consistent in thickness. IE8 and IE7 do not support border-radius and will instead display the normal square corners.

#close {
    position:absolute;
    background:url(close.png) 0 0 no-repeat;
    width:24px;
    height:27px;
    display:block;
    text-indent:-9999px;
    top:-7px;
    right:-7px;
}

The close button is absolutely positioned relative #modal, since #modal is it’s closest ancestor element with positioning. Since I used an anchor element for the close button, it’s default display of inline must be changed to block in order to apply a fixed width and height to it. Text-indent moves the anchor’s text offscreen so that it will not overlay the background image.

See The Work In Progress

The Script

I would like the modal to handle the following things:

  • Create it’s own HTML and append it to the document
  • Display specified content
  • Open on command
  • Close on command
  • Be positioned in the center of the viewport
  • Stay centered if the viewport is resized

Let’s stump out the function and variable names:

var modal = (function(){
    var 
    method = {},
    $overlay,
    $modal,
    $content,
    $close;

    // Center the modal in the viewport
    method.center = function () {};

    // Open the modal
    method.open = function (settings) {};

    // Close the modal
    method.close = function () {};

    return method;
}());

Here we have a function that is immediately executed and returns an object containing the methods needed to control the modal. This object is assigned to the modal variable. The rest of the variables are references to their corresponding elements in the document. Now lets fill in the missing code.

Append The HTML

$overlay = $('<div id="overlay"></div>');
$modal = $('<div id="modal"></div>');
$content = $('<div id="content"></div>');
$close = $('<a id="close" href="#">close</a>');

$modal.hide();
$overlay.hide();
$modal.append($content, $close);

$(document).ready(function(){
    $('body').append($overlay, $modal);
});

This generates the same HTML structure used earlier in the guide, now created and added to the document by our script.

Center Method

method.center = function () {
    var top, left;

    top = Math.max($(window).height() - $modal.outerHeight(), 0) / 2;
    left = Math.max($(window).width() - $modal.outerWidth(), 0) / 2;

    $modal.css({
        top:top + $(window).scrollTop(), 
        left:left + $(window).scrollLeft()
    });
};

Here we balance the distance between each side of the viewport and each side of the modal. jQuery’s .outerWidth() and .outerHeight() are used because borders and padding should also be factored in. I used Math.max() so that I could be sure that no value returned would be less than 0. This means that if the modal is larger than the visitor’s viewport, it will be positioned in the top left of the viewport rather running beyond that where it could be potentially being inaccessible. $(window).scrollTop() returns the position of the vertical scrollbar, which is the distance from the top of the document to the top of the viewport. This needs to be added to account for any scrolling that has happened.

Open Method

method.open = function (settings) {
    $content.empty().append(settings.content);

    $modal.css({
        width: settings.width || 'auto', 
        height: settings.height || 'auto'
    })

    method.center();

    $(window).bind('resize.modal', method.center);

    $modal.show();
    $overlay.show();
};

The open method accepts an object of our settings. It can contain three properties: content, width, height. Content can be text, HTML, or an element/jQuery object. We append the data from settings.content to #content, and give #modal a width and height if either was specified in the settings object. Method.center() is called to center the modal in the viewport and is bound to the window’s resize event. This will cause the modal to re-center itself whenever the viewport changes size. I made use of jQuery’s event namespacing so that later, when we unbind the event, we can make sure not to disturb any other resize events that other scripts may have bound to the window. The last thing we do is reveal #modal and #overlay.

Close Method

method.close = function () {
    $modal.hide();
    $overlay.hide();
    $content.empty();
    $(window).unbind('resize.modal');
};

Here we hide the #modal and the #overlay and remove the content. Then we unbind the resize event because there is no need for it when the modal is closed. Outside of the method

Close Button

$close.click(function(e){
    e.preventDefault();
    method.close();
});

The default action of the click event is canceled (the browser from redirecting to the link’s href) by calling e.preventDefault(), then the close method is called.

Examples Calling The Modal

modal.open({content: "Howdy"});

modal.open({content: "<p>Howdy</p>"});

modal.open({content: $("<p>Howdy</p>"), width: "500px", height: "200px"});

// Ajax
$.get('ajax.html', function(data){
    modal.open({content: data});
});

See The Demo

Short URL → http://ednh.ca/ZwF4R7

Utility vs. Beauty

SOURCE: http://52weeksofux.com/post/372655370/utility-vs-beauty

Utility vs. Beauty

A good designer always works to keep the form, function and the aesthetic quality of a design in balance throughout the life of a project. Just because something looks good doesn’t mean its useful. And just because something is useful does not make it beautiful.

More often than we want to admit, we use glitz and glam—or worse, the current popular design trend—to hide the areas where we simply dont have an elegant solution appropriate to the problem at hand. It is too easy to get caught in the trap of focusing on “making it pretty” without giving consideration to the actual purpose of the design.

At the same time, a designer should understand that even the most utilitarian product can benefit from subtle, refined aesthetic treatments and turn what is a dull and boring, yet necessary, task into something enjoyable and engaging.

The most elegant solution will yield a design that is gracefully tempered with restraint and precision—both useful and beautiful.

Short URL → http://ednh.ca/ZwBwOO

The Evernote Hack

SOURCE: http://scriptogr.am/slaven/post/how-not-to-send-password-reset-notification-email

How not to send password reset notification email

So it seems Evernote got hacked and issued password resets for all their users. I wasn’t aware of this fact when I received their email:

evernote

I was almost certain this was a phishing attempt. The URL destinations were on links.evernote.mkt5371.com, which at a first glance looks like Evernote’s URL until you realize a throwaway domain was involved. I laughed it off and continued to read my email until I realized the email’s read a little too well to be a phishing attempt. Sure enough, it was real.

When sending account-specific emails to your customers, always always disable click tracking unless you’re somehow doing it through your own domain.

Short URL → http://ednh.ca/ZwBl68

jQuery Tabs

SOURCE: http://www.jacklmoore.com/notes/jquery-tabs

Tabs are easy to implement and can be built to work with your existing markup. This guide will walk through the process.


Writing the Markup

Lets start by writing our markup as if our visitor doesn’t even have JavaScript enabled. Even if you do not wish to support users without JavaScript, it is still a good pattern to follow to exercise separation of concerns.

<ul class='tabs'>
    <li><a href='#tab1'>Tab 1</a></li>
    <li><a href='#tab2'>Tab 2</a></li>
    <li><a href='#tab3'>Tab 3</a></li>
</ul>
<div id='tab1'>
    <p>Hi, this is the first tab.</p>
</div>
<div id='tab2'>
    <p>This is the 2nd tab.</p>
</div>
<div id='tab3'>
    <p>And this is the 3rd tab.</p>
</div>

I used fragment identifiers (#tab1, #tab2, #tab3) for the href values in the navigation. Each element containing a tab’s content is given an ID that corresponds to a fragment identifier. This way the links are semantic and continue to be functional even if the visitor has JavaScript disabled.

A nice side effect of using only a fragment identifier for the href value is that retrieving the value through jQuery’s .attr() method will give us the exact selector we need to query the document for that tab’s content, ie. $('ul.tabs a').attr('href') will return #tab1.

Writing the jQuery

Rather than describing the code, I am just going to include it with comments that explain each step.

$('ul.tabs').each(function(){
    // For each set of tabs, we want to keep track of
    // which tab is active and it's associated content
    var $active, $content, $links = $(this).find('a');

    // If the location.hash matches one of the links, use that as the active tab.
    // If no match is found, use the first link as the initial active tab.
    $active = $($links.filter('[href="'+location.hash+'"]')[0] || $links[0]);
    $active.addClass('active');
    $content = $($active.attr('href'));

    // Hide the remaining content
    $links.not($active).each(function () {
        $($(this).attr('href')).hide();
    });

    // Bind the click event handler
    $(this).on('click', 'a', function(e){
        // Make the old tab inactive.
        $active.removeClass('active');
        $content.hide();

        // Update the variables with the new link and content
        $active = $(this);
        $content = $($(this).attr('href'));

        // Make the tab active.
        $active.addClass('active');
        $content.show();

        // Prevent the anchor's default click action
        e.preventDefault();
    });
});

See the complete document. I used jQuery’s .on() method for event binding, so be sure to use jQuery 1.7 or higher.

Short URL → http://ednh.ca/ZwBeYd

The Trend Against Skeuomorphic Textures and Effects in User Interface + Design

SOURCE: http://contentandchro.me/archives/459

Dave Wiskus, in a thoughtful piece for Macworld, “Apple and the Future of Design”:

It’s curious how Apple’s hardware and software have taken such
divergent paths. Looking at iOS hardware and software separately,
one might think they were produced by different companies. The
drop-shadows and textures of iOS stand in sharp contrast to the
clean lines and invisible seams of Apple’s hardware. Comparing
major models of either the iPhone or iPad line, Jony Ive’s
industrial design team seems to be on the march, creating devices
that feel ever more like they’re carved from a single block of
magical stone. So why is it that Apple would ship these devices
with software featuring deep shadows and visible stitching?

I’ve been giving this a lot of thought lately, and I think I’ve figured it out.

There is a shift going on, fashion-wise. A turn against faux textures, perhaps epitomized by the rich Corinthian leather of Apple’s Calendar apps for iOS, OS X, and iCloud. Such objections are not entirely new; some of us bristled at Brushed Metal as soon as it (or he, if you will) appeared over a decade ago. But what’s going on today is more than just a rejection of over-the-top skeuomorphic textures; it is instead a very strong push in the opposite direction. The Brushed Metal era criticism was “This is too far”; the trend today is “Let’s go in the opposite direction”.

Wiskus rightly cites Loren Brichter’s Letterpress for being at the leading edge of this trend.1 Letterpress exemplifies not just a rejection of over-the-top textures, but a rejection of faux cosmetic texturing period. For lack of a better term, many are calling this “flat” interface design. That’s not a terrible description of the trend, but it’s not entirely apt. Letterpress, again, is a perfect example. It is indeed, mostly flat, particularly compared to the visual aesthetics of most iOS games and apps. But Letterpress does have Z-axis depth: when you drag a letter tile, it pops up and has a drop shadow under it until you place it. There’s nothing “flat” about that. What Letterpress rejects is not depth, but depth as mere decoration. The visual “raising” of a tile as you play it is a natural visual cue, a way of emphasizing what it is you’re moving.

What occurs to me is that the timing of this trend, and the fact that iOS — and the iPhone in particular — is its leading edge, is not coincidental. It’s because of retina displays.

The whole default iOS look — the textures, the shadows, the subtle (and sometimes unsubtle) 3D effects — is optimized for non-retina displays. It’s makeup to cover up the fact that 163 pixels per inch, though better than anything we had before the original iPhone, is still a crude resolution overall. Retina displays are no longer limited in such ways, and need no phony effects to create interfaces that are beautiful.2

A year ago, Erik Spiekermann tweeted:

If you want good type on Retina displays, stop discussing hinting
et al. Just search for faces that happen to look good. Like the
old days.

I’d go further, and argue that this principle applies to all aspects of designing for retina displays, not just typeface selection. Do what looks good and is true, like we do with print.

But Spiekermann makes an excellent point. Fonts are emblematic of the changes in design enabled by higher resolution displays. In the old days, we used (and needed) screen fonts crafted pixel-by-pixel: Chicago, Geneva, Monaco, etc.. With Mac OS X, we moved to anti-aliasing and mid-range resolution displays, allowing for the practical use of vector (as opposed to pixel) fonts for on-screen use. But what looked best remained fonts that were optimized for the screen, rather than print. Fonts like Lucida Grande, Verdana, and Georgia. Those fonts look best-of-breed on sub-retina resolution color displays, but look cheap when used in high resolution print. (Ikea, I’m looking in your direction.) On retina displays there’s simply no reason not to use any font you want. All fonts render nicely on retina displays.

The trend away from skeuomorphic special effects in UI design is the beginning of the retina-resolution design era. Our designs no longer need to accommodate for crude pixels. Glossy/glassy surfaces, heavy-handed transparency, glaring drop shadows, embossed text, textured material surfaces — these hallmarks of modern UI graphic design style are (almost) never used in good print graphic design. They’re unnecessary in print, and, the higher the quality of the output and more heavy-handed the effect, the sillier such techniques look. They’re the aesthetic equivalent of screen-optimized typefaces like Lucida Grande and Verdana. They work on sub-retina displays because sub-retina displays are so crude. On retina displays, as with high quality print output, these techniques are revealed for what they truly are: an assortment of parlor tricks that fool our eyes into thinking we see something that looks good on a display that is technically incapable of rendering graphic design that truly looks good.

Something like Letterpress doesn’t look bad on sub-retina displays (I play on my iPad Mini frequently), but there’s no wow to it. It’s just there, true to itself, and true to the mediocrity of the display. But on retina displays, simple, pure, true designs like Letterpress have a bracing effect, like the first sip of an ice-cold beverage on a scorching hot day.

If you want to see the future of software UI design, look to the history of print design.


  1. Another leading-edge example: Twitterrific 5 for iOS. But I’m not arguing that Letterpress is the first to head in this direction (Instapaper is another good example, especially on the iPad), just that it’s emblematic of the trend. 
  2. The lack of skeuomorphic effects and almost extreme flatness of the “modern” (née Metro) Windows 8 interface is remarkably forward-thinking. It’s meant to look best on retina-caliber displays, not the sub-retina displays it debuted on (with Windows Phone 7.x) or the typical PC displays of today. That said, I think there’s a sterility to Metro that prevents it from being endearing. It epitomizes “flat” design, but I don’t think it’s great design. 
Short URL → http://ednh.ca/ZwB9nv

CSS3 Ribbon Menu

SOURCE: http://www.jacklmoore.com/notes/css3-ribbon-menu

This uses CSS3 transitions and CSS2 pseudo-elements to create an animated navigation ribbon with minimal markup.


Browser Support

IE8 and IE9 do not support CSS3 transitions, so the hover state will not be animated for those browsers. Otherwise it looks and functions the same, which I think is a very acceptable fallback. IE7 lacks support the :before and :after pseudo-elements, so the ribbon will not have the forked ends or display folds while hovering. The pseudo-elements could be replaced with actual markup IE7 support is needed.

The HTML

<div class='ribbon'>
    <a href='#'><span>Home</span></a>
    <a href='#'><span>About</span></a>
    <a href='#'><span>Services</span></a>
    <a href='#'><span>Contact</span></a>
</div>

The forked ends and folds are created with CSS pseudo-elements, allowing for very clean HTML.

The CSS

Forked ends

.ribbon:after, .ribbon:before {
    margin-top:0.5em;
    content: "";
    float:left;
    border:1.5em solid #fff;
}

.ribbon:after {
    border-right-color:transparent;
}

.ribbon:before {
    border-left-color:transparent;
}

Here the :before and :after pseudo-elements are used to create empty elements with a thick border. The border has one edge set to transparent. This leaves the element looking like it had a triangular section removed, creating the forked look.

Links

.ribbon a:link, .ribbon a:visited { 
    color:#000;
    text-decoration:none;
    float:left;
    height:3.5em;
    overflow:hidden;
}

The links and the forked ends are all floated left so that they fit flush against each other. The hidden overflow hides the folds that are positioned beneath the span elements.

Animated Folds

.ribbon span {
    background:#fff;
    display:inline-block;
    line-height:3em;
    padding:0 1em;
    margin-top:0.5em;
    position:relative;

    -webkit-transition: background-color 0.2s, margin-top 0.2s;  /* Saf3.2+, Chrome */
    -moz-transition: background-color 0.2s, margin-top 0.2s;  /* FF4+ */
    -ms-transition: background-color 0.2s, margin-top 0.2s;  /* IE10 */
    -o-transition: background-color 0.2s, margin-top 0.2s;  /* Opera 10.5+ */
    transition: background-color 0.2s, margin-top 0.2s;
}

.ribbon a:hover span {
    background:#FFD204;
    margin-top:0;
}

.ribbon span:before {
    content: "";
    position:absolute;
    top:3em;
    left:0;
    border-right:0.5em solid #9B8651;
    border-bottom:0.5em solid #fff;
}

.ribbon span:after {
    content: "";
    position:absolute;
    top:3em;
    right:0;
    border-left:0.5em solid #9B8651;
    border-bottom:0.5em solid #fff;
}

A nifty trick for vertically centering text inside of an element is to set the line-height to be the desired height of the element, and remove any vertical padding. This will keep the height fixed and the text centered, even if the font-face, font-size, or font-weight changes.

The top-margin gives the span space to be animated while staying within the anchor element. The span is given a relative position so that it will be the positioning reference for it’s absolutely positioned :before and :after pseudo-elements.

A CSS3 transition animates the transition between the span’s normal state and it’s hover state. Transitions are an experimental feature and are not yet part of the CSS3 specification. As such, each browser has implemented transitions using their respective vendor prefix. The non-prefixed transition property is used under the assumption that, in the future, the property will be a part of the CSS specification.

The :before and :after pseudo-elements create the folded edges when hovering a link. They are absolutely positioned underneath the span element, and will normally be hidden due to the anchor element having a hidden overflow. The pseudo-elements contain only a bottom border and a left or right border. Since the borders are as thick as they are long (0.5 x 0.5) and are adjacent, it creates two triangles for us to apply a color to. The bottom borders will be the same color as the ribbon, and the left or right borders will have the color of the folded edge.

See The Demo

Short URL → http://ednh.ca/ZwATVu

Infinite Scroll

SOURCE: http://eviltrout.com/2013/02/16/infinite-scrolling-that-works.html

Shortly after we began working together on Discourse, Jeff wrote a post about infinite scrolling. At first, I was surprised at how many people claimed to hate sites that used it. However, after reading through many comments about it, I realized that most didn’t hate the scrolling itself, they hated how it broke their browser!

Infinite Scrolling done wrong: Twitter

When I visit Twitter, I am presented with a list of tweets in reverse chronological order. If I scroll down far enough, Twitter will automatically load more tweets so I don’t have to stop reading. Initially, their implementation seems great. I can keep scrolling until I’m done reading.

Twitter’s infinite scrolling has a huge limitation – it only works while you keep your tab open. Try this: open a Twitter window, and scroll down a fair bit. Remember the tweet at the top of your browser window. Restart your browser. If you’re using Chrome, it should re-open all your tabs automatically. Is the tweet you remembered anywhere near the top of your screen? No. Personally, when I do this, I get stuck at the bottom of the first set of tweets Twitter loads.

The engineers at Twitter seem aware of this flaw. Their whole UI is designed around making sure you never lose state in that one tab. All links in tweets open in new tabs when clicked. Your private messages load in a pop-up. Clicking on a user shows a modal with their most recent tweets. If you want to see more of their tweets, it reloads the whole tab with a view of just that user.

After you’ve read a few tweets, what if you want to go back to where you left off in your initial stream? Sorry! Your back button only goes back to the top.

How Discourse deals with these issues

Discourse has infinite scrolling, but it doesn’t have any of the aforementioned issues Twitter does. If you scroll down in a topic and close your browser, you’ll end up right back where you left off. We don’t force links to open in new tabs by default: if users want to do that, they can do it themselves.

How do we do this this? By taking advantage of HTML5’s History API.

Compare the URL

You might have noticed that as you scroll through a topic in Discourse that the URL changes. At first, a URL in a topic looks like this:

http://meta.discourse.org/t/is-it-better-for-discourse-to-use-javascript-or-coffeescript/3153

After you’ve scrolled down a little bit, it will look like this:

http://meta.discourse.org/t/is-it-better-for-discourse-to-use-javascript-or-coffeescript/3153/4

The /4 that is appended to the end refers to the current post number you’re looking at in the topic. In this case, your screen is positioned near the top of the 4th post of topic id 3153.

The replaceState function in the History API allows us to do this. replaceState tells the browser that the URL has changed and the new one should be used if the user hits the back button or reopens a closed tab.

To complete the back button functionality, we then have to support incoming URLs with a post number in them, so we can restore the view the user saw before they left. In Discourse, a URL with a post number doesn’t mean “only show me post x”, it actually means “give me a bunch of posts with x near the top.”

The rest of our infinite scrolling implementation is fairly straightforward. When you reach the bottom of the posts in memory, we trigger a call to load posts after the last post we know. If you scroll upwards to the top, we load the posts before the first post.

The URL is the serialized state of your web application

URLs are meant to represent the location of resources – but I often think people are too focused on resources as documents or videos. Why shouldn’t a URL mean “the posts near post 100”? Twitter’s URL is almost always twitter.com, and their user experience suffers for it.

I won’t claim that Discourse’s approach to infinite scrolling is perfect. There is certainly room for improvement and of course we’d love contributions to our source code. However, I do feel we’ve taken the right approach to infinite scrolling so far by persisting user state in the URL as they scroll.

Short URL → http://ednh.ca/ZwAK4t

Copy and paste programming

SOURCE: http://zacharyvoase.com/2013/02/08/copypasta/

Adherence to DRY (“Don’t Repeat Yourself”) does not necessarily preclude
repetition of code. In the endless struggle to refactor, the entropy we are
trying to reduce
is not in the raw text of our source code; it is in
our business logic, which (in applications with little or poor testing) is
often uncodified. Sometimes, refactoring can hamstring our code, and when done
naïvely it can be a source of technical debt, rather than an antidote thereto.

Many programmers would agree with the following statement:

[I]f you use copy and paste while you’re coding, you’re probably committing a
design error. Instead of copying code, move it into its own routine. Future
modifications will be easier because you will need to modify the code in only
one location. The code will be more reliable because you will have only one
place in which to be sure that the code is correct.

http://www.stevemcconnell.com/ieeesoftware/bp16.htm

I subscribed to this philosophy for the first few years of my real-life
programming career. But recently I’ve questioned it, driven by experience
working with clients whose requirements change (that is, all of them).

Identity

There are often situations where a code path is required to be the same as
another for an explicit reason. Take the Twitter API documentation, for example:

In version 1.1, we’re requiring applications to authenticate all of their
requests with OAuth 1.0a. — Overview: v1.1 of the Twitter API

The whole OAuth protocol setup, and addition of necessary tokens to each
request, would no doubt benefit from being written once and only once. Bugs and
security issues that arise will only need to be fixed in one place, and OAuth
is mandated by the specification for all calls to the API. If you were to
look at the call trace of a process requesting tweets for a given user, and
that of one searching for a hashtag, the OAuth-related logic in common between
the two would be identical in the philosophical sense.

When Refactoring Goes Bad

But just because two processes have to do the same thing, it doesn’t mean the
business cases represented by those code paths are necessarily identical.
Let’s say we’re building a very basic Twitter clone, with both a global feed
displaying all tweets on the site, and individual user timeline pages showing
just tweets from that user. Well, we might start out like this:

  # urls:
urlpatterns = patterns('twitclone.views',
    url(r'^$', 'global_feed'),
    url(r'^(?P<username>\w+)/$', 'user_timeline'),
)

# twitclone.views:
def global_feed(request):
    tweets = Tweet.objects.all()
    return render(request, 'global_feed.html', {'tweets': tweets})

def user_timeline(request, username):
    tweets = Tweet.objects.filter(user__username=username)
    return render(request, 'user_timeline.html', {'tweets': tweets})

This is reasonable enough. The overzealous refactorer would look at code like
this and say: “Configuration over code—I can see similarities between two
different functions!” and replace it with this:

  # urls:
urlpatterns = patterns('twitclone.views',
    url(r'^$', 'tweet_list', kwargs={'template': 'global_feed.html'}),
    url(r'^(?P<user__username>\w+)/$', 'tweet_list', kwargs={'template': 'user_timeline.html'}),
)

# twitclone.views:
def tweet_list(request, **kwargs):
    template = kwargs.pop('template')
    tweets = Tweet.objects.filter(**kwargs)
    return render(request, template, {'tweets': tweets})

Said developer congratulates itself with a pat on the back for a job well done.

Then a product owner says that a profanity filter needs to be added to the
global feed, but not the individual user feed. The freshly-refactored code now
gets a configurable profanity filter switch:

  # urls:
urlpatterns = patterns('twitclone.views',
    url(r'^$', 'tweet_list',
        kwargs={'template': 'global_feed.html', 'filter_profanity': True}),
    url(r'^(?P<user__username>\w+)/$', 'tweet_list',
        kwargs={'template': 'user_timeline.html', 'filter_profanity': False}),
)

# twitclone.views:
import itertools

def tweet_list(request, **kwargs):
    template = kwargs.pop('template')
    filter_profanity = kwargs.pop('filter_profanity')
    tweets = Tweet.objects.filter(**kwargs)
    if filter_profanity:
        tweets = itertools.ifilter(lambda t: not t.is_profane(), tweets)
    return render(request, template, {'tweets': tweets})

Because the profanity filter is implemented in Python, we have to sacrifice the
QuerySet API for the tweets object, and hope that the global_feed.html
template doesn’t rely on any QuerySet methods. But we maintain a single function,
with the options configurable through the URLconf, so we’re still ‘DRY’, right?

Now a requirement is introduced that the user timeline (but not the global
feed) needs to be paginated. Again, our solution grows more legs:

  # urls:
urlpatterns = patterns('twitclone.views',
    url(r'^$', 'tweet_list',
        kwargs={'template': 'global_feed.html', 'filter_profanity': True}),
    url(r'^(?P<user__username>\w+)/$', 'tweet_list',
        kwargs={'template': 'user_timeline.html',
                'filter_profanity': False,
                'tweets_per_page': 20}),
)

# twitclone.views
import itertools

def tweet_list(request, **kwargs):
    template = kwargs.pop('template')
    filter_profanity = kwargs.pop('filter_profanity')
    tweets_per_page = kwargs.pop('tweets_per_page', None)
    if tweets_per_page is not None:
        page = request.GET.get('page', 1)
        offset = (page - 1) * tweets_per_page
        tweets = Tweet.objects.filter(**kwargs)[offset:offset + tweets_per_page]
    else:
        tweets = Tweet.objects.filter(**kwargs)
    if filter_profanity:
        tweets = itertools.ifilter(lambda t: not t.is_profane(), tweets)
    return render(request, template, {'tweets': tweets})

I find this code to be unnecessarily confusing, and I believe most other
developers would agree. I strongly dislike putting configuration of any sort in
the URLconf unless it directly impacts the URLs, and we now have a single
generic function in the place of two specific ones, which has the potential to
grow combinatorially in the future as more options are added.

Furthermore, the business spec talks about a global feed, which is
profanity-filtered but not paginated, and a user timeline, which is paginated
but not profanity-filtered. Our generic code implements both cases, so it would
seem it meets the spec, but it also implements two cases which aren’t called
for by the specification. When another developer (or even the same one, in six
months’ time) reviews this code, it will be unclear whether the cases of
‘filtered and paginated’ and ‘neither filtered nor paginated’ are intended but
untested, or unintended cases which nevertheless must now be supported for
the sake of backwards-compatibility. The code has been parametrized to the
point where you can no longer determine if anything else relies on it behaving
a certain way.

This may come across as a straw man argument, but I’ve worked in many a
situation isomorphic to this—most frequently when I’ve been called in to deal
with a mess after the old developers abandoned ship.

Equality

The reason two specific functions were replaced with an unnecessarily-complex
generic one was because our developer mistook equal code for identical
code. Code paths which happened to be accidentally the same were viewed as
being essentially the same, and thus a Huffman coding was applied to
the source without attention to the business specification—which, remember, may
not even exist in codified form. As a result, when the product owner asked for
what should have been an isolated change to an uncoupled part of the
information architecture, technical debt was created.

A Better Solution


Added after @danso on
Hacker News asked for an example of how the non-refactored way would be better.

Keeping the two functions separate:

  # urls: (still the same)
urlpatterns = patterns('twitclone.views',
    url(r'^$', 'global_feed'),
    url(r'^(?P<username>\w+)/$', 'user_timeline'),
)

# twitclone.views:
def global_feed(request):
    tweets = Tweet.exclude_profane()
    return render(request, 'global_feed.html', {'tweets': tweets})

def user_timeline(request, username):
    tweets = Tweet.objects.filter(user__username=username)
    # You could put the `per_page` argument in your Django settings as
    # `USER_TIMELINE_TWEETS_PER_PAGE`, if you're that way inclined.
    tweets = paginate(tweets, 20, request.GET.get('page', 1))
    return render(request, 'user_timeline.html', {'tweets': tweets})

def paginate(qset, per_page, page_num=1):
    return qset[(page_num - 1) * per_page:page_num * per_page]

# twitclone.models:
class Tweet(models.Model):
    ...
    # Better implemented as a method on the QuerySet, but this'll do.
    # See: https://github.com/zacharyvoase/django-qmethod
    @classmethod
    def exclude_profane(model):
        for tweet in model.objects.iterator():
            if not tweet.is_profane():
                yield tweet

Obviously there’s more going on here than in my straw man example. But the
views are simple, and far more easy to debug, because rather than dealing with
an implicit, dynamic configuration object (in the form of the kwargs
dictionary), there’s a single, simple path through the code.

In terms of testing, you would ideally unit test both the paginate function
and the exclude_profane classmethod, and do high-level behavioral testing
on the global_feed and user_timeline views to ensure they met their
contract, and there would be no ambiguity due to the combinatorial
possibilities introduced by the configuration object.

When Tools Make It Worse

I want to draw attention to a very simple case of where this affected an
application I worked on, because it was enabled and encouraged by Django: you
can specify an ordering property on a model class, and all queries for that
model will have the ordering implicitly applied to them. So let’s say you’re
reading through your code one day, and you notice the following everywhere:

  def view(request):
    tweets = Tweet.objects.filter(...).order_by('-created_at')

The principle of Not Repeating Yourself kicks in, and you work on ‘cleaning up’
your code by removing all those pesky order_by() calls, replacing them with
this:

  class Tweet(models.Model):
    ...
    class Meta:
        ordering = ['-created_at']

Over the next six months, your Twitter clone becomes a big hit, and you’re
storing tens of millions of tweets. However, many of your queries are slow, and
Postgres query stats indicate that applying ordering to every single tweet
query is slowing your site down. So you want to remove that ordering
property—but you can’t, because you’ve now lost track of which methods
necessarily rely on tweets being ordered, which ones don’t care, and which
ones implicitly need the ordering to work, but you can’t detect breakages
because the ordering is added by default. No application’s tests are perfectly
comprehensive (except perhaps SQLite), so you can’t be certain you won’t
break everything by removing the property.

This is how Django’s ordering property invites us to shoot ourselves in the
foot.

The Lesson

I’d like coders to recognize the relationship between the business
specification, information architecture and source code, and use that to inform
smart refactoring decisions, rather than basic gzipping of code.

Short URL → http://ednh.ca/YQ1oUB