Start with Mobile First website.

Before I wrote about advantages of mobile first websites. Now I decided to write a tutorial to show the full picture of creating a mobile first website and how to make it fully featured on the desktop. So, Moff follows these main purposes:

  • How to create light, fast and designed for mobile devices website first
  • And how to make this website fully featured on the desktop, like even it was not designed for mobile devices

Let’s start from creating Mobile first website. For example, we have to create simple ad website for our company. The website will be created with Twitter Bootstrap and Moff frameworks.

Markup

First, we should markup our website base HTML structure.

<!DOCTYPE html>
<html lang="en">
<head>
	<title>Mobile First website.</title>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<header>
	<nav class="navbar navbar-default" role="navigation">
		<div class="container">
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
						data-target="#navbar-collapse">
					<span class="sr-only">Toggle navigation</span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
				</button>
			</div>
			<div class="collapse navbar-collapse" id="navbar-collapse">
				<ul class="nav navbar-nav">
					<li><a href="/#about-us" title="Comapny - About us">About Us</a></li>
					<li><a href="/#our-achievements" title="Company - Our achievements">Our achievements</a></li>
					<li><a href="/#contact-us" title="Company - Contact us">Contact us</a></li>
				</ul>
			</div>
		</div>
	</nav>
</header>
<main>
	<div id="about-us" class="container">
		<section>
			<h1>Company Name</h1>
			<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
				The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using
				'Content here, content here', making it look like readable English. Many desktop publishing packages and web page
				editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy.
				Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
			</p>
		</section>
		<a name="our-achievements"></a>
		<section>
			<h3>Our achievements</h3>
			<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
				The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using
				'Content here, content here', making it look like readable English.</p>
		</section>
		<a name="contact-us"></a>
		<section>
			<h3>Our contacts</h3>
			<address>
				<strong>Company, Inc.</strong><br>
				453 Some Street, Suite 410<br>
				San Francisco, CA 59874<br>
				<abbr title="Phone">P:</abbr> (257) 145-9578
			</address>
		</section>
	</div>
</main>
<footer>
	<div class="container">
		<div class="row">
			<div class="col-xs-12 col-sm-6">
				<ul class="list-inline">
					<li><a href="#about-us">About us</a></li>
					<li><a href="#our-achievements">Our achievements</a></li>
					<li><a href="#contact-us">Contact us</a></li>
				</ul>
			</div>
			<div class="col-xs-12 col-sm-6">Copyright © 2016 Company Inc.</div>
		</div>
	</div>
</footer>
</body>
</html>

So, our website consists of three sections:

  • About us
  • Our achievements
  • Contact us

Include Bootstrap

Moff supports modulated Bootstrap which allows to include only needed parts of Bootstrap framework, thus we noticeably decrease loaded styles size.

At first, we should determine what Bootstrap components we used in our markup and include only needed parts. Usually, you always should include core.css as the main file. On this page, we have used Bootstrap grid system, typography and navigation component. Add these style into head tag.

<head>
	<title>Mobile First website.</title>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/main/core.css">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/main/grid.css">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/main/typography.css">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/components/navs.css">
</head>

And these scrips into body right before it close. We include jQuery, because it is dependency of the Bootstrap.

<script src="bower_components/jquery/dist/jquery.min-1468949512196.js"></script>
<script src="bower_components/moff/dist/bootstrap/javascripts/collapse.js"></script>
</body>

And now ours styles and script size is 121.34 KB. If we use Bootstrap by default it is 225.6 KB, this way we reduce our style and script size about 50%.

Enhancements

Now we can add more info into our website and optimize it for mobile devices.

Install Moff

First of all, we have to install and include Moff framework.

To use it with Bower you have to create bower.json file in the root of the project or run

$ bower init

Then run

$ bower install moff --save

In other cases, you can download it from framework website . In the tutorial we use bower. After installation, need to include Moff into the page.

<script src="bower_components/jquery/dist/jquery.min-1468949512196.js"></script>
<script src="bower_components/moff/dist/bootstrap/javascripts/collapse.js"></script>
<script src="bower_components/moff/dist/moff.js"></script>
</body>

