Skip to content

Instantly share code, notes, and snippets.

@cannap
Created March 20, 2020 15:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cannap/ded375a38eb40ca826c654856d9d027a to your computer and use it in GitHub Desktop.
Save cannap/ded375a38eb40ca826c654856d9d027a to your computer and use it in GitHub Desktop.
(function($) {
// cache some values
var cache = {
idx_expanded : -1, // the index of the current expanded slice
sliceH : 0, // the default slice's height
current : 0, // controls the current slider position
totalSlices : 0 // total number of slices
},
aux = {
// triggered when we click a slice. If the slice is expanded,
// we close it, otherwise we open it..
selectSlice : function( $el, $slices, $navNext, $navPrev, settings ) {
return $.Deferred(
function( dfd ) {
var expanded = $el.data('expanded'),
pos = $el.data('position'),
itemHeight, othersHeight,
$others = $slices.not( $el );
// if it's opened..
if( expanded ) {
$el.data( 'expanded', false );
cache.idx_expanded = -1;
// the default values of each slices's height
itemHeight = cache.sliceH;
othersHeight= cache.sliceH;
// hide the content div
$el.find('.va-content').hide();
// control the navigation buttons visibility
if( aux.canSlideUp( $slices, settings ) )
$navPrev.fadeIn();
else
$navPrev.fadeOut();
if( aux.canSlideDown( $slices, settings ) )
$navNext.fadeIn();
else
$navNext.fadeOut();
}
// if it's closed..
else {
$el.data( 'expanded', true );
cache.idx_expanded = $el.index();
$others.data( 'expanded', false );
// the current slice's height
itemHeight = settings.expandedHeight;
// the height the other slices will have
othersHeight= Math.ceil( ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 ) );
// control the navigation buttons visibility
if( cache.idx_expanded > 0 )
$navPrev.fadeIn();
else
$navPrev.fadeOut();
if( cache.idx_expanded < cache.totalSlices - 1 )
$navNext.fadeIn();
else
$navNext.fadeOut();
}
// the animation parameters for the clicked slice
var animParam = {
height : itemHeight + 'px',
opacity : 1,
top : ( pos - 1 ) * othersHeight + 'px'
};
// animate the clicked slice and also its title (<h3>)
$el.stop()
.animate( animParam, settings.animSpeed, settings.animEasing, function() {
if( !expanded )
$el.find('.va-content').fadeIn( settings.contentAnimSpeed );
})
.find('.va-title')
.stop()
.animate({
lineHeight : cache.sliceH + 'px'
}, settings.animSpeed, settings.animEasing );
// animate all the others
$others.each(function(i){
var $other = $(this),
posother= $other.data('position'),
t;
if( expanded )
t = ( posother - 1 ) * othersHeight ;
else {
if( posother < pos )
t = ( posother - 1 ) * othersHeight ;
else
t = ( ( posother - 2 ) * othersHeight ) + settings.expandedHeight;
}
$other.stop()
.animate( {
top : t + 'px',
height : othersHeight + 'px',
opacity : ( expanded ) ? 1 : settings.animOpacity
}, settings.animSpeed, settings.animEasing, dfd.resolve )
.find('.va-title')
.stop()
.animate({
lineHeight : othersHeight + 'px'
}, settings.animSpeed, settings.animEasing )
.end()
.find('.va-content')
.hide();
});
}
).promise();
},
// triggered when clicking the navigation buttons / mouse scrolling
navigate : function( dir, $slices, $navNext, $navPrev, settings ) {
// if animating return
if( $slices.is(':animated') )
return false;
// all move up / down one position
// if settings.savePositions is false, then we need to close any expanded slice before sliding
// otherwise we slide, and the next one will open automatically
var $el;
if( cache.idx_expanded != -1 && !settings.savePositions ) {
$el = $slices.eq( cache.idx_expanded );
$.when( aux.selectSlice( $el, $slices, $navNext, $navPrev, settings ) ).done(function(){
setTimeout(function() {
aux.slide( dir, $slices, $navNext, $navPrev, settings );
}, 10);
});
}
else {
aux.slide( dir, $slices, $navNext, $navPrev, settings );
}
},
slide : function( dir, $slices, $navNext, $navPrev, settings ) {
// control if we can navigate.
// control the navigation buttons visibility.
// the navigation will behave differently for the cases we have all the slices closed,
// and when one is opened. It will also depend on settings.savePositions
if( cache.idx_expanded === -1 || !settings.savePositions ) {
if( dir === 1 && cache.current + settings.visibleSlices >= cache.totalSlices )
return false;
else if( dir === -1 && cache.current === 0 )
return false;
if( dir === -1 && cache.current === 1 )
$navPrev.fadeOut();
else
$navPrev.fadeIn();
if( dir === 1 && cache.current + settings.visibleSlices === cache.totalSlices - 1 )
$navNext.fadeOut();
else
$navNext.fadeIn();
}
else {
if( dir === 1 && cache.idx_expanded === cache.totalSlices - 1 )
return false;
else if( dir === -1 && cache.idx_expanded === 0 )
return false;
if( dir === -1 && cache.idx_expanded === 1 )
$navPrev.fadeOut();
else
$navPrev.fadeIn();
if( dir === 1 && cache.idx_expanded === cache.totalSlices - 2 )
$navNext.fadeOut();
else
$navNext.fadeIn();
}
var $currentSlice = $slices.eq( cache.idx_expanded ),
$nextSlice,
t;
( dir === 1 ) ? $nextSlice = $currentSlice.next() : $nextSlice = $currentSlice.prev();
// if we cannot slide up / down, then we just call the selectSlice for the previous / next slice
if( ( dir === 1 && !aux.canSlideDown( $slices, settings ) ) ||
( dir === -1 && !aux.canSlideUp( $slices, settings ) ) ) {
aux.selectSlice( $nextSlice, $slices, $navNext, $navPrev, settings );
return false;
}
// if we slide down, the top and position of each slice will decrease
if( dir === 1 ) {
cache.current++;
t = '-=' + cache.sliceH;
pos_increment = -1;
}
else {
cache.current--;
t = '+=' + cache.sliceH;
pos_increment = 1;
}
$slices.each(function(i) {
var $slice = $(this),
pos = $slice.data('position');
// all closed or savePositions is false
if( !settings.savePositions || cache.idx_expanded === -1 )
$slice.stop().animate({top : t}, settings.animSpeed, settings.animEasing);
else {
var itemHeight, othersHeight;
// if the slice is the one we should open..
if( i === $nextSlice.index() ) {
$slice.data( 'expanded', true );
cache.idx_expanded = $slice.index();
itemHeight = settings.expandedHeight;
othersHeight = ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 );
$slice.stop()
.animate({
height : itemHeight + 'px',
opacity : 1,
top : ( dir === 1 ) ? ( pos - 2 ) * othersHeight + 'px' : pos * othersHeight + 'px'
}, settings.animSpeed, settings.animEasing, function() {
$slice.find('.va-content').fadeIn( settings.contentAnimSpeed );
})
.find('.va-title')
.stop()
.animate({
lineHeight : cache.sliceH + 'px'
}, settings.animSpeed, settings.animEasing );
}
// if the slice is the one opened, lets close it
else if( $slice.data('expanded') ){
// collapse
$slice.data( 'expanded', false );
othersHeight = ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 );
$slice.stop()
.animate({
height : othersHeight + 'px',
opacity : settings.animOpacity,
top : ( dir === 1 ) ? '-=' + othersHeight : '+=' + settings.expandedHeight
}, settings.animSpeed, settings.animEasing )
.find('.va-title')
.stop()
.animate({
lineHeight : othersHeight + 'px'
}, settings.animSpeed, settings.animEasing )
.end()
.find('.va-content')
.hide();
}
// all the others..
else {
$slice.data( 'expanded', false );
othersHeight = ( settings.accordionH - settings.expandedHeight ) / ( settings.visibleSlices - 1 );
$slice.stop()
.animate({
top : ( dir === 1 ) ? '-=' + othersHeight : '+=' + othersHeight
}, settings.animSpeed, settings.animEasing );
}
}
// change the slice's position
$slice.data().position += pos_increment;
});
},
canSlideUp : function( $slices, settings ) {
var $first = $slices.eq( cache.current );
if( $first.index() !== 0 )
return true;
},
canSlideDown : function( $slices, settings ) {
var $last = $slices.eq( cache.current + settings.visibleSlices - 1 );
if( $last.index() !== cache.totalSlices - 1 )
return true;
}
},
methods = {
init : function( options ) {
if( this.length ) {
var settings = {
// the accordion's width
accordionW : 1000,
// the accordion's height
accordionH : 450,
// number of visible slices
visibleSlices : 3,
// the height of a opened slice
// should not be more than accordionH
expandedHeight : 350,
// speed when opening / closing a slice
animSpeed : 250,
// easing when opening / closing a slice
animEasing : 'jswing',
// opacity value for the collapsed slices
animOpacity : 0.2,
// time to fade in the slice's content
contentAnimSpeed: 900,
// if this is set to false, then before
// sliding we collapse any opened slice
savePositions : true
};
return this.each(function() {
// if options exist, lets merge them with our default settings
if ( options ) {
$.extend( settings, options );
}
var $el = $(this),
// the accordion's slices
$slices = $el.find('div.va-slice'),
// the navigation buttons
$navNext = $el.find('span.va-nav-next'),
$navPrev = $el.find('span.va-nav-prev');
// each slice's height
cache.sliceH = Math.ceil( settings.accordionH / settings.visibleSlices );
// total slices
cache.totalSlices = $slices.length;
// control some user config parameters
if( settings.expandedHeight > settings.accordionH )
settings.expandedHeight = settings.accordionH;
else if( settings.expandedHeight <= cache.sliceH )
settings.expandedHeight = cache.sliceH + 50; // give it a minimum
// set the accordion's width & height
$el.css({
width : settings.accordionW + 'px',
height : settings.accordionH + 'px'
});
// show / hide $navNext
if( settings.visibleSlices < cache.totalSlices )
$navNext.show();
// set the top & height for each slice.
// also save the position of each one.
// as we navigate, the first one in the accordion
// will have position 1 and the last settings.visibleSlices.
// finally set line-height of the title (<h3>)
$slices.each(function(i){
var $slice = $(this);
$slice.css({
top : i * cache.sliceH + 'px',
height : cache.sliceH + 'px'
}).data( 'position', (i + 1) );
})
.children('.va-title')
.css( 'line-height', cache.sliceH + 'px' );
// click event
$slices.bind('click.vaccordion', function(e) {
// only if we have more than 1 visible slice.
// otherwise we will just be able to slide.
if( settings.visibleSlices > 1 ) {
var $el = $(this);
aux.selectSlice( $el, $slices, $navNext, $navPrev, settings );
}
});
// navigation events
$navNext.bind('click.vaccordion', function(e){
aux.navigate( 1, $slices, $navNext, $navPrev, settings );
});
$navPrev.bind('click.vaccordion', function(e){
aux.navigate( -1, $slices, $navNext, $navPrev, settings );
});
// adds events to the mouse
$el.bind('mousewheel.vaccordion', function(e, delta) {
if(delta > 0) {
aux.navigate( -1, $slices, $navNext, $navPrev, settings );
}
else {
aux.navigate( 1, $slices, $navNext, $navPrev, settings );
}
return false;
});
});
}
}
};
$.fn.vaccordion = function(method) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.vaccordion' );
}
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment