Responsive image/html slider scroller

13th January 2014

It seems like almost every website you visit now has a sliding image banner at the top of the website, underneath the menu. So here is an image/HTML slider that works at varying resolutions and on mobile devices.

Live Demo

HTML

The code works by selecting elements from a block of html, this allows you to customise your html fully and then call the method to make it work like a slider. The code that you should base your content on is as follows:

<div class="slider" id="demo">
<div class="slider_slides">
<div class="boxes">
<div class="box" style="background-color: red;"><a href="javascript:alert('Box 1');">Box 1</a></div><!--
--><div class="box" style="background-color: green;"><a href="javascript:alert('Box 1');">Box 2</a></div><!--
--><div class="box" style="background-color: blue;"><a href="javascript:alert('Box 1');">Box 3</a></div>
</div>
</div>

<div class="prev">◀</div>
<div class="next">▶</div>

<div class="indicators"></div>
</div>

You will notice that I have used some unicode characters to represent some graphics on the prev/next buttons. You may also notice that I have some Javascript written on the hyperlinks onclick method, you can customise these however you like/ Typical usage would be to remove the box style, change the hyperlink href and change the content so that an image sits within each hyperlink. You may also choose to customise the prev and next buttons with background images instead of using the text. You should keep the html comments between each box element as this removes whitespace characters between the elements (as display inline-block is used).

CSS

You may also require adding 'position:relative' to some elements as this is included in the clear stylesheet on this site.

.slider .slider_slides {
width: 100%;
height: 100%;
overflow: auto;
}

.js_on .slider .slider_slides {
overflow: hidden;
}

.slider .controls {
display: none;
}

.js_on .slider .controls {
display: block;
}
.slider .boxes { display: inline-block; white-space: nowrap; height: 100%; width: 100%; vertical-align: top; }
.slider .box {
display: inline-block;
width: 100%;
vertical-align: top;
white-space: normal;
height: 100%;
}

.slider .box a {
width: 100%;
height: 100%;
color: white;
text-decoration: none;
display: block;
text-align: center;
}

.slider .indicators {
padding: 2px;
line-height: 6px;
}

.slider .indicators .indicator {
width: 6px;
height: 6px;
border-radius: 6px;
border: 1px solid black;
display: inline-block;
margin: 2px;
cursor: pointer;
}

.slider .indicators .indicator.current_indicator {
background-color: black;
border: 1px solid transparent;
}

.slider .prev, .slider .next {
position: absolute;
top: 50%;
margin-top: -0.6em;
color: white;
cursor: pointer;
padding: 4px;
display: block;
}

.slider .next {
right: 0px;
}

.slider .prev {
left: 0px;
}

.slider .indicators {
text-align: center;
position: absolute;
bottom: 0px;
width: 100%;
left: 0px;
}

.slider .disabled, .slider .indicators.single_indicator {
display: none;
}

If you want your slider to work in Internet Explorer 7 and lower then you will also want to include the following an an IE specific CSS file (using IE conditional comments):

.box, .indicator {
display: inline;
}

Javascript

Note: requires jQuery and the easy drag method:

$("html").addClass("js_on");

function makeContentSlider($dom, options) {
	options = options || {};
	
 	var defaults = {
 		moveSpeed: 1000, //pixels per second
minSlideSpeed: 500, //pixels per second
slideThreshold: 0.3, //the amount of the width the user must cross to move into the next/prev block
autoslide: false,
autoslideWait: 2000, //the interval to wait (in ms) between each slide
autoslideRepeat: true,
sliderSelector: ".slider_slides",
wrapSelector: ".boxes",
listSelector: ".box",
nextSelector: ".next",
prevSelector: ".prev",
indicatorSelector: ".indicators",
currentClass: "current_box",
nextClass: "next_current_box",
indicatorClass: "indicator",
disabledClass: "disabled",
currentIndicatorClass: "current_indicator"
};
for (var key in defaults) { if (defaults.hasOwnProperty(key) && !options.hasOwnProperty(key)) { options[key] = defaults[key]; } } //TODO: to support cyclic sliders, we clone the first, append it to the list, alter the indicator and make it snap back to the first seamlessly when the last is selected
$($dom).each(function() {
//let the main screen be dragged and snap to the nearest box (animate to the current box, which changes if we slide more than a threshold over)
var userSpeed = 0, startTime = 0, startScroll;

var $main = $(this);
$main.addClass("slider");

var $slides = $main.find(options.sliderSelector), $boxes = $main.find(options.listSelector), $next = $main.find(options.nextSelector), $prev = $main.find(options.prevSelector), $indicator = $main.find(options.indicatorSelector);
var $firstBox = $boxes.first();

//set the first box active if one is not already specified
if (!$boxes.filter("." + options.currentClass).length) {
$firstBox.addClass(options.currentClass);
}

//for ie7, we must set a width in the box container and then reset the box widths so they appear to be 100% (for inline-block no-wrap to work)
//use percentage widths so that we do not need to update on resize
if ($boxes.length) {
$(options.wrapSelector).css({width: ($boxes.length*100) + "%"});
$boxes.css({width: (100/$boxes.length) + "%"});
}

drag($slides, function(e) {
startScroll = $slides.scrollLeft();
startTime = (new Date()).getTime();
}, function(e, delta) {
var newScrollLeft = startScroll - delta.x;
$slides.scrollLeft(newScrollLeft);

//register the snap to the current box if we have not already move it once
var $currentElem = $boxes.filter("." + options.currentClass);
var currentScrollLeft = $currentElem.position().left - $firstBox.position().left;
var threholdWidth = $currentElem.width()*options.slideThreshold;

if (-delta.x > threholdWidth && $currentElem.next().length) {
$currentElem.prev().removeClass(options.nextClass);
$currentElem.next().addClass(options.nextClass);
} else if (delta.x > threholdWidth && $currentElem.prev().length) {
$currentElem.next().removeClass(options.nextClass);
$currentElem.prev().addClass(options.nextClass);
} else {
$currentElem.next().removeClass(options.nextClass);
$currentElem.prev().removeClass(options.nextClass);
}
}, function(e, delta) {
//if we have a new box to snap to then alter the classes and do the magic
var $nextCurrent = $boxes.filter("." + options.nextClass);
if ($nextCurrent.length) {
$boxes.filter("." + options.currentClass).removeClass(options.currentClass);
$nextCurrent.addClass(options.currentClass).removeClass(options.nextClass);
}

//we attempt to match the average speed that the user moved with to make the transition look even more smooth
userSpeed = Math.abs($slides.scrollLeft() - startScroll) / ((new Date()).getTime()-startTime) * 1000;

animateToCurrent(Math.max(options.minSlideSpeed, userSpeed));
});

animateToCurrent(0);

if (options.autoslide) {
resetAutoslideTimeout();
}

//EVENTS

//as we can have percentage widths for the main box, we should re-evaluate the scroll position when the window changes size
$(window).on("resize", function() {
//alter the scroll left so that our current box is still lined up in the view
var targetScrollLeft = $boxes.filter("." + options.currentClass).position().left - $firstBox.position().left;
$slides.scrollLeft(targetScrollLeft);
});

//let a click animate the transition into the next page
$prev.on("click", function() {
var $currentBox = $boxes.filter("." + options.currentClass);
var $prevBox = $currentBox.prev();

if ($prevBox.length) {
$prevBox.addClass(options.currentClass);
$currentBox.removeClass(options.currentClass);
animateToCurrent(options.moveSpeed);
}
});

$next.on("click", function() {
var $currentBox = $boxes.filter("." + options.currentClass);
var $nextBox = $currentBox.next();

if ($nextBox.length) {
$nextBox.addClass(options.currentClass);
$currentBox.removeClass(options.currentClass);
animateToCurrent(options.moveSpeed);
}
});

//METHODS

function animateToCurrent(animSpeed, callback) {
//smooth slide to the current box
var targetScrollLeft = $boxes.filter("." + options.currentClass).position().left - $firstBox.position().left;

//we can animate with a speed rather than time so the distance is covered linearly
var animTime = (Math.abs(targetScrollLeft - $slides.scrollLeft()) / animSpeed) * 1000;
$slides.stop(true, false).animate({scrollLeft: targetScrollLeft}, animTime, function() {
if (callback) callback();
});

//update the next/prev buttons
updateControls();

//update the visual indicator that goes with this list slider
updateIndicators();
}

function updateControls() {
var $currentBox = $boxes.filter("." + options.currentClass);

if ($currentBox.next().length) {
$next.removeClass(options.disabledClass);
} else {
$next.addClass(options.disabledClass);
}

if ($currentBox.prev().length) {
$prev.removeClass(options.disabledClass);
} else {
$prev.addClass(options.disabledClass);
}
}

function setCurrent(i, callback) {
$boxes.filter("." + options.currentClass).removeClass(options.currentClass);
$boxes.eq(i).addClass(options.currentClass);
animateToCurrent(options.moveSpeed, callback);
}

function updateIndicators() {
//make the same number of items as there are boxes
var $indicators = $indicator.find("." + options.indicatorClass);

if ($boxes.length > $indicators.length) {
for (var i=0; i<$boxes.length - $indicators.length; i++) {
var $newIndicator = $('<div class="' + options.indicatorClass + '"></div>');

$newIndicator.on("click", function() {
//let the user jump to an item by clicking on an indicator
setCurrent($(this).index("." + options.indicatorClass));
});

$indicator.append($newIndicator);
}

$indicators = $indicator.find("." + options.indicatorClass);
}

if ($indicators.length<2) {
$indicator.addClass("single_indicator");
} else {
$indicator.removeClass("single_indicator");
}

//get the index of the current item
var index = $boxes.filter("." + options.currentClass).index();
$indicators.filter("." + options.currentIndicatorClass).removeClass(options.currentIndicatorClass);
$indicators.eq(index).addClass(options.currentIndicatorClass);
}

//scroll between the boxes in an automated manner, built to work in a nicer timed manner with requestanimationframe in mind
var autoslideTimeout = null;
function autoslideNext() {
var $currBox = $boxes.filter("." + options.currentClass);
var nextId = $currBox.index()+1;

if (!$currBox.next().length) {
if (!options.autoslideRepeat) {
stopAutoslideTimeout();
return;
}

nextId = 0;
}

setCurrent(nextId, function() {
resetAutoslideTimeout();
});
}

function stopAutoslideTimeout() {
if (autoslideTimeout) {
clearTimeout(autoslideTimeout);
autoslideTimeout = null;
}
}

function resetAutoslideTimeout() {
stopAutoslideTimeout();
autoslideTimeout = setTimeout(autoslideNext, options.autoslideWait);
}
});
}

Usage

Including the CSS and Javascript above, you can now transform your content into a slider by calling the Javasscript as follows:

$(document).ready(function() {
makeContentSlider($("#demo"), {
moveSpeed: 1000,
autoSlide: false
});
});

Comments

Would it be at all possible to add tooltips to the individual indicators?
Cameron - 5th Dec 2014 19:15

Make a comment

Contribute to this article and have your say.