Jamie Gaskins

Ruby/Rails developer, coffee addict

Don't Change Perceived Browser Functionality

Published Jul 30, 2016

When developing front-end apps, you have the ability to override some basic browser functionality, and this is a bit of a double-edged sword. Some functionality is required to be overridden in front-end apps, like link clicks and form submissions. If you don't override these, your front-end app starts working less like an app and more like a document-based web page. Overriding these is okay as long as they still appear to work just like they do on web pages — links take me to a different place in the app and forms store my input somewhere, whether locally or sent to the server.

However, even though you can override some functionality, there is a list of basic functionality the browser provides with which you should not interfere. Users expect these features to work:

  • Copy/paste
  • Right click
  • Command/Control-click to open a new tab
  • Shift-click (opens a new window in Chrome, adds to Reading List in Safari)
  • Searching within a page
  • Back button
  • Refresh
  • Cmd-# to navigate directly to a specific tab

Copy/Paste

Copy/paste in web pages is useful for so many things. The thing I personally use it for most is pasting a password stored in 1Password, but some websites disable copy/paste in password fields "for security purposes". This is a misguided attempt to keep their users safe. There's just no way I'm going to type in a 50-character password (assuming their app even allows passwords that long).

If there is ever a reason to disable copy/paste, I haven't come across it. Maybe detecting a copy/paste is reasonable sometimes, but preventing it entirely is never what a user wants.

Right Click

Right-clicking anywhere on a web page traditionally brings up a context menu for the element clicked. On a Mac, Ctrl-clicking does the same thing (a throwback to when Macs only had one mouse button). This context menu might have different information based on the type of element you're right-clicking.

For example, a link's context menu might provide options to open its target in a new tab or window, download the link target, etc. A video element's context menu might let you open it in full-screen mode, show/hide controls, etc.

Overriding this is extremely situational. Apps like Google Docs get a pass because their target audience expects it to work like Microsoft Office, but think hard about whether taking the default right click from the user is actually improving their experience.

Command/Control-click to open a new tab

This one frustrates me the most and is frequently unintentional on the developer's part. When you command-click a link, it fires a click event on that element, so if a click handler calls event.preventDefault() indiscriminately, it keeps its users from opening the link's target in a new tab unless they right-click and select "Open in New Tab" (another reason not to override right click).

Don't feel bad if you've broken this before by mistake. It's very common. Even a giant like Twitter still breaks it in their desktop web app. To fix it, you can put something like this at the top of your click handlers:

function handleClick(event) {
  var hasModifiers = (
    event.metaKey ||
    event.shiftKey ||
    event.altKey ||
    event.ctrlKey
  );

  // Only handle unmodified left click. Leave everything else alone.
  if(hasModifiers || event.button !== 1) return;

  event.preventDefault(); // Only prevent AFTER confirming you should handle this.
};

Notice we check the value of event.button in there. A right click doesn't trigger a normal click event, so we don't need to worry about it, so why do we need to check that?

Turns out, clicking the mouse button (middle click) does trigger a click event with event.button === 2. You don't want to handle that the same way as a left click.

Shift-click

This is closely related to Command/Control-click. In many browsers, Shift-click opens a link's target in a new window. In Safari, it adds the link to the user's Reading List.

Web apps like Gmail force all Shift-clicked links to open in a new window, so if I'm using Safari and I find a great link in Ruby Weekly, I can't Shift-click it to add to my Reading List. I have to right-click and select "Add to Reading List" instead (as you may have guessed, the positioning of the right-click example above these was deliberate).

Searching within a page

There are two violations of this behavior that I've seen. The first is overriding Cmd-F to activate your app's own search. This is a misguided attempt to improve searching for content, but if I'm using Cmd-F, I probably want to use the search feature provided by the browser. Gitter used to override Cmd-F this way, but they've since removed it.

Offering your own search with Cmd-F will indeed let users know it's there, but it's frustrating way to find out because at that moment it's probably not what they want. Use Shift-Cmd-F for your search if you like, but leave the basic one alone. If your search bar stays on your page, you can label it with Search (Shift-Cmd-F) to let them know how to get to it quickly with the keyboard. Many of your power users will likely appreciate that.

The other violation I've seen is moving DOM nodes around while the user scrolls. This is usually done for performance reasons, but it's annoying when you know a particular word or phrase appears on the page but it's not coming up in a search. The Facebook timeline and the Twitter's mobile web timeline do this.

If you want to save memory, only render images that appear within the viewport (and remove them when they are scrolled out of the viewport), but please leave text there.

Alternatively, in the case of Facebook and Twitter, reducing the number of DOM nodes per item in the timeline would go a long way to reducing memory usage. The Twitter desktop web timeline is unbearably slow sometimes, but uses nearly 50,000 DOM elements (not counting text nodes) to render 400 tweets:

Photo of JavaScript console with evidence of tweet count vs DOM element count

A 400-element list isn't lightweight, but you don't need list items to average 125 elements inside them.

Back Button

If I click an element and it swaps out a large portion of the page content, that appears as a navigation to me, regardless of whether it triggered a browser-level navigation. When I click the back button (for the sake of brevity, I'm just going to refer to all similar functionality to be filed under "clicking the back button"), I expect to be "taken back" to that previous content.

For example, if I'm viewing a list of messages and I click one of them, it might replace the list of messages with the contents of that one message's thread. As the user, I don't care if this is "technically" a navigation. It looks like navigation from the perspective of someone who doesn't know or care about the internal implementation. If I then hit the back button, I expect to see the list of messages again.

The easiest way to handle this is to use a web framework that provides a router. Ember, Clearwater, React (with React Router), and even Backbone provide this functionality.

Refresh

If an app is stalled for some reason (waiting on incoming data that's taking too long to load, a JavaScript exception broke my click handlers, etc), the most common thing for users to do (besides closing the tab) is refreshing the page. If I'm not in the same spot I was in before the refresh (for example, I have to drill back down through several layers of content to get back there), that's a frustrating user experience.

Losing some internal app state is understandable, but I should at least be in the same spot.

Handling this case is also important for the mobile web. Mobile browsers frequently dump pages to save memory. When you go back to them, they have to reload the page from scratch. If you're shown the app's entry point again, this is probably going to be frustrating. Instead, when the page reloads, I should be right where I was when I left off.

Using some sort of routing to store where you are in the app is essential to providing this kind of user experience.

Tab Navigation via Command-

In most browsers these days, Cmd-# (where # is a number from 1-9) selects that specific tab (1-9). Some WYSIWYG editors override this by setting Cmd-1 through Cmd-6 to correspond to headings h1 through h6.

This is problematic because you may need to swap between a few different tabs to get all the information you need to write up a document. If your app modifies your document instead of swapping tabs, that's gets old real quick.

Conclusion

The app that users expect to be using is the browser; your app just runs inside it. Be respectful of that context. If you do override functionality, ensure that the functionality you're providing in its place feels similar — don't override functionality with entirely different functionality.

The best way to avoid breaking functionality by accident is to use an app framework that handles the minutiae for you. Clearwater, Ember.js, and React Router for React.js are frameworks/libraries that I've personally used that handle all the necessary link- and routing-related functionality for you. You'll never need to worry about breaking modified link clicks, the back button, or page refreshes in these ways.

The rest of the browser features listed above (copy/paste, right-click, page searching, and Cmd-#) are things you have to go out of your way to break. Push back against any product manager or client that decides that they want to override any of those features. Their job is to make decisions that improve a product. Overriding those goes against that goal; assure them of that.

TwitterGithubRss