January 2014
Native or HTML5/hybrid app? The debate is on and most likely won't end up
anytime soon. Both native and HTML5 technologies evolve at a rapid pace, and
both have their pros and cons. I could chat about native vs HTML but instead
went on to code, to experiment how far it is possible to push HTML5 to
provide a native alike user experience
. The image gallery is a simple sample
app that I came up with. This blog briefly explains the building blocks of the
app.
Note that images are fetched from the public Flickr feed. It's their fault if you see something odd... And I did not yet test Internet Explorer; IE9 is minimum.
The gallery app is a simple but yet "complete" to function as a tutorial HTML5 application. The app has the following features:
The tutorial gallery app demonstrates the following technical building blocks that are essential for modern HTML5 mobile apps:
The app has the following external library dependencies:
There is only 1 HTML, 1 CSS and 1 JS file in the app (plus above external files).
The gallery app is a responsive web app: by using CSS media queries
it
adapts its layout and behaviour according to the device; adapting from mobile
to desktop. It has 2 main layouts
: a small screen layout and a bigger screen
layout.
In the small screen layout, the header only shows the current section name. A horizontally sliding menu drawer can be opened on demand which lists the all 3 sections of the app.
In the large screen layout, the header shows all 3 tabs and the menu drawer is disabled.
In addition to 2 main layouts, the number of image tiles changes from 3 to 8
according to viewport size. Try resizing your browser.
Small screen kicks in under width of 550px, and can be seen on most smart phones in portrait mode. Turning phone into landscape activates large screen. (Atleast in Galaxy S3 and iPhones.)
But responsivity is not only about layout changes; in my opinion it is also
about adapting the behavior
of the app, see touch events next.
In a typical mobile gallery application, it is expected that the images can be swiped left and right with a finger. A gallery app with no swipe lets the user down and just doesn't feel right.
In the tutorial app, finger swipe is properly implemented with the help of
hammer.js library that provides touch gesture events for the app. It would
have been possible to capture events touchstart/touchmove/touchend
on my own,
but hammer.js nicely hides away the nasty browser differences and provides a
higher level gesture abstraction. Recommended for mobile projects. The carousel code was taken from the good Hammer.js demo.
And since there is no pointer, touch browsers do not really have a :hover
pseudostyle. Instead, they simulate :hover by activating it just before the
click. This sudden hover effect can lead to unexpected visualization. Hence it
is usually better to disable the :hover style on touch devices. In the app,
the image title is thus only visible in the :hover style in the desktop.
To detect touch capability, I'm using Modernizer library which attaches the
class "touch"
to html if touch capability is detected. In CSS, I can then
easily enable or disable CSS styles on touch/desktop browser like this:
/* enable transition on desktop */
html:not(.touch) div.my:hover {
-webkit-transform: scale(1.1,1.1);
z-index: 30;
}
/* just opacity on mobile */
html.touch div.my:active {
opacity: 0.7;
}
Swipe gesture is essential in mobile but it has also become common in desktop. Since Hammer.js provides the swipe event also on desktop, I'm not disabling it in the demo either.
Speed is essential in good user experiences; all kinds of delays annoy the
user. The infamous 300ms click delay in mobile surfing is a nuicance that is
just recently getting properly addressed.
Chrome 32 on Android has no click delay
if width=device-width
is specified in meta data.
Internet Explorer has touch-action property. Other vendors follow suit.
You can verify the click delay yourself. (Chrome 32 on Android: no click delay anymore.)
Before the browsers natively support fast clicks, we can get rid of the delay with the help of Javascript. FastClick is a library that enables fast clicks. It is very simple to use - a single line of Javascript will do:
FastClick.attach(document.body);
By using event delegation, this single event handler attached to the document body fastens clicks anywhere in the page. Magic!
In addition to fast clicks, always provide instant feedback for the
clicked element! Lack of feedback is bad. You get really simple feedback by
just writing a pseudo style :active
that is activated on element click.
This guide applies to both mobile and desktop browsers. A typical :active
style is to press down the element a few pixels. I typically use something like
this:
/* enable pressed down effect on click */
a.ico:active, ul.menu a:active {
position: relative;
top: 2px;
}
What is nice is that the :active style is instant also even without the Fastclick library!
CSS3 accelerations have hit mainstream in web design a while ago but they
still have mysticism around them and it's not quite clear how and to what extent
they should be
applied. We are experimenting and learning.
Web developers have understood that animating CSS properties with
Javascript is a no-no
performance wise, but what is not widely known is the
fact that even though you are using CSS transitions or CSS animations,
browsers can currently accelerate only some of the CSS properties fast:
transform and opacity.
Yes, even common properties left
and top
are slower to animate.
Accelerations are just hard to implement in browsers and we'll have to wait for browsers to mature. In addition, the current CSS animation model is suboptimal and improvements are being specified in the web-animations working group. To conclude: CSS3 accelerations are somewhat quirky but kind of essential. Use with care.
To achieve silky smooth acceleration in the app at present day, I'm
accelerating only the fast properties "transform and opacity".
Acceleration
takes place in many places of the app: for the menu drawer, search input, image hover, image reload and the carousel.
Although there is no lack of menu drawer components in the wild, most of them do not have the best performance. To mention a few good and featureful menu drawer components that do acceleration right, check out Snap.js and Trunk.js.
However, a basic properly accelerated horizontal menu drawer is not hard to do. For learning purposes, I implemented one myself in this tutorial app. Here's the basic CSS:
/* menu drawer is initially hidden outside screen */
#menu {
position: fixed;
left: 0; top: 0;
width: 40%; height: 100%;
background: #333;
z-index: 20;
overflow-y: auto;
-webkit-overflow-scroll: touch; /* smooth scrolling on mobile */
-webkit-transition: -webkit-transform 0.2s ease-in;
-webkit-transform: translate3d(-100%,0,0); /* hide initially */
}
/* adding a class slides menu in from left */
#menu.openmenu {
-webkit-transform: translate3d(0,0,0);
}
Since CSS3 accelerations are hard to implement and optimize in browsers, there are differences between browsers and platforms. It is important to test acceleration on each platform. Sometimes the right call is to disable the acceleration.
SPA is a basic well known construction by now: you load a page once and then maintain the state and layout completely in the client. AJAX is used to fetch more resources from servers.
SPA is about speed too. The user experience is a lot better if you can instantly switch pages and not load them from the server. Only SPAs can function as offline/hybrid apps where there might exist no server. SPA's are definately stuff of future.
Most Javascript frameworks implement SPA and page routing. However, if you
want to stay lean and survive without a framework, you can take the simple
approach that I took in this app. Just rely on the old fateful hashchange
event which is supported by every browser. It's simple as this:
// handler for hash change
$(window).on('hashchange', function () {
route_url();
});
// based on url hash, adjust layout and do stuff
function route_url() {
var hash = window.location.hash;
if (!hash) {
// home
images.show();
about.hide();
favorites.hide();
} else if (hash == "#about") {
// about
images.hide();
about.show();
favorites.hide();
} else if (hash == "#favorites") {
// favorites
images.hide();
about.hide();
favorites.show();
}
// and so on...
}
When the URL gets changed either via regular anchors <a
href="#about">About</a>
or javascript window.location.hash = "#about";
the
event handler gets called. Browser back/forward works as expected. Voila.
The tricky bit is to decide what is considered to be a state/hash change and what is not. Think this via browser back; when is an operation to be considered such a large that back should rewind it? For example, while swiping images, does back take you to the previous image or back to the main screen?
Since the advent of retina screens and explosion in the number of different
screen sizes, it has become increasingly difficult to deal with raster images
in many sizes. To rescue comes SVG and icon fonts with scalable vector icons!
(Also flat-design trend drives the use of icon fonts.)
One of the most popular icon font currently is Font Awesome with its 369 icons. This font is used in the app.
To minimize the amount of CSS, I only copied the needed stuff from Font Awesome. A bare minimum of CSS to have a single search icon from Font Awesome is just:
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.0.3');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
.fa {
display: inline-block;
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.fa-search:before {
content: "\f002";
}
The icon is used in HTML like this:
<i class="fa fa-search"></i>
Browser local storage offers a simple way to store data on the client side. In the app I store an array of favorited images (an array of objects containing "title" and "url").
Local storage only accepts key/value pairs of strings. With little help from JSON, a builtin library of browsers, we can store any object:
// store an object into localstorage
localStorage.setItem("imgdata", JSON.stringify(data));
// read an object from localstorage
var data = JSON.parse(localStorage.getItem("imgdata") || "[]");
Since I tried to keep the size of the code at minimum, I have left out a few improvements:
To complete this tutorial, I'll soon wrap up the app with Phonegap and
provide the wrapping in the Android Play store.
In theory it is the same HTML5
app that just runs in an embedded browser control, but the browser engine is most likely different, atleast on Android.
Stay tuned.
Back