Tutorial: How to Create a Responsive Website with AngularJS
Join the DZone community and get the full member experience.
Join For Freein today’s tutorial, i’m going to show you the process of creating nearly an entire website with a new library – angularjs. however, i would like to introduce to you to angularjs first. angularjs is a magnificent framework by google for creating web applications. this framework lets you extend html’s syntax to express your application’s components clearly and succinctly, and lets you use standard html as your main template language. plus, it automatically synchronizes data from your ui with your js objects through 2-way data binding.
if you’ve ever worked with jquery, the first thing to understand about angular is that this is a completely different instrument. jquery is a library, but angularjs is framework. when your code works with the library, it decides when to call a particular function or operator. in the case of the framework, you implement event handlers, and the framework decides at what moment it needs to invoke them.
using this framework allows us to clearly distinguish between templates (dom), models, and functionality (in controllers). let’s come back to our template, take a look at our result:
live demo
download in package
description
this template is perfect for business sites. it consists of several static pages: projects, privacy, and about pages. each product has its own page. there is also a contact form for communication. that is all that is necessary for any small website. moreover, it is also a responsive template, thus it looks good on any device.
i hope you liked the demo, so if you’re ready – let’s start making this application. please prepare a new folder for our project, and then create new folders in this directory:
- css – for stylesheet files
- images – for image files
- js – for javascript files (libraries, models, and controllers)
- pages – for internal pages
stage 1. html
the main layout consists of four main sections: a header with navigation, a hidden ‘contact us’ form, a main content section, and a footer. first we have to prepare a proper header:
index.html
<head>
<meta charset="utf-8" />
<meta name="author" content="script tutorials" />
<title>responsive website using angularjs | script tutorials</title>
<meta name="description" content="responsive website using angularjs - demo page">
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<!-- add styles -->
<link href="css/style.css" rel="stylesheet" type="text/css" />
<!-- add javascripts -->
<script src="js/jquery-2.0.3.min.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
as you can see, it’s an ordinary header. now – the header with the navigation:
<header>
<div class="wrap">
<!-- logo -->
<a href="#!"><img class="logo" src="images/logo.png" /></a>
<!-- navigation menu -->
<nav>
<ul>
<li><a id="workbtn" href="#!/" ng-class="{activesmall:part == 'projects'}" >our projects</a></li>
<li><a id="privacybtn" href="#!/privacy" ng-class="{activesmall:part == 'privacy'}">privacy & terms</a></li>
<li><a id="aboutbtn" href="#!/about" ng-class="{activesmall:part == 'about'}">about</a></li>
<li style="margin-right:0px"><a id="contactbtn" class="active" href="javascript: void(0)" ng-click="showform()">contact us</a></li>
</ul>
</nav>
</div>
</header>
it's an ordinary logo, and the menu is the usual ul-li menu. the next section is more interesting – the ‘contact us’ form:
<!-- contact us form -->
<div class="paddrow contactrow">
<div class="wrap">
<div class="head">contact us</div>
<img class="close" src="images/close.png" ng-click="closeform()" />
<form ng-submit="save()" class="contactform" name="form" ng-hide="loaded">
<input class="input" required="required" type="text" name="name" placeholder="your name" ng-model="message.name" />
<input class="input email" required="required" type="email" name="email" value="" placeholder="your email" ng-model="message.email" /><br />
<textarea class="textarea" rows="5" required="required" placeholder="your message" ng-model="message.text" ></textarea>
<button class="btn green">send message</button>
</form>
<!-- contact us form response messages -->
<div ng-show="process" style="text-align:center">
<img class="loader" src="images/loader.png" />
</div>
<div ng-show="success"><p>your message has been sent, thank you.</p></div>
</div>
</div>
finally, the last key element is the main content section:
<!-- main content -->
<div style="position:relative">
<div style="width:100%" ng-view ng-animate="{enter: 'view-enter', leave: 'view-leave'}"></div>
</div>
have you noticed the numerous ‘ng-’ directives? all these directives allow us to do various actions directly in the dom, for example:
- ng-class – the ngclass allows you to set css classes on an html element dynamically by databinding an expression that represents all classes to be added.
- ng-click – the ngclick allows you to specify custom behavior when element is clicked.
- ng-hide – the nghide directive shows and hides the given html element conditionally based on the expression provided to the nghide attribute.
- ng-include – fetches, compiles, and includes an external html fragment.
- ng-model – is a directive that tells angular to do two-way data binding.
- ng-show – the ngshow directive shows and hides the given html element conditionally based on the expression provided to the ngshow attribute.
- ng-submit – enables binding angular expressions to onsubmit events.
stage 2. css
in this rather large section, you can find all the styles used:
css/style.css
/* general settings */
html {
min-height:100%;
overflow-x:hidden;
overflow-y:scroll;
position:relative;
width:100%;
}
body {
background-color:#e6e6e6;
color:#fff;
font-weight:100;
margin:0;
min-height:100%;
width:100%;
}
a {
text-decoration:none;
}
a img {
border:none;
}
h1 {
font-size:3.5em;
font-weight:100;
}
p {
font-size:1.5em;
}
input,textarea {
-webkit-appearance:none;
background-color:#f7f7f7;
border:none;
border-radius:3px;
font-size:1em;
font-weight:100;
}
input:focus,textarea:focus {
border:none;
outline:2px solid #7ed7b9;
}
.left {
float:left;
}
.right {
float:right;
}
.btn {
background-color:#fff;
border-radius:24px;
color:#595959;
display:inline-block;
font-size:1.4em;
font-weight:400;
margin:30px 0;
padding:10px 30px;
text-decoration:none;
}
.btn:hover {
opacity:0.8;
}
.wrap {
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
box-sizing:border-box;
margin:0 auto;
max-width:1420px;
overflow:hidden;
padding:0 50px;
position:relative;
width:100%;
}
.wrap:before {
content:'';
display:inline-block;
height:100%;
margin-right:-0.25em;
vertical-align:middle;
}
/* header section */
header {
height:110px;
}
header .wrap {
height:100%;
}
header .logo {
margin-top:1px;
}
header nav {
float:right;
margin-top:17px;
}
header nav ul {
margin:1em 0;
padding:0;
}
header nav ul li {
display:block;
float:left;
margin-right:20px;
}
header nav ul li a {
border-radius:24px;
color:#aaa;
font-size:1.4em;
font-weight:400;
padding:10px 27px;
text-decoration:none;
}
header nav ul li a.active {
background-color:#c33c3a;
color:#fff;
}
header nav ul li a.active:hover {
background-color:#d2413f;
color:#fff;
}
header nav ul li a:hover,header nav ul li a.activesmall {
color:#c33c3a;
}
/* footer section */
footer .copyright {
color:#adadad;
margin-bottom:50px;
margin-top:50px;
text-align:center;
}
/* other objects */
.projectobj {
color:#fff;
display:block;
}
.projectobj .name {
float:left;
font-size:4em;
font-weight:100;
position:absolute;
width:42%;
}
.projectobj .img {
float:right;
margin-bottom:5%;
margin-top:5%;
width:30%;
}
.paddrow {
background-color:#dadada;
color:#818181;
display:none;
padding-bottom:40px;
}
.paddrow.aboutrow {
background-color:#78c2d4;
color:#fff !important;
display:block;
}
.paddrow .head {
font-size:4em;
font-weight:100;
margin:40px 0;
}
.paddrow .close {
cursor:pointer;
position:absolute;
right:50px;
top:80px;
width:38px;
}
.about {
color:#818181;
}
.about section {
margin:0 0 10%;
}
.about .head {
font-size:4em;
font-weight:100;
margin:3% 0;
}
.about .subhead {
font-size:2.5em;
font-weight:100;
margin:0 0 3%;
}
.about .txt {
width:60%;
}
.about .image {
width:26%;
}
.about .flleft {
float:left;
}
.about .flright {
float:right;
}
.projecthead.product {
background-color:#87b822;
}
.projecthead .picture {
margin-bottom:6%;
margin-top:6%;
}
.projecthead .picture.right {
margin-right:-3.5%;
}
.projecthead .text {
position:absolute;
width:49%;
}
.projecthead .centertext {
margin:0 auto;
padding-bottom:24%;
padding-top:6%;
text-align:center;
width:55%;
}
.image {
text-align:center;
}
.image img {
vertical-align:top;
width:100%;
}
.contactform {
width:50%;
}
.input {
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
box-sizing:border-box;
margin:1% 0;
padding:12px 14px;
width:47%;
}
.input.email {
float:right;
}
button {
border:none;
cursor:pointer;
}
.textarea {
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
box-sizing:border-box;
height:200px;
margin:1% 0;
overflow:auto;
padding:12px 14px;
resize:none;
width:100%;
}
::-webkit-input-placeholder {
color:#a7a7a7;
}
:-moz-placeholder {
color:#a7a7a7;
}
::-moz-placeholder { /* ff18+ */
color:#a7a7a7;
}
:-ms-input-placeholder {
color:#a7a7a7;
}
.loader {
-moz-animation:loader_rot 1.3s linear infinite;
-o-animation:loader_rot 1.3s linear infinite;
-webkit-animation:loader_rot 1.3s linear infinite;
animation:loader_rot 1.3s linear infinite;
height:80px;
width:80px;
}
@-moz-keyframes loader_rot {
from {
-moz-transform:rotate(0deg);
}
to {
-moz-transform:rotate(360deg);
}
}
@-webkit-keyframes loader_rot {
from {
-webkit-transform:rotate(0deg);
}
to {
-webkit-transform:rotate(360deg);
}
}
@keyframes loader_rot {
from {
transform:rotate(0deg);
}
to {
transform:rotate(360deg);
}
}
.view-enter,.view-leave {
-moz-transition:all .5s;
-o-transition:all .5s;
-webkit-transition:all .5s;
transition:all .5s;
}
.view-enter {
left:20px;
opacity:0;
position:absolute;
top:0;
}
.view-enter.view-enter-active {
left:0;
opacity:1;
}
.view-leave {
left:0;
opacity:1;
position:absolute;
top:0;
}
.view-leave.view-leave-active {
left:-20px;
opacity:0;
}
please note that css3 transitions are used, which means that our demonstration will only work modern browsers (ff, chrome, ie10+ etc)
stage 3. javascript
as i mentioned before, our main controller and the model are separated. the navigation menu can be handled here, and we also can operate with the contact form.
js/app.js
'use strict';
// angular.js main app initialization
var app = angular.module('example359', []).
config(['$routeprovider', function ($routeprovider) {
$routeprovider.
when('/', { templateurl: 'pages/index.html', activetab: 'projects', controller: homectrl }).
when('/project/:projectid', {
templateurl: function (params) { return 'pages/' + params.projectid + '.html'; },
controller: projectctrl,
activetab: 'projects'
}).
when('/privacy', {
templateurl: 'pages/privacy.html',
controller: privacyctrl,
activetab: 'privacy'
}).
when('/about', {
templateurl: 'pages/about.html',
controller: aboutctrl,
activetab: 'about'
}).
otherwise({ redirectto: '/' });
}]).run(['$rootscope', '$http', '$browser', '$timeout', "$route", function ($scope, $http, $browser, $timeout, $route) {
$scope.$on("$routechangesuccess", function (scope, next, current) {
$scope.part = $route.current.activetab;
});
// onclick event handlers
$scope.showform = function () {
$('.contactrow').slidetoggle();
};
$scope.closeform = function () {
$('.contactrow').slideup();
};
// save the 'contact us' form
$scope.save = function () {
$scope.loaded = true;
$scope.process = true;
$http.post('sendemail.php', $scope.message).success(function () {
$scope.success = true;
$scope.process = false;
});
};
}]);
app.config(['$locationprovider', function($location) {
$location.hashprefix('!');
}]);
pay attention here. when we request a page, it loads an appropriate page from the ‘pages’ folder: about.html, privacy.html, index.html. depending on the selected product, it opens one of the product pages: product1.html, product2.html, product3.html or product4.html
in the second half, there are functions to slide the contact form and to handle its submit process (to the sendemail.php page). next is the controller file:
js/controllers.js
'use strict';
// optional controllers
function homectrl($scope, $http) {
}
function projectctrl($scope, $http) {
}
function privacyctrl($scope, $http, $timeout) {
}
function aboutctrl($scope, $http, $timeout) {
}
it is empty, because we have nothing to use here at the moment.
stage 4. additional pages
angularjs loads pages asynchronously, thereby increasing the speed. here are templates of all additional pages used in our project:
pages/about.html
<div style="width:100%">
<div class="paddrow aboutrow">
<div class="wrap">
<div class="head">about us</div>
<p>script tutorials is one of the largest web development communities. we provide high quality content (articles and tutorials) which covers all the web development technologies including html5, css3, javascript (and jquery), php and so on. our audience are web designers and web developers who work with web technologies.</p>
</div>
</div>
<div style="background-color:#f5f5f5">
<div class="wrap about">
<div class="head">additional information</div>
<section>
<div class="image flright">
<img src="images/ang.png" class="abicon">
</div>
<div class="txt flleft">
<h2 class="subhead">promo 1</h2>
<p>lorem ipsum dolor sit amet, consectetur adipiscing elit. nunc et ligula accumsan, pharetra nibh nec, facilisis nulla. in pretium semper venenatis. in adipiscing augue elit, at venenatis enim suscipit a. fusce vitae justo tristique, ultrices mi metus.</p>
</div>
<div style="clear:both"></div>
</section>
.....
</div>
</div>
<ng-include src="'pages/footer.html'"></ng-include>
</div>
pages/privacy.html
<div style="width:100%">
<div class="paddrow aboutrow">
<div class="wrap">
<div class="head">privacy & terms</div>
<p> by accessing this web site, you are agreeing to be bound by these web site terms and conditions of use, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. if you do not agree with any of these terms, you are prohibited from using or accessing this site. the materials contained in this web site are protected by applicable copyright and trade mark law.</p>
</div>
</div>
<div style="background-color:#f5f5f5">
<div class="wrap about">
<div class="head">other information</div>
<section>
<div class="image flleft">
<img src="images/ang.png" class="abicon">
</div>
<div class="txt flright">
<h2 class="subhead">header 1</h2>
<p>lorem ipsum dolor sit amet, consectetur adipiscing elit. nunc et ligula accumsan, pharetra nibh nec, facilisis nulla. in pretium semper venenatis. in adipiscing augue elit, at venenatis enim suscipit a. fusce vitae justo tristique, ultrices mi metus.</p>
</div>
<div style="clear:both"></div>
</section>
.....
</div>
</div>
<ng-include src="'pages/footer.html'"></ng-include>
</div>
pages/footer.html
<footer>
<div class="copyright"><a href="http://www.script-tutorials.com/" target="_blank">copyright © 2013 script tutorials</a></div>
</footer>
pages/index.html
<div style="width:100%">
<a class="projectobj" href="#!/project/product1" style="background-color:#87b822">
<div class="wrap">
<div class="name">product #1</div>
<img class="img" src="images/element.png" />
</div>
</a>
<a class="projectobj" href="#!/project/product2" style="background-color:#3f91d2">
<div class="wrap">
<div class="name">product #2</div>
<img class="img" src="images/element.png" />
</div>
</a>
<a class="projectobj" href="#!/project/product3" style="background-color:#f1784d">
<div class="wrap">
<div class="name">product #3</div>
<img class="img" src="images/element.png" />
</div>
</a>
<a class="projectobj" href="#!/project/product4" style="background-color:#f0c42c">
<div class="wrap">
<div class="name">product #4</div>
<img class="img" src="images/element.png" />
</div>
</a>
<ng-include src="'pages/footer.html'"></ng-include>
</div>
<script>
$('.projectobj').bind('click', function (e) {
e.preventdefault();
var me = this;
var width = $(me).width() / 1.5;
$(me).find('.wrap').width($(me).find('.wrap').width());
$(me).animate({
opacity: 0,
marginleft: -width
}, 500);
var delayn = 150;
var delayp = 150;
var nextel = $(me).nextall('.projectobj');
var prevel = $(me).prevall('.projectobj');
nextel.each(function (index, elem) {
settimeout(function () {
$(elem).find('.wrap').width($(elem).find('.wrap').width());
$(elem).animate({
opacity: 0,
marginleft: -width
}, 500, function () {
});
}, delayn);
delayn += 100;
});
prevel.each(function (index, elem) {
settimeout(function () {
$(elem).find('.wrap').width($(elem).find('.wrap').width());
$(elem).animate({
opacity: 0,
marginleft: -width
}, 500, function () {
});
}, delayp);
delayp += 100;
});
settimeout(function () {
document.location = $(me).attr('href');
},1000)
return false;
});
</script>
finally, the product pages. all of them are prototypes, so i decided to publish only one of them.
pages/index.html
<div style="width:100%">
<div class="projecthead product">
<div class="wrap">
<div class="text left">
<h1>product 1 page</h1>
<p>lorem ipsum dolor sit amet, consectetur adipiscing elit. nunc et ligula accumsan, pharetra nibh nec, facilisis nulla. in pretium semper venenatis. in adipiscing augue elit, at venenatis enim suscipit a. fusce vitae justo tristique, ultrices mi metus.</p>
<p>lorem ipsum dolor sit amet, consectetur adipiscing elit. nunc et ligula accumsan, pharetra nibh nec, facilisis nulla. in pretium semper venenatis. in adipiscing augue elit, at venenatis enim suscipit a. fusce vitae justo tristique, ultrices mi metus.</p>
<a class="btn" href="javascript: void(0)">download the app</a>
</div>
<img class="picture right" src="images/element.png" />
<div style="clear:both"></div>
</div>
</div>
<ng-include src="'pages/footer.html'"></ng-include>
</div>
finishing touches – responsive styles
all of these styles are needed to make our results look equally good on all possible mobile devices and monitors:
@media (max-width: 1200px) {
body {
font-size:90%;
}
h1 {
font-size:4.3em;
}
p {
font-size:1.3em;
}
header {
height:80px;
}
header .logo {
margin-top:12px;
width:200px;
}
header nav {
margin-top:11px;
}
header nav ul li {
margin-right:12px;
}
header nav ul li a {
border-radius:23px;
font-size: 1.3em;
padding:10px 12px;
}
.wrap {
padding:0 30px;
}
.paddrow .close {
right:30px;
}
}
@media (max-width: 900px) {
.contactform {
width:100%;
}
}
@media (max-width: 768px) {
body {
font-size:80%;
margin:0;
}
h1 {
font-size:4em;
}
header {
height:70px;
}
header .logo {
margin-top:20px;
width:70px;
}
header nav {
margin-top:8px;
}
header nav ul li {
margin-right:5px;
}
header nav ul li a {
border-radius:20px;
font-size:1.1em;
padding:8px;
}
.wrap {
padding:0 15px;
}
.projectobj .name {
font-size:3em;
}
.paddrow {
padding-bottom:30px;
}
.paddrow .head {
font-size:3em;
margin:30px 0;
}
.paddrow .close {
right:20px;
top:60px;
width:30px;
}
.projecthead .picture {
width:67%;
}
.projecthead .picture.right {
margin-right:16.5%;
}
.projecthead .text {
position:static;
width:100%;
}
.projecthead .centertext {
width:70%;
}
.view-enter,.view-leave {
-webkit-transform:translate3d(0,0,0);
transform:translate3d(0,0,0);
}
}
@media (max-width: 480px) {
body {
font-size:70%;
margin:0;
}
header {
height:50px;
}
header .logo {
display:none;
}
header nav {
margin-top:3px;
}
header nav ul li {
margin-right:3px;
}
header nav ul li a {
border-radius:20px;
font-size:1.3em;
padding:5px 14px;
}
#contactbtn {
display:none;
}
.wrap {
padding:0 10px;
}
.paddrow {
padding-bottom:20px;
}
.paddrow .head {
margin:20px 0;
}
.paddrow .close {
right:10px;
top:45px;
width:20px;
}
.about .image {
margin:10% auto;
width:60%;
}
.about .abicon {
display:inline;
}
.projecthead .centertext {
width:90%;
}
.about .txt,.input {
width:100%;
}
.about .flleft,.about .flright,.input.email {
float:none;
}
}
live demo
download in package
conclusion
that’s all for today. thanks for your patient attention, and if you really like what we did today, share it with all your friends in your social networks.
Published at DZone with permission of Andrey Prikaznov, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments