Tuesday 15 October 2013

Zooming responsive css

Until the browser gods bestow on us wide ranging support for viewport relative dimensions , I've been using the following technique to resize my CSS styling for different mobile screen widths. It consists of three simple steps:
  1. Use ems for all dimensions - no pixel sizes - with the BODY font-size set to 10px
  2. Design to a 320px wide layout
  3. Use my 'responsive zooming' media queries to scale up and down to common screen sizes
So for examples, say you had a really specific font-size on your main header so that it always filled the full width, the CSS might look like this:

body {
    font-size: 10px;
    width:100%
}
h1 {
    font-size:1.36em;
}

This might display perfectly at 320px wide, but as soon as you change the screen size, your header won't fill the full width. So, at the end of our CSS we do this:

@media all and (max-width: 319px) {BODY {font-size:7.5px;}}
@media all and (min-width: 360px) {BODY {font-size:11.25px;}}
@media all and (min-width: 400px) {BODY {font-size:12.5px;}}
@media all and (min-width: 480px) {BODY {font-size:15px;}}
@media all and (min-width: 540px) {BODY {font-size:16.875px;}}
@media all and (min-width: 640px) {BODY {font-size:20px;}}
@media all and (min-width: 720px) {BODY {font-size:22.5px;}}
@media all and (min-width: 768px) {BODY {font-size:24px;}}
@media all and (min-width: 800px) {BODY {font-size:25px;}}

This takes into account a bunch of common screen sizes, and scales the body font-size accordingly, so on different sized screens, all of the content in the body is scaled perfectly.

Final tip: if you want to edit the rules above for another screen size, just take your target screen width and divide by 32, and use that as the font-size. Simple!

Tuesday 18 June 2013

Removing the iPhone navigation bar in 100% height layouts

Just a quicky - I've just finished a site that uses

#container {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    overflow: hidden;
}

to lock the site to an app-like, 100% x 100% layout (thanks Steven Sanderson). But when it came to the usual trick of

if(navigator.userAgent.toLowerCase().indexOf("iphone") !==-1) {
    window.scrollTo(0,1);
}

to remove the Safari navigation bar nothing happens! This is because, as the layout is only 100% tall, there's nowhere to scroll to. So a quick fix is to do this:

if(navigator.userAgent.toLowerCase().indexOf("iphone") !==-1) {
    $('body').height(screen.availHeight-44);
    window.scrollTo(0,1);
}

This (jQuery assumed) pushes the body out to fill the available height (minus 44px for the navigation bar). Then the scrollTo works perfectly.

Voila, app like layout.

Wednesday 22 May 2013

Adding text strokes with jquery and css text shadows

Due to the fact that css text stroke is not widely supported I decided to roll own using jquery, CSS text shadows, and a little bit of trigonometry.

It's easy enough to do, because CSS allows you to apply multiple text shadows to an object. If you set the blur on these to zero, and place them evenly around your text, you get a stroke.

A single CSS text-shadow rule looks like

.classname {
    text-shadow: 1px 1px 2px #ff0000;
}

giving a 1px offset in each direction, a 2px blur and a nice red shadow (urgh).

Multiple shadows can simply be joined by commas, like so

.classname {
    text-shadow: 1px 1px 2px #ff0000, 0px 1px 3px #00ff00, 1px 3px 2px #ffff00;
}

(Don't try this at home kids, it'll look disgusting).

The trick here is working out where to put the stroke in relation to the text. I want a smooth stroke with rounded corners and caps (I might extend this to square caps, but not right now!), so I'm going to place my strokes in a circular pattern. Imagine a full stop/period, then add a stroke above, right and down a bit, right and down a bit, reach 3 o'clock, down and left a bit, down and left a bit, 6 o'clock etc.

But how to calculate exactly where to put these shadows?

This is easy with the parametric equation of the circle, which is:

x = r * cos(t)
y = r * sin(t)

Don't worry about the specifics of this equation, just trust me that X and Y are the number of pixels up or down put our shadow and r is the width of the shadow. The variable t just steps around our circle, from 0 to 2π (360°) and gives us the x and y position.

So, to the code.

First, this is a jQuery plugin, so we start with

$.fn.textStroke = function(r, colour) {

r is the radius in pixels and colour is a hex code, e.g. '#ff0000'

var rules = [];
var steps = 24;

I'm creating an array to add each CSS text-shadow rule to as we go around the circle, and I'm using 24 steps - this is purely subjective. 24 steps was necessary for a smooth stroke on a big header, but 12 will probably do for smaller text. Note I've gone for multiples of 4 so that each quadrant has the same number of text-shadows.

for (var t=0;t<=(2*Math.PI);t+=(2*Math.PI)/steps){

Here i'm looping through a full circle in radians (2π radians to 360°, kids!), in the number of steps set above.

var x = r*Math.cos(t);
var y = r*Math.sin(t);

Here we get our x and y values. And we're done for this step! Except we're not, because sometimes things break. Because we'll convert x and y to strings in a minute, we hit a problem when we get really really tiny but not quite zero numbers at certain points around the circle - numbers like 5.2485234e-013 (a very, very small number) aren't understood by CSS, so...

x = (Math.abs(x) < 1e-6) ? '0' : x.toString();
y = (Math.abs(y) < 1e-6) ? '0' : y.toString();

...if the number is less than one millionth, we set it to zero. And convert to strings here because I like messy code. Finally, we make a rule from these values and add it to the rules array...

rules.push( x + "px " + y + "px 0px " + colour );

and continue the loop...

Finally, we join the rules we've created and apply using the css function in jQuery.

this.css('textShadow',rules.join());

Note I haven't mentioned vendor-specific prefixes here - that's because, thankfully, jQuery generates them automatically.

And that's it!

$.fn.textStroke = function(r, colour) {
 
var rules = [];
var steps = 24;

for (var t=0;t<=(2*Math.PI);t+=(2*Math.PI)/steps){
 
var x = r*Math.cos(t);
var y = r*Math.sin(t);

x = (Math.abs(x) < 1e-6) ? '0' : x.toString();
y = (Math.abs(y) < 1e-6) ? '0' : y.toString();

rules.push( x + "px " + y + "px 0px " + colour );

}

this.css('textShadow',rules.join());

};

So, all we have to do to add a lovely text stroke to any element is to do

$('#thingtobestroked').textStroke(5, '#ffffff')

and we're done.

There's a jsfiddle demo at http://jsfiddle.net/KjyYV/ , and you can check out my crappy code at my GitHub repo. It's currently a mess and doesn't follow the jQuery boilerplate, because I'm lazy and hacked this together in ten minutes, but it'll improve!