iPhone App Layout with CSS3 box-flex on mobile webkit (jqtouch)

Introduction

CSS3 provides a much needed improvement on the box model: flex-box: it fills a void left by CSS2 by providing a better way to do elastic layout, in which some part of the HTML expands to fill leftover space.

The best tutorial of flex-box can be found at Mozilla's site: http://hacks.mozilla.org/2010/04/the-css-3-flexible-box-model/.

>

Behaviors are similar in Mozilla and WebKit implementations. Although we are online concerned with mobile WebKit here, it’s a good start. It should be relatively easy to port for your own purpose.

iPhone App Layout

To support navbar (available to BeeDesk jQTouch's forks), landscape layout, and iPad split screen, reworking jQTouch's theme using flex-box makes sense. It doesn't hurt that it works better on a desktop screen.

Before the change we used javascript to calculate height for the flex <div> triggered by orientation change and page events. It is bug-prone, not always reliable, and noticeably slower than CSS.

Layout

The layout generally contains three parts: toolbar (fixed), tabbar (fixed), and content (flexible).

screenshot of iphone

Achieving backward compatibility was tricky, and it supports content wrapper, which is used to support scrolling (not available in the official fork).

That jQTouch doesn't have a class name for the flex part also adds some work.

To get boxflex starts, three markups need to be added:

  1. on container: { display: -webkit-flex-box; }
  2. on each flex children of the container: { display: -webkit-flex-box; }
  3. non-zero value on each flex children of the container: { -webkit-box-flex: 10; }

For our vertical layout, we add these lines to the container:

{
    -webkit-box-sizing: border-box;
    -webkit-box-orient: vertical;
    -webkit-box-pack: start;
    -webkit-box-align: stretch;
}

Using { display: block; } as children of { display: -webkit-flex-box }

With WebKit the default layout of the "children of the {display: webkit-flex-box;}" behaves differently than "children of {display: block;}". Content of the children does not span the width of the flex box. To compensate we need to add these lines to the children:

{
    display: block;
    top:0;
    left
:0;
    right:0;
}

The { left:0; right:0; } markup causes the content to span the entire width.

On the other hand, using { width: 100%; } would not work, because width does not compensate for the margin, and causes content to overflow to the right.

Markup { width: ?px; } won't work either, because it is not flexible.

No name for content

Because jqt didn't give a name for the flex part, we have to resolve to a series of :not() to avoid affecting the toolbar, navbar, and tabbar. This is is how it looks:

#jqt > * > div:not(.contentwrap):not(.navibar):not(.toolbar):not(.bar):not(.info)

Support content wrapper

The rest of the complexity comes from the need to support a variety of content. For example:

<body id="jqt">
  <div id="var1"><!-- simple case -->
     <div class="toolbar"></div>
     <div class="">Content</div>
     <div class="navibar"></div>
  </div>

  <div id="var2"><!-- simple case with .info -->
     <div class="toolbar"></div>
     <div class="info">Warning: this is a warning to warn you that you're warned.</div>
     <div class="">Content</div>
     <div class="navibar"></div>
  </div>

  <div id="var3"><!-- scroll pane case -->
     <div class="toolbar"></div>
     <div class="contentwrap">
       <div>Content</div>
     </div>
     <div class="navibar"></div>
  </div>

  <div id="var4"><!-- modified scroll pane case -->
     <div class="toolbar"></div>
     <div class="contentwrap">
       <div class="afloat_header"></div>
       <!-- must specify .content if it is not the first element -->
       <div class="content">Content</div>
     </div>
     <div class="navibar"></div>
  </div>
</body>