Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 155 additions & 17 deletions js/slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
indicators: true,
height: 400,
duration: 500,
interval: 6000
interval: 6000,
pauseOnFocus: true,
pauseOnHover: true,
indicatorLabelFunc: null // Function which will generate a label for the indicators (ARIA)
};

/**
Expand All @@ -31,9 +34,19 @@
* @prop {Number} [height=400] - height of slider
* @prop {Number} [duration=500] - Length in ms of slide transition
* @prop {Number} [interval=6000] - Length in ms of slide interval
* @prop {Boolean} [pauseOnFocus=true] - Pauses transition when slider receives keyboard focus
* @prop {Boolean} [pauseOnHover=true] - Pauses transition while mouse hovers the slider
* @prop {Function} [indicatorLabelFunc=null] - Function used to generate ARIA label to indicators (for accessibility purposes).
*/
this.options = $.extend({}, Slider.defaults, options);

// init props
this.interval = null;
this.eventPause = false;
this._hovered = false;
this._focused = false;
this._focusCurrent = false;

// setup
this.$slider = this.$el.find('.slides');
this.$slides = this.$slider.children('li');
Expand All @@ -49,6 +62,13 @@

this._setSliderHeight();

// Sets element id if it does not have one
if (this.$slider.attr('id')) this._sliderId = this.$slider.attr('id');
else {
this._sliderId = 'slider-' + M.guid();
this.$slider.attr('id', this._sliderId);
}

// Set initial positions of captions
this.$slides.find('.caption').each((el) => {
this._animateCaptionIn(el, 0);
Expand All @@ -63,12 +83,18 @@
$(el).attr('src', placeholderBase64);
}
});
this.$slides.each((el) => {
// Sets slide as focusable by code
if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', -1);
// Removes initial visibility from "inactive" slides
el.style.visibility = 'hidden';
});

this._setupIndicators();

// Show active slide
if (this.$active) {
this.$active.css('display', 'block');
this.$active.css('display', 'block').css('visibility', 'visible');
} else {
this.$slides.first().addClass('active');
anim({
Expand All @@ -77,13 +103,14 @@
duration: this.options.duration,
easing: 'easeOutQuad'
});
this.$slides.first().css('visibility', 'visible');

this.activeIndex = 0;
this.$active = this.$slides.eq(this.activeIndex);

// Update indicators
if (this.options.indicators) {
this.$indicators.eq(this.activeIndex).addClass('active');
this.$indicators.eq(this.activeIndex).children().first().addClass('active');
}
}

Expand Down Expand Up @@ -137,22 +164,39 @@
_setupEventHandlers() {
this._handleIntervalBound = this._handleInterval.bind(this);
this._handleIndicatorClickBound = this._handleIndicatorClick.bind(this);
this._handleAutoPauseFocusBound = this._handleAutoPauseFocus.bind(this);
this._handleAutoStartFocusBound = this._handleAutoStartFocus.bind(this);
this._handleAutoPauseHoverBound = this._handleAutoPauseHover.bind(this);
this._handleAutoStartHoverBound = this._handleAutoStartHover.bind(this);

if (this.options.pauseOnFocus) {
this.el.addEventListener('focusin', this._handleAutoPauseFocusBound);
this.el.addEventListener('focusout', this._handleAutoStartFocusBound);
}
if (this.options.pauseOnHover) {
this.el.addEventListener('mouseenter', this._handleAutoPauseHoverBound);
this.el.addEventListener('mouseleave', this._handleAutoStartHoverBound);
}

if (this.options.indicators) {
this.$indicators.each((el) => {
el.addEventListener('click', this._handleIndicatorClickBound);
});
this.$indicators.children().on('click', this._handleIndicatorClickBound);
}
}

/**
* Remove Event Handlers
*/
_removeEventHandlers() {
if (this.options.pauseOnFocus) {
this.el.removeEventListener('focusin', this._handleAutoPauseFocusBound);
this.el.removeEventListener('focusout', this._handleAutoStartFocusBound);
}
if (this.options.pauseOnHover) {
this.el.removeEventListener('mouseenter', this._handleAutoPauseHoverBound);
this.el.removeEventListener('mouseleave', this._handleAutoStartHoverBound);
}
if (this.options.indicators) {
this.$indicators.each((el) => {
el.removeEventListener('click', this._handleIndicatorClickBound);
});
this.$indicators.children().off('click', this._handleIndicatorClickBound);
}
}

