Scaling Our App Down to Fit Tablet Screens Safely

Recently I'm looking into fitting our PC site to tablets – mainly iPads, but also other Android tablets that are popular in some of our regions. Our web app for PC was made with the assumption of 1200px minimum width, and therefore would result in an annoying overflow on most tablets, of which effective pixel width normally would be narrower, especially in portrait mode. Essentially, this is responsive design, or so I thought. Initially, I didn't think this could still be an issue at this time (year 2020), especially since most articles I found on this topic are 10 years old. I thought whatever I need should already have a definite answer. Interesting enough, this is not the case.

In this article, we will start from why naïve responsive implementation couldn't work in our situation, the "one-liner" solution we found, and the nuances we fixed to make this work for production.

Background: Are tablets part of the mobile family or a closer relative to desktop?

If you open Shopee on PC v.s. on a mobile phone browser, you will see two different layouts. Now, if you inspect the site just a little bit more, you will notice that the two layouts are not a result of responsive design – they are two completely different sites. We don't have one set of code serving both PC and mobile responsively, we have two sets of code serving PC or mobile, respectively.

Here's our PC site:

Shopee PC

And here's our mobile site:

Shopee mobile

For us, the decision was made 5+ years back, and the key rational was not just the difference in screen size, but that the interactions on mobile screens are very different from PC. Our mobile web app looks nearly the same as our native app, whereas our PC assumes a much broader viewport and many different gestures in mind.

But now, with the popularity of tablets, the boundaries are a bit vague. Tablets are similar with PC in aspect ratio, but have mobile devices' browsers and the gestures that offers much more intimate browsing experience that I'd personally like to opt for. This brings me to a recent project that I'm quite excited about, to find a way to cater for our tablet users. But, with strings attached, better not very costly.

Making the desktop site work on tablets

Our PC site assumes minimum browser width at 1200px. If the viewport is smaller, we introduce a horizontal overflow but will render the whole content at its assumed 1200px size. This could be a bad assumption, but that ship has sailed.

Now that we want this to fit tablets, which range from 600px-1440px mostly. Such wide range is because users may hold the tablets in portrait or landscape mode. And if we look at the wider axis only, the majority falls somewhere between 800px-1100px. Therefore, opening our site on tablets would result in a horizontal overflow as users scroll the app. To me it simply felt... unprofessional.

A naïve solution was to remove our min-width: 1200px and start writing responsive code. This has one advantage, it's easier to explain, or don't even need to. Just imagine designers' rolled eyes as if saying "can't you write some media queries?" But the actual situation is much more difficult. Our app now serves 14(?) countries and regions, countless features and lots of toggled mechanisms that aren't possibly known by just a few developers. It simply wasn't feasible for us to dig the white-box approach to fix for every page, every component, every feature and its every logical branches to give them responsive layouts. It would be a project management nightmare to coordinate every single team to modify their code for responsive layout. The project will never end and the merge request will never land.

Other than layout, there are some other aspects we need to take care of such as gestures, differences in users' navigation and interaction patterns, etc. (I wrote a separate post for this Intermediate States for PC, Tablets and Keyboard Navigation).

A look back on mobile viewports

An inspiration we took that eventually became our solution was this question. In the old days where most sites were designed for desktop, how did the old Nokia Symbian 40's render that site in that tiny screen, scaled down and fit everything in its initial screen?

I even tried my high school's website again, it still works. The site obviously is designed to be quite wide, in the old days likely some 960px. But it is crunched down to the mini phone screen without cropping, just that texts are not readable. This is because mobile browsers let users see bigger sites as if with a telescope, where one end is the whole site - 960px, the other end is users' eyes, some 140px (?) in the old days. So crunch 960px -> 140px, and scale everything down.

Turns out, this is relevant to mobile browsers being born with two viewports. They're like telescopes.

iPads and other tablets are close to desktop in terms of CSS pixels as well as aspect ratios, but they're mobile devices in the end. They have two viewports too.

One is the visual viewport, its size roughly corresponds to the actual screen size.

The other is the layout viewport. This is the "actual" viewport that we make our layout for. For example, CSS media queries will use this viewport's size. With mobile Safari, if you don't set any viewport configurations, this is by default 980px in width.

When the user zooms in or out, a scale factor is in play that relates the two viewports, like a telescope.

Normally, on a mobile site, you might see this tag:

<meta name="viewport" content="width=device-width, initial-scale=1" />

or, assuming the device is 320px in width, it implies:

<meta name="viewport" content="width=320, initial-scale=1" />

What this means is the "other" end of the telescope will render 320px viewport width. So CSS media queries like @media (max-screen: 321px) will hit this and render its specified layout accordingly. But if we didn't and we write app as if we didn't know about mobile devices, the whole site will be scaled down proportionally to 320px.

For more technical details on the viewport tales, make sure to check out A Tale of Two Viewports and other articles in the series. The author does an amazing job explaining it, and he also built an emulator for it, similar to which Jake has a demo in a video as well.

There is another great article explaining the viewport – the ages old Safari Web Content Guide - Configuring the Viewport.

Possible to use this to fit our PC app to tablets 🤔

I think we found quite an ingenius solution to our problem -- by specifying a layout viewport size that covers the assumed browser width for our existing PC app.

So, our PC app assumes 1200px. If we say:

<meta name="viewport" content="width=1200, initial-scale=1" />

mobile browsers will render a layout viewport precisely at 1200px width. So our entire app will fit here.

That's precisely what we did, but we used 1240px instead of 1200px, give it some spacing on both sides.

Even when users change orientation when browsing, the layout viewport will still be 1240px, and the whole app is automatically scaled according to the orientation.

In the end, it isn't one-liner

