A while ago I wrote about AppGyver’s SteroidsJS and its interesting take on hybrid app development. If you haven’t read it, basically they add the ability to use native UI elements in PhoneGap applications.
It wasn’t all that easy to create an hybrid app with a quality UI, so I figured I’d share some of the gotchas I ran into. I know it’s a longer article than what I use to post, but really this is what I wish was written when I started working on my app.
Getting The Device To Leave You Alone
You probably know all about using a reset.css file, but for a webview trying to pass for a native view, it’s not enough.
300ms Tap Delay
Mobile browsers implement a delay on tap to check if you tried to double tap or not. Overall it goes: touchstart -> touchend -> wait 300ms -> send click. This results in a terrible experience when navigating your app as it feels slow and/or broken.
For some browsers you can just specify a viewport and it’ll go away:
If this solution doesn’t work, for instance if your code runs on Safari, use FastClick:
Disable Selection/Copy When Long Tapping
You know… this thing:
Fixing this one is pretty easy. For webkit you add this in your CSS:
Disable Double Tap
If you don’t like double tapping and use jQuery, try this stackoverflow answer. Just in case this answer disapears, here’s what it looks like:
I think that you want the “bounce” effect when you scroll out of a webview. It actually feels better for the end user. However in some specific cases you might need to disable this behavior. There are many ways of doing this and I personnaly used this one from stackoverflow:
Allow Text Selection For Input
This is an iOS issue. In some cases you won’t be able to select and edit the text in your inputs as you’d like. You can fix this using user-select
Better Looking Text
I noticed that changing the text-rendering option gets things to look better. The trade-off is performance, but if you don’t have huge chunks of text you’ll be fine.
Remove Glossy Looking Buttons in Safari
How to go from a glossy stylized button (left) to a cleaner, flatter, easier to update button (right)?
You can read more about this here. I also found that changing box sizing could help in the “undesign” process. Smarter people than me explained how it works, you can read about it or try it out like this:
Removing Gray Highlight When Tapping Links
On mobile Safari, tapped links will get quickly highlighted, which doesn’t look very “native” once embeded in a Steroids JS webview. This is especialy true for images. Change this using:
I found that changing the color feels better than removing it.
If you have some elements that are set to scroll when overflowing, you might feel that the scrolling itself doesn’t “feel right”. I suggest looking in this direction:
On Fluid Layouts
Using percentages for size can be a good option to handle different screen sizes. Just know that there is a known issue where Safari rounds up percentages that can cause for weird behaviors when using fluid layouts.
If you build for iOS, you have to take retina into account. Spoiler: it’s a pain.
The Big Idea
Retina Display is a brand name used by Apple for liquid crystal displays that have a pixel density high enough that the human eye is unable to discern individual pixels at a typical viewing distance.
The short version of how to deal with it is pretty straightforward: create images twice as big, call them firstname.lastname@example.org and resize them in CSS.
Don’t Bother With Normal Size
If all your images are in your build and not on a remote server, I would recommend always loading the @2x version. The rule of thumb is that if it looks fine in retina, it will look fine in 1x and embeding the 1x image will only increase your build’s size and complexify the way you manage your assets.
Be careful when using background positions with background sizes as they sometimes don’t play well together.
If you specify a 1px border in your CSS, a 2px border will be displayed on retina displays. Yeah, this is pretty unituitive and a major pain. There are a lot of ways to try to get this to work.
This might not be best for you but, for my use case, I found Stephan Bönnemann’s solution simpler, and I ended adding a DOM element and styling it like this:
This is not ideal, but I wasn’t a fan of the alternatives either. Check them out and make an opinion for yourself:
- Yes We Can Do Fraction Of A Pixel
- CSS, Retina, and Physical Pixels
- CSS: Emulate borders with inset box shadows
Avoid HTTP Calls
I used jQuery. There, I said it.
I knew it would degrade performances, but I benchmarked it a bit versus Vanilla JS and the difference was not really noticable for my use cases, so I kept it because I’m used to the syntax and it gets the job done.
Of course I followed the usual performances tips, not too much DOM manipulation, do not reselect the same element over and over etc.
On a lot of devices CSS animations are slow. Often it’s not slow enough you want to stop using them altogether, but they are still not that good.
My point is that you should not count on perfectly smooth animations when designing your app.
Linear Gradients, QtWebkit and Graphics
During development I did run into major CSS performance issues. I’m not even kidding. This happened when I tried to apply an animation to a div with a linear-gradient.
Sometimes it’s tempting to use webkit’s drawing features, like -webkit-gradient, when it’s not actually necessary - maintaining images and dealing with Photoshop and drawing tools can be a hassle. However, using CSS for those tasks moves that hassle from the designer’s computer to the target’s CPU. Gradients, shadows, and other decorations in CSS should be used only when necessary (e.g. when the shape is dynamic based on the content) - otherwise, static images are always faster. On very low-end platforms, it’s even advised to use static images for some of the text if possible.
The solution here is to prefer using images as background images rather than CSS gradients. This is irritating, but I never found a way around this performance issue.
I’m not going to explain everything about JS performances. This could be a serie of articles in itself. It could even be a book. Actually, it is a book! Go check it out!
To sum it up: reuse objects, do not lock processes, be evented, large arrays will slow everything down.
Working with SteroidsJS
Alright, we talked a lot of general facts that could apply to any hybrid HTML 5 / CSS 3 app, but what about SteroidsJS itself?
Like any new libraries there will be problems, bugs and API changes. It is important to keep up to date, I’d suggest:
For documentation and tips on how to code:
- Use the API documentation which is quite extensive. Be aware that it is very poorly referenced on Google right now so you’ll have to be precise in your searches.
- Reading the tutorials is a must. They are well done and give a good overview of what the technology can do. There are also a lot of good step by step examples.
I personnally didn’t go the AngularJS route because it felt overkill for what I tried to achieve. I also didn’t like the MVC solution proposed by basic steroids scaffolding. In the end what worked for me was quite view-centric approach that looked like this:
- /app/views/my_scope/action.html (a view in a separate webview)
- /app/controllers/my_scope/action.js (all the JS related to a view)
- /app/models/data_persistence.js (everything that is transversal and relates to data)
Overall once I set it up like this, it was easier to figure out what would interact with what.
Basic Customization & Features
If you’re in need of a touch slider, I recommend using Swipe.Js. It’s both simple, effective and very customizable.
The only gotcha is that you won’t be able to put inputs in a slide because text edition (long press on iOS) will not work properly.
Hammer ships with Steroids JS, so I gave it a try. Overall it works fine for some events such as tap or release, but the way it handles clicks seemed buggy. I can’t quite put my finger on it, but in some cases I was better of just using jQuery for click events.
When in doubt, use links and their click event. It will feel better for the end user than using tap or any other event. There is no need to get fancy.
The loader overlay is to prevent any clicking to happened while the loader is present on the screen. The retry_load and text_load are the elements displayed when it succeeds or fails.
As you can see, the CSS is very simple. Again, not claiming to provide the definitive solution here, just some guidance. Feel free to improve on it and add a comment.
I think I managed to get a decent looking settings tab, but I didn’t find anything that would do it out of the box. Here’s a screenshot:
I’m not perfectly happy with the way I had to do it, so I won’t share it even if it does look fine. However you can take a look at what I used for the checkbox here. I found it in a comment on a very interesting article. Based on this I only had to change a couple of things (webkit-appearance, mostly) to get it to work perfectly within my app.
Here is a quick extract of my User model using the localStorage API for persistence.
If I were to complexify my data model, I would consider using SQLite.
Deeper Into SteroidsJS and Cordova
I decided to create my app in both French and English, so I had to deal with internationalization right away. I prefered handling it in the app rather than creating multiple builds or other complicated solutions. The performance problem was a non issue thanks to preloading (as explained further below).
Working with SteroidsJS means working with Cordova/PhoneGap. In this case the globalization API.
… meaning it will use my I18n object (very basic JSON) and replace the content of any HTML element with a data-t attribute. The body ID is set so I can load different images in CSS if needed.
I find it very easy to use every day and it’s decent performance wise if you don’t have too much text displayed.
Preloading is great, it really speeds up everything.
As explained above, I internationalized my whole app. Because of this I’d have a couple of miliseconds blink before the text appears. Preloading views fixed this.
I also wait a bit before actually preloading the views, so the first app load time is reduced. I think it’s better to only preload when you really need it in order to preserve ressources.
Learn more about it on AppGyver’s website.
Note that this not fully functional in production on my app because of an issue in Steroids JS where loading the navigation bar in a certain way disrupts preloading. Because of this you will see some minor blinking in some internal setting screens. It will be fixed soon :)
Getting Out Of The Background
You might want to do something when the user reopens the app after putting it in the background. Here’s how to do it:
Communicate Between Views
Using the postMessage API to communicate between views is the way to go.
Right now there is an open issue when adding alerts in the event listener as discussed [on the forum)[http://forums.appgyver.com/#!/steroids:sometimes-alerts-block-th).
Check Out The Result!
Of course you don’t have to take my word for it, give my application a try !
It’s called Liff and is available on the appstore. The goal is to give a relevant way to get insights on ones day and we put in extra efforts in order to create a pleasant experience with a solid design.
Clearly creating an hybrid app is easier than ever, but there is still a lot of things to keep in mind when doing so. Projects like PhoneGap push the ball in the right direction and I can’t wait to see what kind of apps we’ll be creating in a few years!