
There are hundreds of scripts and plug-ins online for content sliders using JavaScript. Most of those scripts are for image-heavy content that is geared to making a visual impression. This one is a little different. The idea for this tutorial comes from a content switcher that, at the time of writing, appears on the homepage for globalnews.ca (a Canadian news network). In this tutorial, we will be recreating that widget.
This content switcher is text-heavy and is focused on displaying the latest top headlines for that day. It has a vertical list of headlines, along with a lava lamp-like effect where a white bar highlights the currently visible news story. In addition to allowing the user to click to view any headline, the highlight bar animates automatically, cycling through all the available items.
The version on globalnews.ca uses JavaScript for the white bar animation, and the widget isn’t responsive. We’ll improve on that by making ours responsive and use CSS3 transitions for the lava lamp effect.
If you’d like to fiddle with the full code for this widget online, you can view it at this JS Bin: jsbin.com/utaneq/48/edit, or use the files with this tutorial.
DOWNLOAD TUTORIAL FILES
The headlines list
The first thing we’ll do is establish some clean, semantic markup to hold the content that our script will manipulate. We’ll start with a simple unordered list, with the last item being a special “highlight item” that we’ll use to create the lava lamp effect. We’ll also have a default “selected” list item, and we’ll throw in a couple of phony news headlines.
001 <ul> 002 <li>100 red bicycles stolen from local bike store</li> 003 <li>New leash laws in effect for floppy-eared dogs</li> 004 <li>Insider: Can palm trees be saved?</li> 005 <li>Fresh recipes to titillate the taste buds</li> 006 <li>Truck inspections under way for the metropolitan area</li> 007 <li>Are the beaches safe for swimming this year?</li> 008 <li></li> 009 </ul>
The news preview
The right-side of our widget will hold the individual news items. They’ll each have an image, some text, and some css classes for styling and scripting purposes. Our example will consist of six news items. Here’s how each item will be marked up:
001 <div> 002 <img src="images/news1.jpg" width="220" height="143" alt="100 red bicycles stolen from local bike store"> 003 <p><a href="#">100 red bicycles stolen from local bike store</a></p> 004 005 <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</p> 006 </div>
Fixed box model
To ensure width calculations are more intuitive, we’ll set the box-sizing property, along with some universal margin and padding resets (which will usually be done in a CSS reset, so you likely won’t need this):
001 * {
002 margin: 0;
003 padding: 0;
004 -webkit-box-sizing: border-box;
005 -moz-box-sizing: border-box;
006 box-sizing: border-box;
007 }
Adding a transition
To avoid using jQuery for the highlight bar animation, we’ll add some CSS3 transitions to the headline list items. We also add some innocuous transforms to help the animation look smoother. The z-index value is important to help the white highlight bar (which has a lower z-index) appear below all the list items in the stack.
001 .news-headlines li {
002 padding: 5px 20px 5px 24px;
003 margin-bottom: 15px;
004 position: relative;
005 z-index: 20;
006 -webkit-transition: all .75s
ease-out;
007 -moz-transition: all .75s ease-
out;
008 -o-transition: all .75s ease-out;
009 transition: all .75s ease-out;
010 color: #336699;
011 -webkit-transform: translateZ(0);
012 -moz-transform: translateZ(0);
013 -o-transform: translateZ(0);
014 transform: translateZ(0);
015 }
Pseudo-element bullets
To create the list item bullets, we’ll use pseudo-elements. This helps us avoid adding an unnecessary image. The pseudo-element is a small red box set to display: inline-block and margin settings help position it correctly. We also ensure that the cursor changes to a hand when the list item is hovered (since we’re not using <a> elements).
001 .news-headlines li:before {
002 content: "";
003 display: inline-block;
004 width: 5px;
005 height: 5px;
006 background: red;
007 vertical-align: middle;
008 margin-left: -12px;
009 margin-right: 7px;
010 }
011
012 .news-headlines li:hover
013 {
014 cursor: pointer;
015 }
Stacked news items
On the right-side of the widget we’ll display content associated with the selected news item. Each of these items will be absolutely positioned to help stack them so only one is visible at a time. A separate class will be used to bring the selected news item to the top of the stack with z-index:
001 .news-content {
002 position: absolute;
003 background: white;
004 z-index: 10;
005 padding: 10px;
006 top: 0;
007 left: 0;
008 }
009
010 .top-content {
011 z-index: 50;
012 }
Negative margin
We want the white highlight bar to appear as though it’s flowing right into the content area on the right-side of the widget. To do this, we’ll apply a negative margin to the content section, along with a z-index value that’s lower than that of the highlight bar itself.
001 .news-preview {
002 float: left;
003 border: solid 1px #999;
004 width: 51%;
005 background: white;
006 position: relative;
007 z-index: 5;
008 margin-left: -1px;
009 min-height: 304px;
010 position: relative;
011 }
Responsive images
This isn’t a fully-fledged responsive images solution that uses different image resolutions, but instead we’ll just add some simple CSS to help keep the image at a maximum size while reducing its size for smaller devices. The key parts of this code block are the width, height, max-width, and max-height properties:
001 .news-preview img
002 {
003 display: block;
004 border: solid 1px #999;
005 width: 100%;
006 height: auto;
007 max-width: 220px;
008 max-height: 143px;
009 margin: 0 auto 5px auto;
010 }
The highlight bar
Finally, the last list item in our headlines list is an empty element that’s used as the highlight bar, to indicate the current selected item. Semantic purists might want to inject this element with JavaScript to keep the HTML clean. We’ll apply the following default styles to help position it:
001 .news-headlines .highlight {
002 width: 100%;
003 background: white;
004 border-top: solid 1px #999;
005 border-left: solid 1px #999;
006 border-bottom: solid 1px #999;
007 position: absolute;
008 top: 0;
009 left: 0;
010 z-index: 10;
011 }
Defining our variables
The first thing our script will do is cache some objects that we’ll refer to multiple times in our code. Then we’ll initiate some utility variables, some of which are set initially to null because they depend on values that aren’t constant. The vPadding and vMargin variables are used to calculate the size of the headline items so that it can be fully controlled with CSS and thus will be more maintainable.
001 var hl = $('.highlight'),
002 newsList = $('.news-headlines'),
003 newsListItems = $('.news-headlines li'),
004 firstNewsItem = $('.news-headlines li:nth-child(1)'),
005 newsPreview = $('.news-preview'),
006 elCount = $('.news-headlines').children(':not(.highlight)').index(),
007 vPadding = (parseInt(firstNewsItem.css('padding-top').replace('px', ''), 10)) + (parseInt(firstNewsItem.css('padding-bottom').
replace('px', ''), 10)),
008 vMargin = (parseInt(firstNewsItem.css('margin-top').replace('px', ''), 10)) + (parseInt(firstNewsItem.css('margin-bottom').
replace('px', ''), 10)),
009 myTimer = null,
010 siblings = null,
011 totalHeight = null,
012 indexEl = 1,
013 i = null;
Equal height columns
Our widget is divided into two columns. To ensure the sides are relatively equal, even after the user resizes the browser window, we’ll create a function that will equal out the columns depending on which side is bigger. The function uses a min-height value set in the CSS, again keeping this data outside the script itself.
001 function doEqualHeight() {
002
003 if (newsPreview.height() < newsList.height()) {
004 newsPreview.height(newsList.height());
005 } else if ((newsList.height() < newsPreview.height()) && (newsList.height()
> parseInt(newsPreview.css('min-height').replace('px', ''), 10))) {
006 newsPreview.height(newsList.height());
007 }
008
009 }
Auto change
We want the widget to cycle through the headlines automatically. For this, we’ll create a function that uses JavaScript’s setInterval() method. This function will trigger a click event every ten seconds. Each time the click event is triggered, the selected news item changes. If the current selected element is the last element, it will cycle back to item one.
001 function doTimedSwitch() {
002
003 myTimer = setInterval(function () {
004 if (($('.selected').prev().index() + 1) === elCount) {
005 firstNewsItem.trigger('click');
006 } else {
007 $('.selected').next(':not(.
highlight)').trigger('click');
008 }
009 }, 10000);
010
011 }
The click function
Next, let’s begin our primary function, the doClickItem() function. We’ll start by adding the click event to our news items. This event gets triggered either manually by the user, or automatically by the script, using jQuery’s trigger() method. We remove any instances of the selected class, and add the selected class to the currently clicked item.
001 function doClickItem() {
002
003 newsListItems.on('click', function () {
004
005 newsListItems.removeClass('selected');
006 $(this).addClass('selected');
007
008 // further code is added here in
subsequent steps…
009
010 });
011
012 }
Calculate highlight position
Lets add to our doClickItem() function. We want to find out how many items appear before the current highlighted item. Once we know that (which we get using jQuery’s prevAll()), we set the totalHeight variable to zero, and then loop through the items. We add up the total height of all the items, plus any vertical padding and margins.
001 siblings = $(this).prevAll();
002 totalHeight = 0;
003
004 for (i = 0; i < siblings.length; i += 1) {
005 totalHeight += $(siblings[i]).height();
006 totalHeight += vPadding;
007 totalHeight += vMargin;
008 }
Move the highlight
Using the totalHeight calculation from the previous step, we use jQuery’s css() method to set the top and height values of the highlight element (cached in our variables as hl). Because we’ve set a CSS3 transition on the list items using the all keyword (which means transition all properties), the top and height values will animate when changed with JavaScript.
001 hl.css({
002 top: totalHeight,
003 height: $(this).height() + vPadding
004 });
Show news item
Further adding to our doClickItem() function, we find out the index of the current highlighted element using jQuery’s index() method, adding one to the result to account for zero-based indexing. Once we know that, we use that number to add the top-content class to the corresponding .news-content element, thus putting it at the top of the stack so it’s visible.
001 indexEl = $(this).index() + 1;
002
003 $('.news-content:nth-child(' + indexEl + ')').siblings().removeClass('top-content');
004 $('.news-content:nth-child(' + indexEl + ')').addClass('top-content');
Finishing up doClickItem()
Finally, to wrap up our doClickItem() function, we use JavaScript’s clearInterval() method to stop the timer, and then we start it again by calling the doTimedSwitch
() function we created earlier. We stop the timer to ensure ten seconds is spent on the newly clicked item.
001 clearInterval(myTimer); 002 doTimedSwitch();
Window resizing
To ensure that our widget looks decent if the window is resized by the user, we’ll set up a function that uses jQuery’s $(window).resize() method. This method will execute an anonymous function each time the window is resized. The first thing we’ll do in that function is stop the timer.
001 function doWindowResize() {
002 $(window).resize(function () {
003 clearInterval(myTimer);
004 });
005 }
Correct the heights
The doWindowResize() function continues when we use JavaScript’s setTimeout() method to trigger a delayed click event on the selected item (to account for time spent resizing the window). This ensures that the white highlight bar is the correct height. Finally, we ensure the two sides of the widget are equal in height by calling our doEqualHeight() function.
001 function doWindowResize() {
002 $(window).resize(function () {
003 clearInterval(myTimer);
004 setTimeout(function () {
005 $('.selected').trigger('click');
006 }, 1000 );
007 doEqualHeight();
008 });
009 }
Initiate the script
Finally, now that we have defined all of our variables and functions, we execute all three functions and then trigger a click event on the currently selected element to get things started.
001 doClickItem();
002 doWindowResize();
003 setTimeout(function () {
004 doEqualHeight();
005 }, 500);
006 $('.selected').trigger('click');