Responsive website using AngularJS

Responsive website using AngularJS

12 102280
Responsive website using AngularJS

Responsive website using AngularJS

In today’s tutorial, I’m going to show you the process of creating almost an entire website with a new library – AngularJS. However, firstly, I would like to introduce to you the AngularJS. AngularJS is a magnificent framework for creating Web applications. This framework lets you extend HTML’s syntax to express your application’s components clearly and succinctly, and lets use standard HTML as your main template language. Plus, it automatically synchronizes data from your UI with your javascript 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 website templates (DOM), models and functionality (in controllers). Let’s come back to our template, take a look at our result:

template preview


Live Demo

Description

This template is perfect for business sites. It consists of several static pages: the list of 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 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 next 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: header with navigation, hidden ‘contact us’ form, main content section and footer. First at all 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 quite 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 &amp; 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>

The ordinary logo, the menu is the usual UL-LI menu. Next section is more interesting – ‘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: 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 HTML an 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, it means that our demonstration will only work in most 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, when we request a page, it loads an appropriate page from the ‘pages’ folder: about.html, privacy.html, index.html. Depending on selected product, it opens one of 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 with it’s 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 asynchronous, 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 &amp; 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="https://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 prototype, 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 well 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

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 using the form below.

SIMILAR ARTICLES

jQuery Mobile Lesson 6

0 45
jQuery Mobile Lesson 5

0 75

12 COMMENTS

  1. Good tutorial, glad to see Angular getting more acceptance. The only issue is that you should never do DOM manipulation within a controller, that’s what directives are for, or just use the animate module (separated in 1.2-rc1) with class level animations based on JS, CSS3 transitions, or CSS3 key frame animations.

  2. As much as I love Angular, I would not use it for building a website (seems like the intention here). It is best suited for building single-page applications.
    You would get poor SEO performance if you were to build a website that way.

    Good tutorial however.

    • Hello Sam,
      I am not sure about your statement about SEO, because it depends on you in the most, not of the technology you use.

  3. I opened the live demo from my laptop and like it. I navigated between the four tabs “Our Projects”, “Privacy & Terms”, “About”, and “Contact Us” with Chrome on Linux Mint Petra 16 XFCE 64 bit using Chrome and Firefox for browsers. I then went to my Android Nexus 5 using the Chrome browser that comes as default. All my software is kept up-to-date. Unfortunately on the Nexus 5 the navigation tabs are not present. The products worked fine though but there was no way to navigate between the sections of the live demo.

    Any chance of fixing that quickly?

    • Hi Ioconnor,
      I haven’t tested it on Nexus phone yet. But if you’d like – I can test it at my HTC phone (it is android based phone too)

  4. Very helpful thanks Andrew. I’m trying this project out as a Chrome App and discovered the app loads but the menus will not load anything.

    I’ve peeked into many Chrome App sample projects and it looks like my manifest configurations are correct but with AJS there might be something unique I do not know which must be considered when run as a Chrome App.

    If you care to comment Andrew any pointers or code mods in this context of Chrome Apps are going to be very helpful but if you do not I hope you run out of beer and your car won’t start. :-)

    • Hi Clinton,
      Do you mean the Chrome browser or what? You mentioned the Chrome App, but App is – application (browser is application too). The demo works good in the Chrome browser.

      • I meant Chrome App in the context of Google’s requirement to use a manifest file and run an app on any desktop that supports the Chrome browser noting Chrome Apps can be run without any “browser chrome” e.g. no menu bars and such exactly alike an Adobe AIR app.

  5. Hi. Thanks Andrew nice tutorial. But unfortunately is not working the package. I have the following error: Uncaught SecurityError: Failed to execute ‘replaceState’ on ‘History’: A history state object with URL ‘file:///C:/Users/kajcs/Desktop/sources359/index.html#!/index.html’ cannot be created in a document with origin ‘null’.

    • Please pay attention, that you shouldn’t run it in browser with the address like that (like a local file), you need to open it in apache environment. I recommend you to use WAMP, so, you just need to run it on localhost

Leave a Reply