Now that Piictu finally launched and is out of beta, I want to write a bit about one of my favorite things I worked on as the front-end web developer there, which is our implementation of an infinite scrolling page improved by the use of the HTML5 History API, the problem it tried to solve, and the solution we arrived at.
A bit of background first. In case you haven’t tried it (and you totally should,) Piictu is an iPhone social photo app that revolves around the concept of “photo streams”, or threads of photos by different users on the same subject. For example, you can take a photo of a sandwich, start a stream titled “eating a sandwich”, and watch as your friends and followers reply with their own photos of their own sandwiches, or whatever they’re having for lunch. Check it out, there are some incredibly creative games and memes going on over there. It’s a lot of fun.
Since these streams could conceivably have hundreds of photos, and we wanted an uninterrupted photo-viewing experience, we immediately decided to implement each photo stream as an infinitely-scrolling page, instead of using regular pagination. However, this concept of streams of thematically-related photos defined one of the main requirements for the design: we never wanted to take a photo out of its context, which meant that when people shared them, we couldn’t have traditional permalinks with just the one photo. The challenge was to figure out the best way to allow a user to share any photo without taking it out of the context of its stream.
The problem with infinite scroll
I’m not a big fan of many sites’ implementations of infinite/endless scroll, and given a choice, I turn it off. Most times it just drives me nuts. For example, in most sites that use it, if my Internet connection goes out or there’s a server error or my browser crashes, I’m forced to start back at the top, which I find infuriating if I’m really deep down the page. Another problem is that I usually can’t bookmark my position, so if I leave and come back later, I’ll have to start over. So, in addition to the photo-sharing-on-an-infinite-page problem, I also wanted to tackle these issues, for a better user experience.
The old Ajax way
My first idea when tackling this problem was a traditional solution using Ajax and fragment identifiers, so we could start the stream of photos at an arbitrary point defined by storing the ID of the desired photo in a URL hash (e.g.
<title> tag, description meta tags, and other images on the page. On a traditional permalink page like Instagram’s, it’s easy, just set the Open Graph tags with the metadata of the one photo in the page. But on a page with a multitude of Ajax-loaded photos, without the server knowing which photo is being requested (remember, the server never gets the hash fragment,) how do you set these tags? If Facebook can’t see that information for the photo being shared, it wouldn’t know what to display in the News Feed, undermining what we set out to accomplish in the first place, which was to make it easier for users to share the photos. As an up-and-coming startup looking to get traffic and exposure, this was a real deal-breaker, so I quickly scrapped this solution.
A better solution using the HTML5 History API
Instead, I decided to use the HTML5 History API. Instead of getting the ID of the photo currently in the viewport and using it to change the fragment identifier, I update the URL in the address bar by calling the
replaceState() method. The basic idea is this:
- Wait for the
scrollevent to fire. (Note that since the
scrollevent can fire a lot, for performance reasons it’s best to run any code attached to this event after a small delay, using a
setInterval, as per John Resig’s recommendation.)
- When the page has scrolled, get the ID of the top-most photo in the viewport. For this I used the Viewport Selectors jQuery plugin, which adds a handy
:in-viewportselector. I also embedded the ID of each photo as a
- If the browser supports the History API, use
replaceState()to add the photo ID to the base URL of the stream page, or remove it, if it’s the first photo in the stream (i.e. if we scroll back to the top.) The reason I chose to use
replaceState()(which updates the current browser history entry) instead of
pushState()(which adds a new history entry) was because I didn’t want to have to click “back” a bunch of times and go back through every photo just to get to the previous page.
You can see this in action by going to any stream on Piictu, such as this Hipstamatic stream I started a few months ago [NB: Piictu has been defunct for many years now, so evidently all these links are busted, but you can see the same effect on my photoblog, which uses a similar implementation.] As you scroll up and down, you’ll notice that the ID of the photo in the viewport is appended to the URL of the stream page, and when you return to the top, it’s restored to the original URL (the
base_url variable in the source code, which is also saved in a
data-* attribute in the markup for easy retrieval.)
So what happens on the server when we request a stream? If we request a plain stream URL, such as
/streams/123, the server returns the first few photos normally, starting with the first photo in the stream. If we request a URL that contains a photo ID, like
<head> of the page, so when you post
/streams/123/photo/345 on Facebook or Google+, they’ll show the correct thumbnail and caption for that photo. It solves our goal for the photos, which was to help users to easily share them: regardless of whether they use the sharing buttons next to each photo, or simply grab the URL from the address bar and paste it in an instant message or their favorite social network, it’ll just work.
It also alleviates some of my pet peeves with infinite scrolling. Since the URL updates automatically as you move up and down the page, you can easily bookmark your position, which is particularly handy on very long streams; and if for whatever reason you’re forced to reload the page or your browser crashes, you’ll start where you left off, avoiding the frustration of having to start over (assuming your browser reopens your tabs after a crash.)
What about Internet Explorer?
As always, the biggest issue with using any modern technology is Internet Explorer, since in this case it doesn’t support the History API in versions 9 and below. I briefly worked on a workaround for IE, using the ol’ hash fragments as a fallback. In the end we simply decided not to support IE, mainly because between January and May, Internet Explorer accounted for only 2.42% of the visits to our signup and teaser page, so the added effort and maintenance it would require seemed counterproductive. In addition, our implementation degrades gracefully in IE. The URL may not change as the user scrolls, but everything else works properly and sharing photos is still possible, using the Twitter and Facebook buttons. In other words, it simply behaves as a traditional implementation of infinite scroll. Finally, it’s a temporary situation, as Internet Explorer 10 will support the History API, and it shouldn’t require any further work. I tested it in the Windows 8 Developer Preview, which includes a preview version of Internet Explorer 10, and it worked perfectly.
I really believe that using the HTML5 History API to augment infinite scrolling offers a superior user experience by alleviating some of the annoyances caused by traditional approaches, such as the lack of bookmarking and sharing. I expect this technique will be used more once Internet Explorer supports the History API, but if you’re willing to live without IE support for a bit (or use one of the many polyfills available,) it’s definitely worth giving it a try now.
Let me know what you think about this; I look forward to your comments and questions, even though I still haven’t gotten around to adding comments to this blog. In the meantime, feel free to tweet @gesteves or send me an email.