This allows a timer or progress bar to be included on a slick slider. It was originally created from this codepen, and converted from coffeescript and LESS into JS and SASS.
How To - Basic horizontal timer
Script
sliders.js
In the ELSE section, insert a script call to the plugin that will initialize the timer. Make sure it goes ABOVE the slider that it’s for. It’s targeting the homepage first, then within the homepage, finding the hero slider (or change to whatever slider is needed), and then targeting the class for the slider timer div.
if($('body').hasClass('home')) {
new SliderProgressBar('.hero__slider', '.slider-timer');
}
Plugins folder
Include the file slickprogress.js in the plugins folder. Make sure to also include in scripts.mustache so that it works locally.
// Note that since we want variables in each instance, they are commented out here and *only* defined
// within a method (in this case, the constructor). Otherwise, the variable would be a static class
// variable which is especially bad for the sliderSelector and other slider-specific vars!
// store the DOM elements here
// sliderSelector: ''
// progressBarSelector: ''
// amount of total time to spend on each slide. Updated every time the slide changes (in case it's different)
// autoplaySpeed: 0
// use these to keep track of current time spent on slide (set in startInterval)
// slideStartTime: 0
// currentSlideTimeTotal: 0
// lastSlideTime: 0
// keep track of the intervalId so we can cancel it
// currentIntervalId: -1
// how often to update the progress bar
// iteratorInterval: 100
// isPaused: false
// You must construct an instance of this class (and, hence, run this function)
// BEFORE you initialize the slick slider!
this.SliderProgressBar = (function() {
function SliderProgressBar(sliderSelector, progressBarSelector) {
var _self;
this.autoplaySpeed = 0;
this.currentSlideTime = 0;
this.currentIntervalId = -1;
this.iteratorInterval = 100;
this.sliderSelector = sliderSelector;
this.progressBarSelector = progressBarSelector;
this.slideStartTime = 0;
this.currentSlideTimeTotal = 0;
this.lastSlideTime = 0;
_self = this;
if ($(this.sliderSelector).length === 0) {
console.log("Error: slider selector " + this.sliderSelector + " given to SliderProgressBar cannot be found");
return;
}
if ($(this.sliderSelector).length === 0) {
console.log("Error: progressBar selector " + this.progressBarSelector + " given to SliderProgressBar cannot be found");
return;
}
$(this.sliderSelector).on('init', (function(_this) {
return function(event, slick) {
_this.autoplaySpeed = parseInt(slick.options.autoplaySpeed);
// if the slider pauses on hover, we need to basically reset the interval (that's what slick does)
if (slick.options.pauseOnHover) {
$(_this.sliderSelector).hover(function() {
_this.stopInterval(true);
return _this.isPaused = true;
}, function() {
_this.hoverTime = Date.now() - _this.startHoverTime;
_this.startInterval(true);
return _this.isPaused = false;
});
}
return _this.startInterval();
};
})(this));
$(this.sliderSelector).on('beforeChange', (function(_this) {
return function(event, slick, currentSlide, nextSlide) {
window.clearInterval(_this.currentIntervalId); // prevent progress bar from going past 100% - due to transitions between the before and after change
$(_this.progressBarSelector).removeClass('is-sliding');
$(_this.progressBarSelector).addClass('is-transitioning');
return $(_this.progressBarSelector).css('width', '0%'); // hide progress bar
};
})(this));
$(this.sliderSelector).on('afterChange', (function(_this) {
return function(event, slick, currentSlide) {
$(_this.progressBarSelector).removeClass('is-transitioning');
_this.autoplaySpeed = parseInt(slick.options.autoplaySpeed);
return _this.startInterval();
};
})(this));
}
// Start the progress bar growth iterator function. This is called
// every time the slide changes.
SliderProgressBar.prototype.startInterval = function(restartInterval) {
var _self;
if (restartInterval == null) {
restartInterval = true;
}
this.stopInterval(restartInterval);
$(this.progressBarSelector).addClass('is-sliding');
_self = this;
return this.currentIntervalId = window.setInterval(function() {
return _self.progressBarIterator(_self);
}, this.iteratorInterval);
};
SliderProgressBar.prototype.stopInterval = function(restartInterval) {
if (restartInterval == null) {
restartInterval = true;
}
if (restartInterval) {
this.slideStartTime = Date.now();
this.currentSlideTimeTotal = 0;
this.lastSlideTime = 0;
} else {
this.lastSlideTime = Date.now();
}
return window.clearInterval(this.currentIntervalId);
};
// Grow the progress bar based on how much time has been spent on the current slide.
// Note that this is called within setInterval, so the context of "this" has changed!
SliderProgressBar.prototype.progressBarIterator = function(progressBarInstance) {
var currentSlideTime, percentElapsed;
if (progressBarInstance.lastSlideTime === 0) {
progressBarInstance.currentSlideTimeTotal = Date.now() - progressBarInstance.slideStartTime;
} else {
progressBarInstance.currentSlideTimeTotal += Date.now() - progressBarInstance.lastSlideTime;
}
currentSlideTime = progressBarInstance.currentSlideTimeTotal;
progressBarInstance.lastSlideTime = Date.now();
percentElapsed = currentSlideTime / progressBarInstance.autoplaySpeed;
percentElapsed = Math.max(0, Math.min(100, percentElapsed * 100)); // constrain number between 0 and 100
percentElapsed = Math.floor(percentElapsed * 100) / 100; // round number to two decimal places
percentElapsed = percentElapsed.toString() + '%';
return $(progressBarInstance.progressBarSelector).css('width', percentElapsed);
};
return SliderProgressBar;
})();
Styling
Inside the SCSS file that corresponds to where your timed slider is (home.scss, subpages.scss, banners.scss, for example), put in the CSS for the timer. You may need to change the colors around. This example has it nested within the .hero class.
.slider-timer {
position: absolute;
left: 0;
right: 0;
top: 0;
width: 100%;
height: rem-calc(3px);
background: #fff;
transition: width 0.09s linear, opacity 0.15s linear;
opacity: 0;
&.is-transitioning {
width: 0% !important;
}
&.is-sliding {
opacity: 1;
background: $green;
}
}
}
HTML
Inside the mustache file for wherever you have your timed slider (home.mustache, slider-hero.mustache, banner-image.mustache, for example), don’t forget to put in the div for the timer itself. In this example, it’s just a div inside the larger .hero div.
<div class="hero">
<div class="hero__slider">
...
</div>
<div class="slider-timer" aria-hidden="true"><span class="sr-only">Timer until the slide switches.</span></div>
</div>
How To - Basic, but make it vertical
There are a few edits you can make so that the slider is vertical instead of horizontal. Rotating it with CSS ends up causing more problems than it solves, so this is an easy way to do it without overcomplicating.
You will need to follow the same steps as listed above, with some changes noted below.
Script - plugins folder
This is an edited version of the slickprogress.js file, which you’ll probably want to save with a filename of slickprogress-vertical.js or something instead, to prevent confusion (and in case your site has both horizontal and vertical sliders).
As of right now, no one has tried making a slider that flips from horizontal to vertical when responding (or vice-versa).
// Note that since we want variables in each instance, they are commented out here and *only* defined
// within a method (in this case, the constructor). Otherwise, the variable would be a static class
// variable which is especially bad for the sliderSelector and other slider-specific vars!
// store the DOM elements here
// sliderSelector: ''
// progressBarSelector: ''
// amount of total time to spend on each slide. Updated every time the slide changes (in case it's different)
// autoplaySpeed: 0
// use these to keep track of current time spent on slide (set in startInterval)
// slideStartTime: 0
// currentSlideTimeTotal: 0
// lastSlideTime: 0
// keep track of the intervalId so we can cancel it
// currentIntervalId: -1
// how often to update the progress bar
// iteratorInterval: 100
// isPaused: false
// You must construct an instance of this class (and, hence, run this function)
// BEFORE you initialize the slick slider!
this.SliderProgressBar = (function() {
function SliderProgressBar(sliderSelector, progressBarSelector) {
var _self;
this.autoplaySpeed = 0;
this.currentSlideTime = 0;
this.currentIntervalId = -1;
this.iteratorInterval = 100;
this.sliderSelector = sliderSelector;
this.progressBarSelector = progressBarSelector;
this.slideStartTime = 0;
this.currentSlideTimeTotal = 0;
this.lastSlideTime = 0;
_self = this;
if ($(this.sliderSelector).length === 0) {
console.log("Error: slider selector " + this.sliderSelector + " given to SliderProgressBar cannot be found");
return;
}
if ($(this.sliderSelector).length === 0) {
console.log("Error: progressBar selector " + this.progressBarSelector + " given to SliderProgressBar cannot be found");
return;
}
$(this.sliderSelector).on('init', (function(_this) {
return function(event, slick) {
_this.autoplaySpeed = parseInt(slick.options.autoplaySpeed);
// if the slider pauses on hover, we need to basically reset the interval (that's what slick does)
if (slick.options.pauseOnHover) {
$(_this.sliderSelector).hover(function() {
_this.stopInterval(true);
return _this.isPaused = true;
}, function() {
_this.hoverTime = Date.now() - _this.startHoverTime;
_this.startInterval(true);
return _this.isPaused = false;
});
}
return _this.startInterval();
};
})(this));
$(this.sliderSelector).on('beforeChange', (function(_this) {
return function(event, slick, currentSlide, nextSlide) {
window.clearInterval(_this.currentIntervalId); // prevent progress bar from going past 100% - due to transitions between the before and after change
$(_this.progressBarSelector).removeClass('is-sliding');
$(_this.progressBarSelector).addClass('is-transitioning');
return $(_this.progressBarSelector).css('height', '0%'); // hide progress bar
};
})(this));
$(this.sliderSelector).on('afterChange', (function(_this) {
return function(event, slick, currentSlide) {
$(_this.progressBarSelector).removeClass('is-transitioning');
_this.autoplaySpeed = parseInt(slick.options.autoplaySpeed);
return _this.startInterval();
};
})(this));
}
// Start the progress bar growth iterator function. This is called
// every time the slide changes.
SliderProgressBar.prototype.startInterval = function(restartInterval) {
var _self;
if (restartInterval == null) {
restartInterval = true;
}
this.stopInterval(restartInterval);
$(this.progressBarSelector).addClass('is-sliding');
_self = this;
return this.currentIntervalId = window.setInterval(function() {
return _self.progressBarIterator(_self);
}, this.iteratorInterval);
};
SliderProgressBar.prototype.stopInterval = function(restartInterval) {
if (restartInterval == null) {
restartInterval = true;
}
if (restartInterval) {
this.slideStartTime = Date.now();
this.currentSlideTimeTotal = 0;
this.lastSlideTime = 0;
} else {
this.lastSlideTime = Date.now();
}
return window.clearInterval(this.currentIntervalId);
};
// Grow the progress bar based on how much time has been spent on the current slide.
// Note that this is called within setInterval, so the context of "this" has changed!
SliderProgressBar.prototype.progressBarIterator = function(progressBarInstance) {
var currentSlideTime, percentElapsed;
if (progressBarInstance.lastSlideTime === 0) {
progressBarInstance.currentSlideTimeTotal = Date.now() - progressBarInstance.slideStartTime;
} else {
progressBarInstance.currentSlideTimeTotal += Date.now() - progressBarInstance.lastSlideTime;
}
currentSlideTime = progressBarInstance.currentSlideTimeTotal;
progressBarInstance.lastSlideTime = Date.now();
percentElapsed = currentSlideTime / progressBarInstance.autoplaySpeed;
percentElapsed = Math.max(0, Math.min(100, percentElapsed * 100)); // constrain number between 0 and 100
percentElapsed = Math.floor(percentElapsed * 100) / 100; // round number to two decimal places
percentElapsed = percentElapsed.toString() + '%';
return $(progressBarInstance.progressBarSelector).css('height', percentElapsed);
};
return SliderProgressBar;
})();
Styling
This is similar to the above setup as well, but instead of having the width be 100% and height be a set px size, you just reverse those values.
How To - Multiple segments
The design will sometimes call for an individual timer per slide, within the “dots” of the slider. This is doable but should come with a high cost as it’s fairly involved.
The one caveat with this setup is that the slider using this method cannot have dots as navigation, because the timer setup overtakes and restyles the existing dots. Users will be able to click the timer segment to jump to that slide. If additional navigation between slides is requested, it will have to be arrows. You could also experiment with creating a connected slider that has its own dots separately of the timed slider, however this hasn’t been tested yet.
You will want to follow the steps above to install the plugin (either horizontal or vertical version to match your design), and the same basic styling/HTML setup will also be used. Here are the edits/updates you will have to make for this setup.
Scripts - sliders.js
Timer
When you are setting up your slider timer above the slider slick setup, instead of targeting a div, target the “dots” of the slider, wherever you decide to place them. For example:
if (!inCms) {
// testimonial vertical slider timer
if ($('.testimonialVertical__slider-wrapper').length) {
new SliderProgressBar('.testimonialVertical__slider', '.testimonialVertical__slider-timerdots .slick-dots li.slick-active button span.time');
}
}
Slider
Then, for the slider setup itself:
$('.testimonialVertical__slider').slick({
accessibility: !inCms,
dots: true,
appendDots: '.testimonialVertical__slider-timerdots',
arrows: false,
autoplay: !inCms,
autoplaySpeed: 8000,
focusOnSelect: true,
cssEase: 'linear',
draggable: !inCms,
fade: false,
pauseOnHover: false,
pauseOnFocus: false,
slidesToShow: 1,
infinite: !inCms,
swipe: !inCms // prevents slide advancing when trying to select text in CMS
});
Nothing in particular is set up differently about the slider in this case, but you need to make sure that it has dots. They can be appended to the slider or in the default place, anywhere is fine as long as they are set to true.
Autoplay can be set to true or false, whichever is needed.
Timer segment addition
The last step is to add a div within each of the slider dots that will become the “sliding” timer section. In the above example, it’s called span.time.
You will want to add this so that it only happens outside the CMS, to prevent confusion.
if (!inCms) {
// testimonial vertical slider timer
$('.testimonialVertical__slider-timerdots .slick-dots li button').each(function(index) {
var progress = "<span class='time'></span>";
$(this).html(progress);
});
}
Styles - CSS
The only styling difference you will need to make from the above setup is to include, at minimum, a background color on the span.time piece, which is the part that will slowly fill up as the time flows.
HTML
The HTML is set up as normal, with the same caveat that you must have dots somewhere, so make sure there’s a separate div for that to be placed in if that’s what the design calls for.
Example Sites
- Kinderhook Bank (Homepage hero slider)
- 1st United Services Credit Union (Homepage hero slider)
- Credit Union of Texas (Vertical multi-segment timer)
Tags: slick, slider, progress bar, timer, BS4, BS3, javascript, js, jquery, html, scss, sass