Now we are ready to apply our enhancements.

Gallery for "Our achievements"

Under the achievements paragraph, we have to add a list of images in our gallery.

<section>
	<h3>Our achievements</h3>
	<p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.
		The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using
		'Content here, content here', making it look like readable English.
	</p>
	<div class="achieve">
		<div class="achieve_button __prev"></div>
		<ul class="achieve_container">
			<li class="achieve_container_slide __active"><img src="images/preview1.jpg"></li>
			<li class="achieve_container_slide"><img data-src="images/preview2.jpg"></li>
			<li class="achieve_container_slide"><img data-src="images/preview3.jpg"></li>
			<li class="achieve_container_slide"><img data-src="images/preview4.jpg"></li>
		</ul>
		<div class="achieve_button __next"></div>
	</div>
</section>

Pay attention, we set src only for the first image in the list, because we don’t need to load all images at once. The best way is to load them by lazy loading on user request (when he clicks "next" button).

Next step we should markup this gallery.

.achieve {
	position: relative;
}

.achieve_button {
	position: absolute;
	width: 20px;
	height: 20px;
	top: 50%;
	margin-top: -10px;
	text-align: center;
	font-size: 18px;
	line-height: 20px;
	background: rgba(225, 225, 225, 0.4);
	cursor: pointer;
}

.achieve_button:hover {
	background: #fff;
}

.achieve_button.__prev {
	left: 0;
}

.achieve_button.__prev::after {
	display: block;
	content: '<';
	font-weight: bold;
}

.achieve_button.__next {
	right: 0;
}

.achieve_button.__next::after {
	display: block;
	content: '>';
	font-weight: bold;
}

.achieve_container {
	list-style: none;
	padding: 0;
}

.achieve_container_slide {
	width: 100%;
	display: none;
}

.achieve_container_slide.__active {
	display: block;
}

And include it in the head tag.

<head>
	<title>Mobile First website.</title>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/main/core.css">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/main/grid.css">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/main/typography.css">
	<link rel="stylesheet" href="bower_components/moff/dist/bootstrap/components/navs.css">
	<link rel="stylesheet" href="modules/gallery/gallery.css">
</head>

And the second step we have to add dynamic for our gallery.

Now we will add Moff module to describe gallery class.

Moff.modules.create('Gallery', function() {
	var _module = this;

	var _activeSlideIndex = 0;

	var _slides;

	this.scopeSelector = '.achieve';

	function handleEvents() {
		_module.find('.achieve_button.__prev')[0].addEventListener('click', showPrev, false);
		_module.find('.achieve_button.__next')[0].addEventListener('click', showNext, false);
	}

	function getSlides() {
		_slides = _module.find('.achieve_container_slide');
	}

	function inactiveAll() {
		Moff.each(_slides, function() {
			var className = this.className.replace(/(^| )__active( |$)/, ' ');

			this.className = className.trim();
		});
	}

	function activate() {
		var slide = _slides[_activeSlideIndex];
		var className = slide.className + ' __active';

		slide.className = className;
	}

	function showSlide() {
		var image = _slides[_activeSlideIndex].querySelector('img');
		var dataSrc = image.getAttribute('data-src');

		if (dataSrc) {
			image.src = dataSrc;
			image.removeAttribute('data-src');
		}
	}

	function showPrev() {
		if (_activeSlideIndex) {
			_activeSlideIndex--;

			inactiveAll();
			activate();
			showSlide();
		}
	}

	function showNext() {
		if (_activeSlideIndex < _slides.length - 1) {
			_activeSlideIndex++;

			inactiveAll();
			activate();
			showSlide();
		}
	}

	this.init = function() {
		handleEvents();
		getSlides();
	}
});

In this class, we have created logic to handle next and previous buttons on the gallery.

And include module into the page and initialize it.

<script src="bower_components/jquery/dist/jquery.min-1468949512196.js"></script>
<script src="bower_components/moff/dist/bootstrap/javascripts/collapse.js"></script>
<script src="bower_components/moff/dist/moff.js"></script>
<script src="modules/gallery/gallery.js"></script>
<script>
	Moff.modules.initClass('Gallery');