In a sense our approach is black-box -- to scale everything proportionally down such that nothing breaks (too badly). But here are some further issues we needed to take note:

  • some components look way too small in some circumstances, we want to allow for a white-list approach to render different layout subject to the width in the visual viewport, in other term the physical width according to how the users are holding the device
  • disable text resizing
  • handle orientation change

White-list approach for responsive layout

Now we've rendered the whole app in a layout viewport of 1240px, we've pretty much disabled responsive layout because all media queries will now receive 1240px fixed. To allow our developers still be able to conditionally render a different layout when they know that the device is physically very small, we need to provide an alternative way to get the device' visual viewport information.

Something along the lines of:

const { screenWidth /** and some util enums */ } = useLayoutInfo();

We provide a hook that returns the actual screen width of the device such that developers can use to render different layout when necessary.

In this case, we need to subscribe to orientationchange to update the physical dimension. Other than this, the logic here is fairly straightforward:

function useResponsiveLayoutInfo<ComputedLayoutInfo>({
  mapLayoutTypeToUsedLayoutValue, // optionally take in a map fn to directly return layout info transformed
}: UseResponsiveLayoutOptions<ComputedLayoutInfo> = defaultUseResponsiveLayoutOptions): ResponsiveLayoutInfo &
  ComputedLayoutInfo {
  // two contexts are relevant here:
  // viewport observer context to subscribe to the singleton
  // responsive context for individual pages to register responsiveness
  const ViewportObserverContextValue = React.useContext(
    ViewportObserverContext
  );
  if (ViewportObserverContextValue === null) {
    throw new Error(`ViewportObserver context not available`);
  }
  const { initialViewportWidth, subscribeToViewportWidthChanges } =
    ViewportObserverContextValue;

  const [screenWidth, setScreenWidth] =
    React.useState < number > initialViewportWidth;
  const updateViewportWidth = React.useCallback(
    newViewportSize => {
      setScreenWidth(newViewportSize);
    },
    [setScreenWidth]
  );
  React.useEffect(
    () =>
      subscribeToViewportWidthChanges(
        // may extend to selector
        () => true,
        updateViewportWidth
      ),
    [subscribeToViewportWidthChanges, updateViewportWidth]
  );

  const layoutType = React.useMemo(() => {
    if (screenWidth <= BREAKPOINTS.PORTRAIT) return ResponsiveLayoutType.Tiny;
    if (screenWidth <= BREAKPOINTS.LANDSCAPE)
      return ResponsiveLayoutType.Portrait;
    if (screenWidth <= BREAKPOINTS.FULLWIDTH)
      return ResponsiveLayoutType.Landscape;
    return ResponsiveLayoutType.Fullwidth;
  }, [screenWidth]);

  return {
    ...mapLayoutTypeToUsedLayoutValue(layoutType),
    layoutType,
    screenWidth,
  };
}

Font sizes and text resizing

Some browsers uses an experimental technology text-size-adjust. The rationale is that when site developer didn't ship responsive layout and the site is accidentally viewed from a small mobile screen, the texts are too small. The browser cares and so it automatically sizes up the text.

In our case, even though we make use of the mobile viewport resizing, we specifically don't want text resizing because it may introduce unwanted text overflow. From our designers' side, this was justified by the assumed behavior that users can easily zoom in by pinching on tablets when texts are too small.

To disable, add this at appropriate node:

body {
  text-size-adjust: none;
}

Handle orientation change

Mobile browsers' viewport sizes don't change mostly, unless when changing orientation, which introduces a rather drastic change on the layout.

Some popular width / height pairs are: 600px / 960px (some popular Android tablet), 768px / 1024px (older and smaller iPads), 1024px / 1366px (newer and bigger iPads).

We may be required to provide different layout for such orientation change. Turns out, this is given with the same hook introduced in the previous section. The performance of this at our app's level of complexity is not great, but acceptable.

Other notes

Fun fact: "standard" mobile viewport tag (that are often misused)

Most sites have this:

<meta name="viewport" content="width=device-width, initial-scale=1" />

This should happen when the site implements responsive layout. Otherwise, it will be hugely misleading if the site has about 1000px content in width but is instructing the mobile devices to render a very narrow viewport. In fact, it is such a common confusion that mobile Safari developers decided to help us by changing its default behavior, overwriting the viewport meta specified by the developer if it finds out that the meta content is written wrongly:

Note: Explaining 3.b, WebKit often sees pages that define width=device-width but then explicitly layout content at very large widths, often greater than 1000px. These sites are usually designed for a large screen, and have added a viewport tag in the hope it makes it a mobile-friendly design. Unfortunately this is not the case. If the browser respected a misleading viewport rule such as this, the user would only see the top left corner of the content—clearly a bad experience. Instead WebKit looks for this scenario and adjusts the zoom factor appropriately. Conceptually, this behaviour is the same as the browser loading the page, then the user pinch zooming out far enough to see all the content, which means the page is no longer at a scale of 1.

How others do it

Amazon: same drill, but they didn't put the meta tag at all, leaving it to the default 960px.

Other solutions I've seen include using mobile phone site directly for tablets. Let me not name names, it does have the gestures like swiping, but it just looks weird. The texts and images are way too large because they are scaled up. Even though I know the pains behind it, from an external perspective it still feels they don't care enough.

The best I've seen is etsy. Responsive web from the smallest to the biggest viewports, all in 1 set. Give it a try and resize your browsers 1000 times like us. I'm in all respect after digging through our app for tablets.

Links

Guides and specs

You should not have to read too much to understand the viewport. Here are the essential guides and articles that I find clear and helpful.

The viewport meta tag is under-specced. The following two CSS specs contain information about the tag and the CSS fields, although the CSS fields are bound for removal.

Quick notes & tips

More about mobile viewports

Other content