These tips are things I've encountered while working on this book and while writing other Cordova/PhoneGap apps. There's no real common theme here, but it might help save you a few minutes should you encounter the same problem in the future.
Mobile browsers are not like desktop browsers. This may sound obvious at first glance, but mobile browsers have to deal with large, squishy fingers, whereas most desktops still use mice or trackpads which have much greater accuracy. This means that it is easier to tap on the wrong item when using a mobile browser. It can also be more difficult to know when a tap has been registered.
To accommodate this profound difference between mobile and desktop browsers, mobile browsers offer affordances to users so that they know that, yes, their tap was registered, and that this is what they tapped. This is usually done by highlighting the element with a specific color or outlining the element in a color, or both.
While it is important for native apps to have affordances, the browser doesn't typically render the affordance in the way the user would like. Some of this can be changed (the color can be modified, for example), but more often than not, the affordance rendered by the browser does not match the designer's vision.
There is a fix, however, to disable these browser-driven affordances. Remember, though, that you should provide your own affordances when applicable if you turn these browser affordances off globally.
These rules can disable both the highlight and outline affordances. Remember to add the browser prefixes in your code as follows:
tap-highlight-color: rgba(0,0,0,0); tap-highlight-color: transparent; outline: none;
To apply these rules globally, you can attach them to the body
element in your CSS. For most native apps, this is what you'll generally want.
Note
The transparent rule is for some Android devices. See http://phonegap-tips.com/articles/essential-phonegap-css-webkit-tap-highlight-color.html.
Now that we've essentially removed all the affordances provided by the browser, we need to add some back for the sake of user experience. Generally, these include changing the text or background color of buttons and list items.
As long as the item in question has an event handler registered that uses a touchstart
event, you can create CSS rules using the active
pseudo-element as follows:
.ui-list-item:active { background-color: blue; }
Native interfaces generally don't allow users to include user-interface widgets inside selections. By default, however, the mobile browser used by Cordova/PhoneGap will permit this, resulting in a decidedly odd experience.
To turn it off globally, attach the following to the body
element:
user-select: none;
For elements that need to support selection (such as actual content or text inputs), replace none
with text
.
By default, the mobile browser renders text fairly poorly; it doesn't support ligatures, kerning, or the like. However, you can enable this feature to use better typography in the app (for fonts that support it). Again, attach to the body element as follows:
text-rendering: optimizeLegibility;
That said, this shouldn't be used on Android devices, since some versions don't calculate the text boundaries correctly. For Android, turn it back off as follows:
body.android { text-rendering: auto !important; }
On a desktop, one would typically use overflow: scroll
or overflow: auto
to make certain elements scrollable. This works to a degree on most mobile devices, but the experience is decidedly non-native on iOS. For example, there may not be inertial scrolling or rubber-banding effects.
To enable this on iOS 5 and above, add the following to any scrollable container:
-webkit-overflow-scrolling: touch;
Keep in mind that native scrolling of this form works only on iOS 5 and higher. It doesn't hurt to use the property on Android devices (or any WebKit browser) since if the browser doesn't know what to do with it, it will ignore the property.
Tip
If Android is not using native scrolling, try this:
For Android platforms, adding a z-index to the scrollable area may trigger native-feeling scrolling if Android wasn't applying it before. It's possible anything that creates a new stacking context may also force the issue (so position:absolute
may also work).
If you can't use native scrolling or need to support a platform where you need to offer a fallback, use a scrolling library.
There are several available, but I can't recommend iScroll enough, it's not perfect (no scrolling library ever is), but it's the closest I've seen and supports a wide variety of platforms. You can get it at https://github.com/cubiq/iscroll.
When you add a 3D transform to an element, it will use the GPU to composite the element instead of the software renderer. This can improve performance during animations and scrolling, but it does come at a cost: memory. This works best on items that aren't more than four times (or so) the size of the screen—more than this may cause visual artifacts (as the GPU must use tiling to draw the element).
There are a couple of methods you can use. The first one is as follows:
transform: translateZ(0);
Alternatively, you can use:
transform: translate3d(0,0,0);
You may notice that elements flicker when using the GPU. If this is the case, hide the back face:
backface-visibility: hidden;
Even though 3D transforms don't automatically default to rendering elements with perspective like in a 3D game, perspective itself is still used. This means that when animating elements over each other, it's possible that you may see elements being rendered in the wrong order. Your first instinct will be to add z-index
, which is fine for non-GPU elements, but it won't do anything when using the GPU. Instead, use a specific z-value to ensure that the element renders closer to the user than those behind it. For example, consider the following code snippet:
z-index:9; transform: translateZ(9px);
The z-index
here has no specific save function to make it clear that this element is intended to render above other elements. The actual work, however, is accomplished by the translateZ(9px)
portion—this renders the element 9px
closer to the user's view than other elements. Since there's no perspective, there's no apparent change in size as elements move along the z( )axis, so the user doesn't notice, but the browser rendering engine surely does.
It's possible that you may encounter what seem to be spurious events occurring immediately after a tap (or other gesture) that invokes a navigation animation. There are a couple ways around this:
Use a "click prevention" DIV: This
DIV
is put up prior to the animation once the gesture is detected and is removed from theDIV
after the navigation is complete. ThisDIV
is told to fill the entire screen and is transparent, so the user never sees it, but the DOM won't pass events below it. If you inspect YASMF's Navigation Controller, you'll notice that it throws up a click preventionDIV
during a view navigation animation for this very reason.Prevent the default browser action: This works as well, if not better, than the prior, since it doesn't involve manipulating the DOM.
Hammer.js
makes this really easy to do—one can enable it by passingprevent_default:true
as an option, as shown in the following code:Hammer(contentsElement, { prevent_default:true }).on("tap", function (e) {} _;
Although most modern devices come with powerful GPUs, this hasn't always been the case. Even with GPUs, it's still pretty easy to bog down the compositor so that performance lags and stalls.
There's no one-size-fits-all fix to handle this, though. Here are some general pointers, however:
Avoid using gradients: The gradients take time to compute.
Avoid scaling images: The interpolation of an image is painfully slow.
Avoid shadows: These require underlying elements to be composited first, and then the shadow to be composited on top. This takes time. Additionally, any blur in the shadow has to be calculated.
Avoid partial transparency: Like shadows, this requires composition. It's a little faster, since the shadow blur doesn't have to be calculated.
Avoid rounded corners: Creating rounded corners requires computations.
Avoid a lot of elements in the DOM: The more the elements in the DOM that have events or need to be positioned by the browser, the slower things run due to the required calculations. This means that you shouldn't ever attach an event handler to a DOM element that doesn't need it (even if it does something cool), and you should limit the amount of positioning calculations the browser has to go through.
Although one would hope that things have progressed to the point where the file type could be inferred from the file itself, we're still sadly stuck with the fact that file extensions matter. Therefore, don't create a video file with a .video
extension, or an audio file with a .audio
extension. This will fail, simply because the extension is important in determining how to parse the file.
Instead, use the proper file extensions. The extensions we used in this book are as follows:
Platform |
Audio |
Video |
---|---|---|
Android |
|
|
iOS |
|
|