</script>
</body>

Now gallery works fine. When user clicks next button image data-src will be replaced by src and the new image will be loaded. This way we optimize page size to 249 KB loading additional images by user request.

Google map in "Contact us"

In the mobile version, we show company address which is the main information in Contact Us section. And also, we can show it on the map like an enhancement. And there is no need to load the map when user enters the page. We can show the map when user click "See us on the map" link. For this, we have to create Moff module and add a link to the page.

<section>
	<h3>Our contacts</h3>
	<address>
		<strong>Company, Inc.</strong><br>
		453 Some Street, Suite 410<br>
		San Francisco, CA 59874<br>
		<abbr title="Phone">P:</abbr> (257) 145-9578
	</address>
	<div id="map">
		<a href="#" data-load-module="map">See us on the map</a>
	</div>
</section>

Now on link click Moff will run AMD module specified in the data-load-module attribute. Moff AMD module is a simple object which describes dependencies and main files of the module. Also, it supports callbacks.

<script>
	Moff.amd.register({
		id: 'map',
		depend: {
			js: [
				'http://maps.google.com/maps/api/js',
				'modules/map/jquery.gomap-1.3.3.min.js'
			]
		},
		file: {
			js: ['modules/map/map.js'],
			css: ['modules/map/map.css']
		}
	});
</script>

To show google map we used goMap jQuery plugin. Module name specified in data-load-module attribute should be the equal to id property of the registered module. And finally, on link click Moff load dependencies of the module, then its files. After all, files are loaded you will see google map on the page.

Here is content of the module files.

// modules/map/map.js

$('#map').goMap({
	address: 'New York, NY, USA',
	maptype: 'ROADMAP'
});
/* modules/map/map.css */

#map {
	width: 100%;
	height: 200px;
}

.gomapMarker {
	display: block;
	width: 150px;
	height: auto;
}

Improve render time

To improve render time of our website we should minimize and concatenate CSS and JS files. Also, we have to apply Critical-path CSS.

To minify and concatenation files we will use Gulp.

Install Gulp

To install gulp, run command

$ npm install –g gulp

And install Gulp locally

$ npm install gulp --save

Be sure you have package.json file in your project root folder.

Next step we have to create gulpfile.js file in the project root folder.

Minify CSS and concatenate

To minify our CSS we have to add Gulp task to our gulpfile.js

var gulp = require('gulp');
var cleanCSS = require('gulp-clean-css');
var concat = require('gulp-concat');

// Concatenate and minify CSS
gulp.task('css', function() {
	return gulp.src([
		'modules/**/*.css',
		'styles/style.css',
		'bower_components/moff/dist/bootstrap/main/core.css',
		'bower_components/moff/dist/bootstrap/main/grid.css',
		'bower_components/moff/dist/bootstrap/main/typography.css',
		'bower_components/moff/dist/bootstrap/components/navs.css'
	])
	.pipe(concat('index.css'))
	.pipe(cleanCSS())
	.pipe(gulp.dest('styles'))
});

And run

$ gulp css

After command execution, you will see the index.css file in styles folder. And now we can replace all CSS links to index.css

<head>
	<title>Mobile First website.</title>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="styles/index.css">
</head>

Apply Critical CSS

Critical CSS is a way to which allows the browser to render website viewport as fast as possible.

Now add new task into gulpfile.js

var critical = require('critical');

// Generate & Inline Critical-path CSS
gulp.task('critical', function () {
	return critical.generate({
		base: './',
		src: 'index.html',
		minify: true,
		width: 1920,
		height: 980,
		inline: true,
		dest: 'index.html'
      });
});

And run

$ gulp critical

After command execution look at index.html and you will see that

<link rel="stylesheet" href="styles/index.css">

Has been replace to

