Skip to content

Instantly share code, notes, and snippets.

@david-mark
Last active January 5, 2017 13:31
Show Gist options
  • Save david-mark/7e7c2148dac0ae49bb525bc35dd1a5b3 to your computer and use it in GitHub Desktop.
Save david-mark/7e7c2148dac0ae49bb525bc35dd1a5b3 to your computer and use it in GitHub Desktop.
How to Detect the Device Type?

#How to Detect the Device Type?

The question of how to detect the device type running the given browser comes up a lot these days. It's virtually the same question as how to detect the browser (or browser version). It's often incorrectly "solved" in the same way, using indirect inferences based on browser sniffing.

##What is the Problem?

As with most such problems in browser scripting, it's best to look at what is to be inferred from such information. We know from experience that such information will often be based on coincidence and will expire at some point in the future, so it is best not to rely on it. An issue at the turn of the century was how to determine whether the browser was IE or not. But why was that information thought to be necessary? For example, at the time IE used attachEvent instead of the standard addEventListener to add event listeners.

##Indirect Inferences

A typical browser sniffing script of the time would attempt to detect IE by parsing the UA string and looking for patterns that matched those known to be present in versions of IE. Of course, that was getting off on the wrong foot as it wasn't an IE issue; other environments used the MSHTML rendering engine and featured completely different UA strings. So developers would compile and periodically update a list of UA string patterns that were known to match MSHTML-based browsers. It wasn't too long before it became clear that such an approach was not viable; the maintenance on such scripts alone was enough to cause developers to look for a better way.

##Finding a Direct Inference

As opposed to trying to identify the browser to make an indirect inference, we simply needed to find whether the browser at hand featured attachEvent or addEventListener. A direct inference could be made based on this more specific criteria and all would be well. Well almost, as we had to endure the age of the indirect object inference before proper feature detection and cross-browser scripting became a thing. In other words, developers took to detecting attachEvent and then setting an isIE flag that was used to make a number of other indirect inferences.

##Why the Device Type?

Getting back to the original issue, why would we try to determine if the given device is a phone, tablet or PC? For years the lines between the three types have blurred and hybrids (e.g. phablets, 2-in-1 laptops, etc.) have made such a determination all but useless. Taking phone detection as an example, will our script attempt to make phone calls? More likely it is simply a matter of making an indirect inference about the size of the screen.

##Screen Size Issues

More often than not, the device type detection is attempted to enable the script to vary the user interface based on ranges of screen sizes. This was done far more often before media queries were supported by the majority of browsers, but even then it was generally a bad idea and other methods were available. For some issues, more direct inferences could be made by measuring the viewport and, even at the turn of the century, some browsers featured handheld style sheets that worked much the same way as media queries do today. It was certainly an imperfect world and still is today, but that doesn't mean we ever need to resort to browser sniffing (or UA sniffing in particular).

##Media Queries

Today media queries allow us to easily present different layouts based on the device screen size. They also allow UI components to be rearranged in numerous ways to make applications appear and behave differently. They are definitely not confined to collapsing columns and turning navigation into popup menus (two common use cases). Creative use of media queries alone can create the illusion that each range of screen sizes has its own application tailored to fit their specific needs.

##Different Components for Different Screen Sizes

