Scaling Ionic with CSS rem units


#1

Background

Have you wanted to scale ALL the things in an Ionic app? Yeah, me too. TL;DR: I converted all the px to rem.

I built a sweet-looking Ionic app, but there was one complaint: everything’s too small! That’s simple, I figured, let’s just scale it up. To my dismay, Ionic specifies everything in pixels, and there are a lot of things to tweak (there is no $scale variable to universally change everything, and fixing _variables.scss still misses a few spots). There must be another way.

rem units are nice

If you’ve used em units, you would’ve discovered that they compound, so the size of an element is scaled relative to the font-size of its parent, and so on – this just wouldn’t work for UI consistency. rem units are the answer: everything is scaled relative to the font-size of the top-level <html> element. And browser support is pretty good.

So let’s drag Ionic into the 21st century!

  1. make everything use rem units (16 px for each rem or em)
  2. set font-size on the top-level <html> to a percentage for scale
  3. em, px, etc could still be used as needed for other things

Find and replace

This is a simple find/replace operation on *nix…

cat ionic.css | python -c "import sys,re;[sys.stdout.write(re.sub(r'([\d.]+)\s*px',(lambda m:str(float(m.group(1))/16)+'rem'),line)) for line in sys.stdin]" > ionic.rem.css

To preserve those sharp borders and thin lines, you could spare the 1px values:

cat ionic.css | python -c "import sys,re;[sys.stdout.write(re.sub(r'([\d.]+)\s*px',(lambda m:(str(float(m.group(1))/16)+'rem') if float(m.group(1))>1 else m.group(0)),line)) for line in sys.stdin]" > ionic.rem.css

For v1.0.0-beta.11: ionic.rem.css | ionic.rem.min.css
For v1.0.0-beta.14: ionic.rem.css | ionic.rem.min.css

Have fun!


Css best practices for responsive header
#2

#3

Think of it like this…you adjust one variable, now the whole entire app is thrown off.

We could have used rems, ems, but we chose to stick with pixels to control of certain parts of an app and not have it messed up by changing one variable, screwing up alignments everywhere.

Why throw the world into eternal chaos with that when we could use pixels and maintain more control


Best practices to use `rem` units for responsiveness in ionic
#4

I agree that this is a possibility, and careful consideration of the risks and benefits is required. px's are probably a safe choice for most users: you could duplicate the native appearance down to the pixel, and all browsers support px's.

I believe some people think: you adjust one variable, and now the whole entire app scales to the desired size… including margins, padding, and alignments.

Actually, if you’re worried about rem's for whatever reason (difficulty in debugging, browser support, etc), you could just as easily modify the find/replace operation to keep everything in px.

Did I stop to think if I should? You bet. Would it work for you? Maybe – and it’s easy to try. Does it void your warranty? Very likely. Here be dragons (or raptors, as the case may be).


#5

But I think ionic should support a choice for users like me to use rem, maybe ionic can add a official introduction about how to switch ionic.css from px to rem.


#6

As awesome as it’d be, I’m not sure if it’d be productive for the team to focus on this right now. It’s something they’d have to offer tech support for, and there’s a whole ecosystem of tools around Ionic. I’m just happy to have a reasonable workaround :slight_smile:

And to be clear: you don’t have to use rem units if all you want is to statically scale ALL the things; just scale as desired and keep using px to avoid the risk of eternal chaos. I’m only using rem because I need to rescale dynamically.


#7

@inportb thanks for that little python script – it works for me on the cmd line, but was having trouble getting it work shelling out from gulp.js, so i wrote a gulp task for it if anyone finds it useful. disclaimer: i whipped this up quick-n-dirty, could most definitely be improved and certainly not as terse as the python :smile:

Note: you have to edit /path/to/* to make this work in your build. You could also use the built in nodejs readline package here instead of importing linebyline.

gulp.task('ionic-rem', ['ionic-sass'], function (done) {
  var fs = require('fs'), 
    readline = require('linebyline'),
    read = readline('./path/to/ionic.app.css'),
    file = fs.createWriteStream('./path/to/ionic.app.rem.css');

  file.on('finish', function () { done(); });

  var regex = /([\d.]+)\s*px/g,
    match;

  read.on('line', function (line) {
    match = regex.exec(line);
    while (match !== null) {
      var num = parseFloat(match[1]);
      if (num > 1) {
        // 16 is the base font-size
        line = line.replace(match[0], (num / 16) + 'rem');
      }
      match = regex.exec(line);
    }
    file.write(line + '\n');
  }).on('close', function () {
    file.end();
  });
});

#8

I suppose it’s time for a quick update :smile:

For v1.0.0: ionic.rem.css | ionic.rem.min.css


#9

Hi,
We also like the solution of all CSS having REM units so font-size scaling is easy. We created a gulp taks during our build process that reads the index.html and gets all CSS assets and replaces the px to rem units, like this:

gulp.task('build:rem', ['build:sass'], function() {
    function replaceWith(match, p1, offset, string) {
        return p1 / 16 + 'rem';
    }

    return gulp.src('./www/index.html')
        .pipe(assets({js: false, css: true}))
        .pipe(tap(function(file) {
            file.contents = new Buffer(file.contents.toString().replace(/([\d.]+)\s*px/g, replaceWith));
        }))
        .pipe(gulp.dest('./www'));
});

All works fine for all CSS assets and all is replaced/calculated to REM units. But unfortunately some other Ionic/AngularJS directives calculate some px on the style attribute. So we added a style directive like:

(function() {
    'use strict';
    angular.module('App.core')
	.directive('style', StyleDirective);

    StyleDirective.$inject = ['$timeout'];

    function StyleDirective($timeout) {
	function pxToRem(el, at) {
	    if (el.attr('style')) {
		at.$set('style', el.attr('style').replace(/([\d.]+)\s*px/g, function(match, p1, offset, value) {
		    return p1 / 16 + 'rem';
		}));
	    }
	}

	return {
	    restrict: 'A',
	    compile: function(element, attr) {
	        pxToRem(element, attr);
	    }
	};
    }
})();

Which works great on most style attributes, but some are a real pain in the ass like collection-repeat. What we have done/tried is extended collection-repeat as follows:

(function() {
    'use strict';
    angular.module('App.core')
	.directive('collectionRepeat', CollectionRepeatDirective);

    CollectionRepeatDirective.$inject = ['$timeout'];

    function CollectionRepeatDirective($timeout) {
	function pxToRem(el, at) {
	    if (el.attr('style')) {
	        $timeout(function() {
	            at.$set('style', el.attr('style').replace(/([\d.]+)\s*px/g, function(match, p1, offset, value) {
	                return p1 / 16 + 'rem';
	            }));
	        });
	    }
	}

	return {
	    restrict: 'A',
	    multielement: true,
	    link: {
	        post: function(scope, element, attr) {
	            pxToRem(element, attr);
	        }
	    }
	};
    }
})();

This addition to the collection-repeat directive works great initially, but after a screen resize etc, the new rem units are not recalculated. I can’t seem to find any solution to that issue. @mhartington @inportb any ideas?

I have also added a pen to demonstrate the collection-repeat directive: http://codepen.io/mark-veenstra/pen/reaBoy


#10

Hi, I’m a total newbie on this and I would like to add to our project this gulp task you have provided. Thing is I don’t a certain idea about where and how to do it. Same thing with the Directive. It would be much appreciated if you can help me out a little on this.
Thanks in advance