<style type="text/css">
	.container:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after{clear:both}#map{width:100%;height:200px}.achieve{position:relative}.achieve_button{position:absolute;width:20px;height:20px;top:50%;margin-top:-10px;text-align:center;font-size:18px;line-height:20px;background:rgba(225,225,225,.4)}body,button{margin:0}.navbar-toggle,a{background-color:transparent}.achieve_button.__prev{left:0}.achieve_button.__prev::after{display:block;content:'<';font-weight:700}.achieve_button.__next{right:0}.achieve_button.__next::after{display:block;content:'>';font-weight:700}.achieve_container{list-style:none;padding:0}.achieve_container_slide{width:100%;display:none}.achieve_container_slide.__active,header,main,nav,section{display:block}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}a{color:#337ab7;text-decoration:none}strong{font-weight:700}h1{margin:.67em 0}img{border:0;vertical-align:middle}button{font:inherit;overflow:visible;text-transform:none;-webkit-appearance:button}address,body{line-height:1.42857}button::-moz-focus-inner{border:0;padding:0}*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px}body{background-color:#fff;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;color:#333}button,h1,h3{color:inherit;font-family:inherit}button{font-size:inherit;line-height:inherit}.img-responsive{display:block;max-width:100%;height:auto}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h3,ul{margin-bottom:10px}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@-ms-viewport{width:device-width}.collapse{display:none}.container:after,.container:before,.nav:after,.nav:before{display:table;content:" "}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}h1,h3{font-weight:500;line-height:1.1;margin-top:20px}h1{font-size:36px}h3{font-size:24px}p{margin:0 0 10px}address{margin-bottom:20px;font-style:normal}ul{margin-top:0}abbr[title]{border-bottom:1px dotted #777}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li,.nav>li>a{display:block;position:relative}.nav>li>a{padding:10px 15px}.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before{content:" ";display:table}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.navbar{border-radius:4px}.navbar-header{float:left}.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (min-width:768px){.navbar-toggle{display:none}.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse{border-color:#e7e7e7}
</style>
<script id="loadcss">
	(function(u,s){!function(e){"use strict";var n=function(n,t,o){var l,r=e.document,i=r.createElement("link");if(t)l=t;else{var a=(r.body||r.getElementsByTagName("head")[0]).childNodes;l=a[a.length-1]}var d=r.styleSheets;i.rel="stylesheet",i.href=n,i.media="only x",l.parentNode.insertBefore(i,t?l:l.nextSibling);var f=function(e){for(var n=i.href,t=d.length;t--;)if(d[t].href===n)return e();setTimeout(function(){f(e)})};return i.onloadcssdefined=f,f(function(){i.media=o||"all"}),i};"undefined"!=typeof module?module.exports=n:e.loadCSS=n}("undefined"!=typeof global?global:this);for(var i in u){loadCSS(u[i],s);}}(['styles/index.css'],document.getElementById("loadcss")));
</script>
<noscript>
	<link rel="stylesheet" href="styles/index.css">
</noscript>

JavaScript minification and concatenation

Add last task – JS minification into gulpfile.js.

var uglify = require('gulp-uglify');

// Concatenate and minify JS
gulp.task('js', function() {
	return gulp.src([
		 'bower_components/jquery/dist/jquery.min-1468949512196.js',
		 'bower_components/moff/dist/bootstrap/javascripts/collapse.js',
		 'bower_components/moff/dist/moff.js',
		 'modules/gallery/gallery.js'
	])
	.pipe(concat('index.js'))
	.pipe(uglify())
	.pipe(gulp.dest('scripts'));
});

Run command

$ gulp js

After this you will see scripts folder with index.js file inside. And you can replace all your scripts

<script src="bower_components/jquery/dist/jquery.min-1468949512196.js"></script>
<script src="bower_components/moff/dist/bootstrap/javascripts/collapse.js"></script>
<script src="bower_components/moff/dist/moff.js"></script>
<script src="modules/gallery/gallery.js"></script>

to index.js

<script src="scripts/index.js"></script>

Overall

Now we have finished creation of Mobile First page and we reached our goals:

  • Make website maximum light and fast
  • Let get user all info into the page
  • Load additional traffic only by user request

Overall we reduced website size to ~244 KB when the user enters. Without Mobile First approach our website would size ~604 KB. So Mobile First approach reduced website size to ~60% and made it fast and light.

Example of created page .