Expand All @@ -161,10 +205,51 @@
* @param {Event} e
*/
_handleIndicatorClick(e) {
let currIndex = $(e.target).index();
let currIndex = $(e.target).parent().index();
this._focusCurrent = true;
this.set(currIndex);
}

/**
* Mouse enter event handler
*/
_handleAutoPauseHover() {
this._hovered = true;
if (this.interval != null) {
this._pause(true);
}
}

/**
* Focus in event handler
*/
_handleAutoPauseFocus() {
this._focused = true;
if (this.interval != null) {
this._pause(true);
}
}

/**
* Mouse enter event handler
*/
_handleAutoStartHover() {
this._hovered = false;
if (!(this.options.pauseOnFocus && this._focused) && this.eventPause) {
this.start();
}
}

/**
* Focus out leave event handler
*/
_handleAutoStartFocus() {
this._focused = false;
if (!(this.options.pauseOnHover && this._hovered) && this.eventPause) {
this.start();
}
}

/**
* Handle Interval
*/
Expand Down Expand Up @@ -223,8 +308,13 @@
_setupIndicators() {
if (this.options.indicators) {
this.$indicators = $('<ul class="indicators"></ul>');
this.$slides.each((el, index) => {
let $indicator = $('<li class="indicator-item"></li>');
this.$slides.each((el, i) => {
let label = this.options.indicatorLabelFunc
? this.options.indicatorLabelFunc.call(this, i + 1, i === 0)
: `${i + 1}`;
let $indicator = $(`<li class="indicator-item">
<button type="button" class="indicator-item-btn" aria-label="${label}" aria-controls="${this._sliderId}"></button>
</li>`);
this.$indicators.append($indicator[0]);
});
this.$el.append(this.$indicators[0]);
Expand Down Expand Up @@ -253,6 +343,8 @@
this.$active = this.$slides.eq(this.activeIndex);
let $caption = this.$active.find('.caption');
this.$active.removeClass('active');
// Enables every slide
this.$slides.css('visibility', 'visible');

anim({
targets: this.$active[0],
Expand All @@ -269,6 +361,8 @@
duration: 0,
easing: 'easeOutQuad'
});
// Disables invisible slides (for assistive technologies)
el.style.visibility = 'hidden';
});
}
});
Expand All @@ -277,8 +371,34 @@

// Update indicators
if (this.options.indicators) {
this.$indicators.eq(this.activeIndex).removeClass('active');
this.$indicators.eq(index).addClass('active');
let activeIndicator = this.$indicators
.eq(this.activeIndex)
.children()
.first();
let nextIndicator = this.$indicators
.eq(index)
.children()
.first();
activeIndicator.removeClass('active');
nextIndicator.addClass('active');
if (typeof this.options.indicatorLabelFunc === "function"){
activeIndicator.attr(
'aria-label',
this.options.indicatorLabelFunc.call(
this,
this.$indicators.eq(this.activeIndex).index(),
false
)
);
nextIndicator.attr(
'aria-label',
this.options.indicatorLabelFunc.call(
this,
this.$indicators.eq(index).index(),
true
)
);
}
}

anim({
Expand All @@ -299,18 +419,35 @@
});

this.$slides.eq(index).addClass('active');
if (this._focusCurrent) {
this.$slides.eq(index)[0].focus();
this._focusCurrent = false;
}
this.activeIndex = index;

// Reset interval
this.start();
// Reset interval, if allowed. This check prevents autostart
// when slider is paused, since it can be changed though indicators.
if (this.interval != null) {
this.start();
}
}
}

/**
* "Protected" function which pauses current interval
* @param {boolean} fromEvent Specifies if request came from event
*/
_pause(fromEvent) {
clearInterval(this.interval);
this.eventPause = fromEvent;
this.interval = null;
}

/**
* Pause slider interval
*/
pause() {
clearInterval(this.interval);
this._pause(false);
}

/**
Expand All @@ -322,6 +459,7 @@
this._handleIntervalBound,
this.options.duration + this.options.interval
);
this.eventPause = false;
}

/**
Expand Down
Loading