Responsive Horizontal Layout

In this tutorial we’ll create a horizontal website layout with individually scrollable content panels. We’ll change the layout for smaller screens, making it responsive.

Responsive Horizontal Layout

Today we want to show you how to create a horizontal layout with several content panels. The idea is to make each panel individually scrollable and animate a content panel to the left of the viewport once it’s clicked or selected from the menu. The challenge is to deal with different viewport sizes meaning that we will change the layout if the screen is not wide enough in order to be stacked vertically instead of horizontally.

We will be using a couple of useful jQuery plugins:

In the demo we are using Charles Darwin’s The Origin of Species by means of Natural Selection, 6th Edition which you can find as an e-book on Project Gutenberg.

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

The Markup

The HTML will be built up of a menu that will be fixed at the left side and a container with all the individual panels inside. All will be wrapped by a main container:

<div id="hs-container" class="hs-container">

	<!-- ... -->
	
</div>

The menu will have the following structure:

<aside class="hs-menu" id="hs-menu">

	<div class="hs-headline">
		<h1>
			<small>The</small> 
			Origin <small>of</small> 
			Species
		</h1>
		<small>6<sup>th</sup> Edition</small>
		<span class="author">by Charles Darwin</span>
	</div>
	
	<nav>
		<a href="#introduction">
			<span>Introduction</span>
		</a>
		<a href="#chapter1">
			<span>Chapter I.</span>
			<span>Variation under Domestication</span>
		</a>
		<a href="#chapter2">
			<span>Chapter II.</span>
			<span>Variation Under Nature </span>
		</a>
		<!-- ... -->
	</nav>
	
</aside>

<a href="#hs-menu" class="hs-totop-link">Go to the top</a>

We’ll have a headline and a navigation. We’ve also added a loose helper link that we’ll need to show for smaller screens where we will stack the content vertically.

The content will have the following structure:

<div class="hs-content-scroller">

	<div class="hs-content-wrapper">
	
		<article class="hs-content" id="introduction">
			<div class="hs-inner">
				<h2>Introduction</h2>
				<p>...</p>
			</div>
		</article>
		
		<article class="hs-content" id="chapter1">
			<div class="hs-inner">
				<h2>Chapter I.</h2>
				<h3>Variation Under Domestication</h3>
				<h4>Causes of Variability</h4>
				<p>...</p>
			</div>
		</article>
		
		<!-- ... -->
		
	</div>
	
</div>

The first wrapper with the class hs-content-scroller will act like a mask, having the width and height that is visible in the viewport. This will be the division that we’ll scroll horizontally because the second wrapper with the class hs-content-wrapper will be as wide as the sum of all article widths.

As you can see, each article will get an ID which we link to in our navigation.

Let’s style this thing.

The CSS

So, our main aim is to fix the sidebar at the left side of the screen and place the content as a horizontal stack.
Let’s first style the body and some headings. We’ll set both, overflow-x and overflow-y to hidden. We use the separate properties instead of the single “overflow” because we want to adjust something in a media query later, but we’ll get back to that.

We’ll first import the normalize.css, a really nice and sensible alternative to the common resets:

@import url('normalize.css');

body{
	font-family: Baskerville, "Hoefler Text", Garamond, "Times New Roman", serif;
	background: #e9f0f5 url(../images/pattern.png) repeat top left;
	font-weight: 400;
	font-size: 12px;
	color: #333;
	overflow-y: hidden;
	overflow-x: hidden;
}

Next, let’s define some general text styles:

h1, h3, h4{
	font-weight: 400;
}
h1{
	font-style: italic;
	border-bottom: 1px solid rgba(126, 126, 126, 0.3);
	padding: 35px 15px 15px 15px;
	margin: 0px 20px 20px 20px;
	position: relative;
	font-size: 38px;
}
h2{
	font-size: 40px;
	padding-bottom: 15px;
	border-bottom: 5px solid rgba(190, 211, 226, 0.2);
	color: #a9becd;
	text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.4);
	box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.4);
	font-weight: 700;
}
h3{
	font-style: italic;
	font-size: 26px;
	color: #585959;
	text-shadow: 1px 0px 1px rgba(255, 255, 255, 0.4);
}
h4{
	text-transform: uppercase;
	letter-spacing: 5px;
	line-height: 20px;
	padding: 10px 0px;
	color: #626a6f;
	border-bottom: 1px solid rgba(126, 126, 126, 0.1);
	box-shadow: 0px 1px 0px 0px rgba(255, 255, 255, 0.4);
}
a{
	color: #308fd9;
	text-decoration: none;
	text-transform: uppercase;
	letter-spacing: 2px;
}
a:hover{
	color: #87b6da;
}
.hs-headline{
	text-align: center;
}
.hs-author{
	text-transform: uppercase;
	display: block;
	letter-spacing: 2px;
	font-weight: 700;
	padding: 20px 10px;
}

Now, let’s position the menu:

.hs-menu{
	position: fixed;
	z-index: 100;
	color: #f8f8f8;
	background: #131313;
	width: 200px;
	left: 0px;
	top: 0px;
	height: 100%;
}

Setting its position to fixed, we’ll stick the sidebar to the left side of the screen.
The navigation will have the following style:

.hs-menu nav{
	position: absolute;
	top: 250px;
	left: 0px;
	right: 0px;
	bottom: 50px;
}

The position of the navigation will be absolute and by setting a top and bottom value, we have a flexible height. Later, we will add custom scrolling to the navigation so that we don’t have to worry about the menu items fitting into the area.

The anchors will have the following style:

.hs-menu nav a{
	display: block;
	padding: 10px 20px;
	text-align: center;
	outline: none;
	border-bottom: 1px dashed rgba(126, 126, 126, 0.2);
}
.hs-menu nav a:active{
	box-shadow: 7px 0px 17px #000 inset;
}
.hs-menu nav a:first-child{
	border-top: 1px dashed rgba(126, 126, 126, 0.2);
}

The second span in a navigation anchor will be styled differently:

.hs-menu nav a span:nth-child(2){
	display: block;
	color: #fff;
	font-style: italic;
	font-weight: 400;
	text-transform: none;
	padding-top: 3px;
}

Now, let’s style the content part. As mentioned before, the division with the class .hs-content-scroller will be acting as a mask where any overflow won’t be visible. This is basically the same principle as in sliders. The left will be set to 200 pixel because of the sidebar:

.hs-content-scroller{
	position: absolute;
	left: 200px;
	right: 0px;
	overflow: hidden;
	height: 100%;
}

The next wrapper will have a width which allows all the inner content panels to fit inside of it if stacked horizontally. We’ll set the overflow to hidden, because each of our content panels will have a custom scroll bar.

.hs-content-wrapper{
	width: 7950px;
	position: absolute;
	height: 100%;
	overflow: hidden;
}

Each content panel is going to have a width of 500 pixel and float left. We’ll add a transition for the background when we hover and when we add a active class:

.hs-content{
	width: 500px;
	overflow-y: scroll;
	height: 100%;
	float: left;
	border-right: 1px dashed rgba(126, 126, 126, 0.2);
	border-left: 1px dashed rgba(255, 255, 255, 0.5);
	background: transparent;
	transition: background 0.3s linear;
}
.hs-content:hover, .hs-content-active{
	background: #f1f5f8;
}

When we add the custom scrolling to the content panels, we only want them to be visible when we hover over them:

.hs-content:hover .jspVerticalBar,
.hs-menu nav:hover .jspVerticalBar{
	opacity: 1;
}

The first content panel will be a bit narrower:

.hs-content:first-child{
	width: 400px;
}

Let’s add some padding and style the text elements:

.hs-inner{
	padding: 30px 20px 10px 30px;
}
.hs-inner p{
	font-size: 18px;
	line-height: 24px;
	text-align: justify;
	position: relative;
	padding: 10px 0px;
	text-shadow: 1px 0px 1px rgba(255, 255, 255, 0.8);
}
.hs-inner p:first-letter{
	font-size: 28px;
}
#introduction .hs-inner p:first-of-type{
	font-size: 24px;
	text-align: left;
	line-height: 36px;
	font-style: italic;
	color: #75838D;
	letter-spacing: 0px;
}

Remember that little anchor right after the sidebar? When clicking that anchor, the page will scroll up. We’ll only need this anchor when our screen is not big enough to stack the content panels horizontally but only vertically, so we’ll set it to display: none initially. In a media query we’ll then show it.

a.hs-totop-link{
	display: none;
	position: fixed;
	z-index: 10000;
	bottom: 0px;
	width: 100%;
	height: 40px;
	line-height: 40px;
	font-size: 14px;
	color: #aaa;
	text-shadow: 1px 1px 1px #fff;
	font-weight: 700;
	cursor: pointer;
	text-transform: uppercase;
	text-align: center;
	background: linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%);
	border-top: 1px solid #cacaca;
}

When we reach the size of the iPad, we want to get rid of the hover behavior and show the horizontal scrollbar. This will allow us to “swipe” the content on the iPad:

/* Media Queries */
@media screen and (max-width: 1024px) {
	.jspVerticalBar{
		opacity: 1;
	}
	.hs-content-scroller{
		overflow-x: scroll;
	}
	.hs-content:hover{
		background: transparent;
	}
	.hs-content-active:hover {
		background: #f1f5f8;
	}
}

At this point, we’ll change the layout in order to be stacked vertically. We have to “reset” all the properties that forced the content to be stacked horizontally. We’ll also show the anchor that will bring us back to the top:

@media screen and (max-width: 715px) {
	body{
		overflow-x: auto;
		overflow-y: auto;
	}
	a.hs-totop-link{
		display: block;
	}
	.hs-menu{
		position: relative;
		width: 100%;
		height: 460px;
	}
	.hs-menu nav{
		top: 230px;
		bottom: 20px;
	}
	.hs-content-scroller{
		position: relative;
		height: auto;
		left: 0;
	}
	.hs-content-wrapper{
		height: auto;
		width: auto;
		margin-left: 0px;
	}
	.hs-content{
		border: none;
	}
	.hs-content, .hs-content:first-child{
		width: auto;
		float: none;
		overflow-y: auto;
	}
}

And that’s all the style! Now, let’s take a look at the JavaScript!

The JavaScript

First, we will set some variables and cache some elements:


var $container			= $( '#hs-container' ),
	// the scroll container that wraps the articles
	$scroller			= $container.find( 'div.hs-content-scroller' ),
	$menu				= $container.find( 'aside' ),
	// menu links
	$links				= $menu.find( 'nav > a' ),
	$articles			= $container.find( 'div.hs-content-wrapper > article' ),
	// button to scroll to the top of the page
	// only shown when screen size < 715
	$toTop				= $container.find( 'a.hs-totop-link' ),
	// the browser nhistory object
	History 			= window.History,
	// animation options
	animation			= { speed : 800, easing : 'easeInOutExpo' },
	// jScrollPane options
	scrollOptions		= { verticalGutter : 0, hideFocus : true },

The init function will be the first one to execute:


init				= function() {
				
	// initialize the jScrollPane on both the menu and articles
	_initCustomScroll();
	// initialize some events
	_initEvents();
	// sets some css properties 
	_layout();
	// jumps to the respective chapter
	// according to the url
	_goto();
	
},

The first step is to create / initialize the jScrollPane (the custom scrollbars) on both the menu and the articles. However, for the articles, we will not do this in case the screen size is smaller than 715px:


_initCustomScroll	= function() {
	
	// Only add custom scrolling to articles if screen size > 715.
	// If not, the articles will be expanded (vertical layout)
	if( $(window).width() > 715 ) {
	
		$articles.jScrollPane( scrollOptions );
	
	}
	// add custom scrolling to menu
	$menu.children( 'nav' ).jScrollPane( scrollOptions );

},

We will load the events for the window, the menu links and the articles.

On window resize, we will need to reinitialize the jScrollPane custom scrollbars, or destroy them in case the screen's size gets smaller than 715px.

On window statechange, we will jump to the respective state / chapter. We are using History.js by Benjamin Lupton to control the history states when the user navigates through the page:


$(window).on({
	// when resizing the window we need to reinitialize or destroy the jScrollPanes
	// depending on the screen size
	'smartresize' : function( event ) {
		
		_layout();
		
		$('article.hs-content').each( function() {
			
			var $article 	= $(this),
				aJSP		= $article.data( 'jsp' );
			
			if( $(window).width() > 715 ) {
				
				( aJSP === undefined ) ? $article.jScrollPane( scrollOptions ) : aJSP.reinitialise();
				
				_initArticleEvents();
				
			}	
			else {
				
				// destroy article's custom scroll if screen size <= 715px
				if( aJSP !== undefined )
					aJSP.destroy();
				
				$container.off( 'click', 'article.hs-content' );
				
			}
			
		});
		
		var nJSP = $menu.children( 'nav' ).data( 'jsp' );
		nJSP.reinitialise();
		
		// jumps to the current chapter
		_goto();
	
	},
	// triggered when the history state changes - jumps to the respective chapter
	'statechange' : function( event ) {
		
		_goto();
	
	}
});

When we click on an article or menu link, we will check to which chapter it refers to, and we will change the state of the browser history object. This will trigger the statechange event which in turn will make the page / scrolling division jump to the respective area.


$links.on( 'click', function( event ) {
					
	var href		= $(this).attr('href'),
		chapter		= ( href.search(/chapter/) !== -1 ) ? href.substring(8) : 0;
	
	_saveState( chapter );
	
	return false;

});

$container.on( 'click', 'article.hs-content', function( event ) {
					
	var id			= $(this).attr('id'),
		chapter		= ( id.search(/chapter/) !== -1 ) ? id.substring(7) : 0;
	
	_saveState( chapter );
	
	return false;

});

_saveState			= function( chapter ) {
				
	// adds a new state to the history object
	// this will trigger the statechange on the window
	if( History.getState().url.queryStringToJSON().chapter !== chapter ) {
			
		History.pushState( null, null, '?chapter=' + chapter );
	
	}

},

We will also control the overflow property of the "scroller" (divison with class "hs-content-scroller") according to the screen size.


_layout				= function() {

	var windowWidth	= $(window).width();
	switch( true ) {
	
		case ( windowWidth <= 715 ) : $scroller.scrollLeft( 0 ).css( 'overflow', 'visible' ); break;
		case ( windowWidth <= 1024 ): $scroller.css( 'overflow-x', 'scroll' ); break;
		case ( windowWidth > 1024 ) : $scroller.css( 'overflow', 'hidden' ); break;
	
	};
	
},

The _goto function triggers the animation for changing the area / chapter on the page. We get the current chapter from the history state URL, and given the respective article we either scroll to the left or top depending on the screen size. Also, if we are in landscape mode, the element scrolling is the div "hs-content-scroller", otherwise it's the BODY element.


_goto				= function( chapter ) {
				
	// get the url from history state (e.g. chapter=3) and extract the chapter number
var chapter 	= chapter || History.getState().url.queryStringToJSON().chapter,
	isHome		= ( chapter === undefined ),
	// we will jump to the introduction chapter if theres no chapter
	$article 	= $( chapter ? '#' + 'chapter' + chapter : '#' + 'introduction' );

	if( $article.length ) {

			// left / top of the element
		var left		= $article.position().left,
			top			= $article.position().top,
			// check if we are scrolling down or left
			// is_v will be true when the screen size < 715
			is_v		= ( $(document).height() - $(window).height() > 0 ),
			// animation parameters:
			// if vertically scrolling then the body will animate the scrollTop,
			// otherwise the scroller (div.hs-content-scroller) will animate the scrollLeft
			param		= ( is_v ) ? { scrollTop : (isHome) ? top : top + $menu.outerHeight( true ) } : { scrollLeft : left },
			$elScroller	= ( is_v ) ? $( 'html, body' ) : $scroller;
		
		$elScroller.stop().animate( param, animation.speed, animation.easing, function() {
			
			// active class for selected chapter
			$articles.removeClass( 'hs-content-active' );
			$article.addClass( 'hs-content-active' );
			
		} );

	}

},

And that's it! I hope you enjoyed this tutorial and find it useful!

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 69

Comments are closed.
  1. wow!!! good solution… thanks for awesome tutorial… i will try to understand more…

  2. …though the fonts seen on the screenshot are not showing up in FF 11 & Chrome… I can only see Times everywhere.

  3. One of the best implementations of the responsive design I’ve ever seen. AMAZING!

  4. Firstly I love the tutorials that you post they have been a great teaching material.

    In the demo for < 715px the scroll is just controlled by the scrollbar, normal scroll doesn't seem to be working.What is causing that?
    Also I think it would look better if instead of including panels that are cut off. the excess space is filled up with 'skeumorphistic' design elements.

  5. Wonderful tutorial! This will be useful for the next issue of FireNews Magazine. I wonder if CSS litebox page on click would work. Now I have some ideas, thanks!

  6. It’s sooo annoying to be constantly reminded about how not as good you are as other people!

    Thanks for a brilliant tute.

    p.s. just checked this out on the ipad and my only suggestion would be to get the free scrolling happening. It feels too rigid on a touch device.

    I did this example for a client once http://dev.justinavery.me/colemanipad/ which used iscroll.js for the scrolling. I did try and use the native

    -webkit-overflow-scrolling:touch;

    to accomplish the same affect, http://dev.justinavery.me/colemanipad/index2.html, but the icons wouldn’t show until the scrolling stopped.

    • Thanks for the tip, Justin! I’ll definitely take a look at that! Cheers, ML

  7. Thanks for sharing another amazing tutorial, Codrops really is the no.01 resource for website design and development.

    One more string to my bow, thanks again for sharing!

  8. lovely tutorial.
    you have smart ideas mary.
    can you explain the $article.data( ‘jsp’ ) please?

    • That returns the jScrollPane API. You initialize it like this:

      $element.jScrollPane(options);

      And if you want to apply any of the public methods of jScrollPane:

      
      var api = $element.data(jsp);
      api.publicMethod();
      
      

      See an example here.
      Cheers, ML

  9. Mary Lou, U always come with amazing 😀 Love this and thank you,
    warm greetings from Indonesia 🙂

  10. Hi Mary Lou

    This is a really interesting tutorial and something I have never come across before. It definatly provides an alternative solution to responsive web design! The only drawback it would seem is the amount of coding evolved and how intuitive it is to a first time user.

    Thanks Cathy

  11. @alirot: It does work in IE – hope you aren´t talking about IE6?! If you mean the media queries – there are JQuery plugins that make them work in IE, too, just do a search.
    Great work as always, Mary Lou! Many thanks for sharing your ideas!

    • Fine. But it doesn’t scroll on firefox. I hope it world on iPad at least.
      Francesco

  12. Looks nice!
    However I felt kind of confused looking at the demo: I saw half of the second chapter but couldn’t scroll horizontally to get to it to read it. I had to click the corresponding link in the menu bar, but since I was so focussed on the content it took me some time to find that option.

    I really liked having part of the next chapter to the original article’s right since it “teases” me to read further, but this way of scrolling doesn’t really work for me. How can I make it possible to scroll horizontally as well? It would be great if each scrolling would stop at every chapter, making it some sort of skimming à la touch device.

  13. It’s on responsive horizontal design, but it’s a shame it’s not responive vertically on netbook screen, which makes it useless.

  14. This is a brilliant tutorial. Thanks so much for making it.

    One question: I can’t place my own links within each chapter(i.e external links).. any idea how to fix this please?

    • To fix that, edit line 201 in jquery.page.js where (initArticleEvents = function() {) , change return false to true and should work, its works for me normal

  15. excelente tutorial, me gusta los bloques horizontales, ademas que es compatible para dispositivos móviles.. 😉

  16. It would be really interesting to see this as an image based tut. I haven’t seen much online dealing specifically with vertical images and responsive design. Great work Mary Lou!

  17. This is most excellent. Im looking at changing the urls generated from “?chapter=X” to more descriptive titles, eg “?page=contact-us” and ran into trouble getting the script to scroll to the appropriate anchor. Any tips on pulling this off would be great. I know its something within the _goto part of the script that needs changing to accomplish this but I cant figure out what it is..

  18. Hello..
    I love this tutorial 🙂
    Can someone help me?! How i do to change the url “?chapter” to “?page”? I tried to change the “jquery.page.js but didn’t work.
    And how can i give a current state to menu? If i click on the menu or directly on the article, the class of the button change to “current”.

    (Sorry my english)

    🙂

    • The menu is now changing the class to “current” 😀

      Now i’m trying to edit the “?chapter”… any idea? :p

  19. Nice idea, but I see some problems in Firefox12 with Win7 Professional: it’s not resizing columns properly and the menu on the left most column appears somewhat weird.

  20. this is great! The one alteration I was wondering about is adding a scrollbar to the bottom of the page to aid in navigation

  21. This is a great layout perfect for what im trying to do. How do i add external links to images that i place in the columns?, So over-ride the click it has there currently that navigated you to that particular column (sorry if its a rookie question!

    Thanks

  22. This is a great guide to some really great ideas. but when viewing the demo on an iPod, the go to top doesn’t stay fixed to the very bottom. On a desktop, it works great. Just thought you should know.

    • Did you manage to get this working? It would awesome to implement for a more liquid/fluid approach than re-declaring the content width each time using @media queries.

  23. I’m starting a side project and I had this idea in mind..and this TOTALLY helps me! Woohoo!!

  24. Woahh, this tutorial is amazing Mary Lou, what a great article!!!
    It would be awesome if there’d be “next” and “prev” buttons to scroll to other sections or a scroll bar at the bottom like here

    Keep writing articles like this please 🙂

  25. Thanks so much for this great tutorial!! I have the same question as Oterox above me – would love to add ‘next’ and ‘prev’ buttons, could someone help us out? Thanks !

  26. ohhh, i just realized the navigation doesn’t work with internet explorer 🙁

  27. Thank you so so so much! This is exactly what I’ve been looking for! I’ve spent hours trying to find this exact demo. Thanks again!

    • wow…fantastic work you guys do. how were able implement the navigation arrows by any chance? help on this would be greatly appreciated.

  28. May I know how to change URL, instead of “?chapter=introduction” or “?chapter=1” , can I change to “?page=introduction” or “?page=contact”?

    or how to remove the “?chapter=(sumting)” at the back???

    need urgent solutions.

    Thanks a lot.

    • Everyone,

      I just found out that this tutorial web layout does not has a Print Media view, I mean when try to print the site with browser, it does not work. anyone can help?!

  29. This tutorial is super! Thanks a lot ML. I have one question I try to put a link <a href="…. in one chapter but it isn't working. Can you help? Thanks.

    • Open “jquery.page.js” and locate :

      _initArticleEvents = function() {
      .
      .
      .
      .
      return false;
      };

      just comment the line that contains : return false;

      It’s means that it should be:

      //return false;

  30. Hi, i trying to integrate this plugin in wordpress, but the javascript ( i think head.min.js ) doesn’ work, some one could help me?