Let's look at the worst case scenario, which often comes up when using monolithic UI frameworks. We have three distinct widgets that have been designed for three ranges of screen size: phone, tablet and PC. It's a bad design from the start and such frameworks virtually always resort to browser sniffing to try to make such schemes "work", but there are much better ways to go about it (assuming such designs can't be simply avoided in the first place).

###One Solution

Consider this HTML:

<div class="device-specific some-widget">...</div>
<div class="device-specific some-other-widget">...</div>
<div class="device-specific another-widget">...</div>

...And this CSS:

.device-specific {
    display: none;
}

The DIV elements may be empty or may contain the basic markup required by each widget. Now the trick is to determine which one to bring to life with our script.

Create media queries based on device height and/or width that override the display style (e.g. change it to block). Will leave that as a trivial exercise.

What is the required information to make a direct inference? We need to know which of the three DIV elements has a computed display style that is not none. This might sound like a good job for the getComputedStyle method, but there is an even simpler solution as the other two elements will have offsetHeight and offsetWidth property values of 0.

After the document is loaded (or ready), the best choice for the widget to present can be found like this:

// Reference all widget containers

var els = document.getElementsByClassName('device-specific');

// Filter all but the visible widget container

var shownEls = Array.prototype.filter.call(els, function(el) { return el.offsetHeight || el.offsetWidth });

// Dimension ranges should be mutually exclusive, so should be only one

if (shownEls.length > 1) {
   throw new Error('More than one widget container shown. Check for overlapping media queries!');
}

var theShownElement = shownEls[0];

// If none passed the eye test...

if (!shownElement) {

    // Use the first one

    theShownElement = els[0];
    
    // Make it visible
    
    theShownElement.classList.remove('device-specific');
}

Then simply create the appropriate widget based on the class name of the visible container element. The media queries should cover mutually exclusive ranges, so there should be only one. If not, it would likely indicate a mistake in one or more of the media queries and the code should either throw an exception or explicity hide the rest.

Note that if the attempt to find the best widget fails for any reason (e.g. the user has zoomed so far out that all three are less than 1x1), the first one is used.

###Conclusion

That's one direct way to solve this specific problem; but, as mentioned, it's best to avoid such designs in the first place.

##Advice for Frameworks

But what if we are marketing a general-purpose framework and want to "help" developers using it to achieve the same thing? This is yet another example of why such frameworks are generally a bad idea and tend to pale next to context-specific solutions in terms of performance, interoperability and durability. Still, there will always be a market for prepackaged, drop-in solutions. The more general they are, the bigger the market, so it should be no surprise that the most popular frameworks usually make the most mistakes in this area.

It's not impossible to create generalized solutions for problems such as these, but effective solutions won't follow the well-traveled path of exposing isPhone, isTablet, etc. flags. They also won't fit as neatly into marketable boxes. For example, they will be best implemented using a combination of scripts and style sheets (as opposed to a mashup of scripts alone), thus doubling the number of tasks for developers. :)

It's the same problem that marketers of frameworks have faced well into this century, but those that stuck with UA sniffing to provide such "simple" interfaces have invariably ended up in history's bit bucket (e.g. Dojo, Ext JS, etc.) In other words, any popularity achieved through such ill-advised designs is likely to be difficult to maintain, just like the code. ;)

@david-mark
Copy link
Author

david-mark commented Jan 3, 2017

Note that with the recent deprecation of device-width/height in Media Queries 4 draft, there's additional motivation to avoid the worst case scenario of creating completely different widgets for different screen sizes.

Better to design widgets that can "morph" to adapt their presentation (and behavior in some cases) to different viewport sizes. Behavior changes are done by hiding and showing different bits of the UI. For example, when the viewport width drops below a certain threshold, a strip of tabs may change to a pop-up menu with a previously hidden button used to toggle its visibility.

Those features were always a bit of a pain to work with due to inconsistent implementations (e.g. do they refer to portrait mode only?), but they just needed time to achieve widespread standardized implementation. Deprecating them seems like a huge mistake.

I blame Google for pushing the idea that media queries are required for responsive layouts on the desktop, where they should clearly be a last resort. Responsive (AKA liquid) layouts were around long before media queries and the techniques used to implement them should still be our first choice today. Always felt that layouts should be made as responsive as possible and that media queries should be relegated to rearrangements required by viewports that can't resize. But then, I don't work for Google, so what do I know? :)

But realize that they aren't going anywhere any time soon and still make for a better solution than anything involving browser sniffing. By the time they do go away, we should have learned to avoid designing apps that require alternate widgets for different screen sizes. Will likely be years before the offending draft is approved and by then the whole landscape (no pun intended) will likely have changed drastically anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment