Skip to content

Instantly share code, notes, and snippets.

@YodasWs
Last active May 15, 2017 14:58
Show Gist options
  • Save YodasWs/131fac4d5c302e48de5c7e1a5f81e929 to your computer and use it in GitHub Desktop.
Save YodasWs/131fac4d5c302e48de5c7e1a5f81e929 to your computer and use it in GitHub Desktop.
Cardrack: cards on big screens, carousel on small screens
Cardrack
========
Your one-stop component for either a table-esque layout of Cards (blocks, tombstones) or a Carousel, _or both!_
With Cardrack, using the same markup, your table-esque layout can collapse into a Carousel on small screens, or large screens, or all screens.
## Installation
### Simple Markup
```html
<section class="cardrack">
<h2>Title Goes Here</h2>
<p>You can include all the non-Card markup you want above the Cards.</p>
<div class="card">
This is a Card
</div>
<div class="card">
This is a Card
</div>
<div class="card">
This is a Card
</div>
</section>
```
### Instantiation
Every element with class `cardrack` is automatically instantiated with the jQuery Plugin:
```javascript
$('.cardrack').Cardrack()
```
If new Cardracks are to be created after initial page load, you can call the plugin again:
```javascript
$('#new.cardrack').Cardrack({ btnPos: 'none' })
```
If you wish to update some of the Cardrack settings, it would be better to set them individually:
```javascript
$('.cardrack').Cardrack('set', 'columns', 3)
$('.cardrack').Cardrack('set', [ 'btnPos', 'none' ])
```
## Carousel Options
### Setting Max Breakpoint of Carousel
`carousel-{size}`<br/>
`maxCarouselBreakPoint: {size}`<br/>
`Cardrack('set', 'maxCarouselBreakPoint', {size})`
**size**: The screen size and down where the Cardrack takes the form of a Carousel.
By default, the Cardrack is a table-esque layout of Cards. To transform this into a single-row Carousel, you must add this class to the Cardrack.
```html
<section class="cardrack carousel-md">
```
```javascript
$('.cardrack').addClass('carousel-md')
```
```javascript
$('#new.cardrack').Cardrack({ maxCarouselBreakPoint: 'md' })
```
```javascript
$('#new.cardrack').Cardrack('set', 'maxCarouselBreakPoint', 'md')
```
To have the Cardrack be displayed as a Carousel on all screen sizes, you just use the new `xxl` breakpoint size:
```html
<section class="cardrack carousel-xxl">
```
### Hide Carousel Buttons
`cr-no-btns`<br/>
`hideAllBtns: (true|false)`<br/>
`Cardrack('set', 'hideAllBtns', (true|false))`
Hides all buttons, including Next and Previous, from the Carousel
```html
<section class="cardrack cr-no-btns">
```
```javascript
$('#new.cardrack').Cardrack({ hideAllBtns: true })
```
```javascript
$('#new.cardrack').Cardrack('set', 'hideAllBtns', true)
```
### Set Position of Card-Selection Buttons, or Hide Them
`data-btnPos={position}`<br/>
`btnPos: {position}`<br/>
`Cardrack('set', 'btnPos', {position})`
Sets the position of the round buttons to jump to a specific Card.
**position**: `top`, `bottom`, `none`
```html
<section class="cardrack carousel-xxl" data-btnPos="bottom">
```
```javascript
$('.cardrack').Cardrack({ btnPos: 'none' })
$('.cardrack').Cardrack('set', 'btnPos', 'none')
```
### Change Animation Style
`Cardrack('animate', {style})`
Sets the style of animation used to switch between Cards.
**style**: `fade`, `slide`
```javascript
$('.cardrack').Cardrack('animate', 'slide');
```
## As a Slideshow
To create a Slideshow, just set the Cardrack to a Carousel and give the animation a timer.
```html
<section class="cardrack carousel-xxl cr-sm-1-cols cr-no-btns">
```
```javascript
$('.cardrack').Cardrack('animate', 'slide', 10000);
```
## Advanced Options
### Set Number of Columns
`cr-{size}-{num}-cols`<br/>
`Cardrack('set', 'columns', {num}[, {size}])`
Sets the number of Columns.
**size**: The screen size and up this applies to.<br/>
**num**: The number of Columns, 1&ndash;12
```html
<section class="cardrack cr-sm-1-cols cr-md-2-cols cr-lg-3-cols cr-xl-4-cols cr-xxl-5-cols">
<div class="card">This is a Card</div>
<div class="card">This is a Card</div>
<div class="card">This is a Card</div>
<div class="card">This is a Card</div>
</section>
```
```javascript
$('.cardrack').addClass('cr-md-2-cols')
```
```javascript
$('.cardrack').Cardrack('set', 'columns', 2, 'md')
```
If no size is specified with the JavaScript method, it will be applied to all sizes:
```javascript
$('.cardrack').Cardrack('set', 'columns', 2)
```
### Set Width of Cards Individually
`{size}-col-{num}`
Sets the number of Columns this Card takes up.
**size**: The screen size and up this applies to.<br/>
**num**: The number of Columns, 1&ndash;12
E.g. `sm-col-4`, `lg-col-2`
```html
<section class="cardrack">
<div class="card md-col-3">This is a narrow Card</div>
<div class="card md-col-9">This is a wide Card</div>
</section>
```
```javascript
$('.cardrack .card:first-child ').addClass('md-col-3')
$('.cardrack .card:nth-child(2)').addClass('md-col-9')
```
## Theme Options
### Rounded Corners on Cards
Gives the Cards rounded corners:
```html
<section class="cardrack rounded">
<div class="card">This is a Card</div>
<div class="card">This is a Card</div>
</section>
```
```javascript
$('.cardrack').addClass('rounded')
```
Can be applied to individual Cards instead:
```html
<section class="cardrack">
<div class="card rounded">This is a Card</div>
<div class="card">This is a Card</div>
</section>
```
```javascript
$('.cardrack .card:first-child').addClass('rounded')
```
### White Background on Cards
Gives the Cards a white background:
```html
<section class="cardrack bg-white">
<div class="card">This is a Card</div>
<div class="card">This is a Card</div>
</section>
```
```javascript
$('.cardrack').addClass('bg-white')
```
Can be applied to individual Cards instead:
```html
<section class="cardrack">
<div class="card bg-white">This is a Card</div>
<div class="card">This is a Card</div>
</section>
```
```javascript
$('.cardrack .card:first-child').addClass('bg-white')
```
// Upgrade Safari, IE, and Edge
Number.parseInt = Number.parseInt || parseInt;
Number.parseFloat = Number.parseFloat || parseFloat;
Number.isNaN = Number.isNaN || function (v) {
return typeof v === 'number' && isNaN(v)
};
Number.isFinite = Number.isFinite || function (v) {
return typeof v === 'number' && isFinite(v)
};
Number.isInteger = Number.isInteger || function (v) {
return Number.isFinite(v) && Math.floor(v) === v
};
Math.sign = Math.sign || function (x) {
x = +x;
if (x === 0 || Number.isNaN(x))return x;
return x > 0 ? 1 : -1
};
var guid = window.guid || function () {
// Create page-unique id (not true guid)
// Simplified from http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(2);
}
return s4() + s4() + s4() + s4();
};
// TODO: Need to fix this to determine "breakpoint" based on screen width
function bp() {
const mediaQueries = {
'small': {query: 'screen and (max-width:40em)', range: {min: 0, max: 640}},
'medium': {query: 'screen and (min-width:40.0625em)', range: {min: 641, max: 768}},
'large': {query: 'screen and (min-width:48.0625em)', range: {min: 769, max: 1024}},
'xlarge': {query: 'screen and (min-width:64.0625em)', range: {min: 1025, max: 1330}},
'xxlarge': {query: 'screen and (min-width:83.125em)', range: {min: 1331, max: Number.Infinity}}
};
// Cache answer so we only test at start and on resize
let breakpoint = '';
// Retest on resize
$(window).on('resize', function () {
const width = $(window).width();
let range, previous;
for (const key in mediaQueries) {
range = mediaQueries[key].range;
if (width >= range.min && width <= range.max) {
if (key !== breakpoint) {
previous = breakpoint;
breakpoint = key;
}
}
}
});
return breakpoint;
}
/**** Cardrack Object ****/
;(function ($) {
var pluginName = 'Cardrack',
slideTimeout = 0,
sizes = {
'xxl': 5,
'xxlarge': 5,
'xl': 4,
'xlarge': 4,
'lg': 3,
'large': 3,
'md': 2,
'medium': 2,
'sm': 1,
'small': 1,
},
sizeAbbr = {
'xxl': 'xxlarge',
'xl': 'xlarge',
'lg': 'large',
'md': 'medium',
'sm': 'small',
'xxlarge': 'xxl',
'xlarge': 'xl',
'large': 'lg',
'medium': 'md',
'small': 'sm'
},
fnResize = function (cr) {
if (cr.isInResize) return;
cr.isInResize = true;
var numColumns = cr.get('columns'),
bp = bp(),
cards = cr.$el.find(cr.options.cardSelector);
// Not Carousel, set CSS for Display
if (cr.$el.css('display') != 'block') {
cr.$el.css({height: ''});
cards.css({
display: '',
left: '',
top: ''
});
if (!cr.$el.is('.cr-no-btns')) {
cr.$el.find('[data-action="prev"]').data('positioned', false);
}
cr.isInResize = false;
return;
}
// Set Cardrack Carousel Size and keep Cards below content
if (cr.options.staticHeight) {
var height = 0,
tallest = false,
width = cr.$el.width();
if (Number.isFinite(cr.options.uniformCardHeight) && cr.options.uniformCardHeight >= 0) {
console.log('cardrack.js', 'Calculate A', cr.options.uniformCardHeight + 'px');
cards.css({
height: cr.options.uniformCardHeight + 'px'
});
} else {
// Find the Tallest Card
cards.css({
height: ''
}).each(function () {
var $t = $(this),
h = $t.outerHeight(false);
if (h > height) {
tallest = $t;
height = h;
}
});
}
// Adjust Card positions and Carousel Height
if (tallest) {
cr.$el.css({
height: ''
});
tallest.css({position: 'static'});
// Set Cardrack Carousel Height
if (cr.options.staticHeight === true) {
console.log('cardrack.js', 'Calculate B', cr.$el.outerHeight(false));
cr.$el.css({
// Safari fails to recalculate to the smaller automatic height, so let's just play safe here and call it a day
height: Math.max(cr.$el.outerHeight(false), tallest.outerHeight(true)) + 'px'
});
}
if (cr.options.uniformCardHeight === true) {
cards.css({
height: tallest.outerHeight(false) + 'px'
});
}
cards.css({
top: tallest.position().top
});
tallest.css({position: ''});
// Recalculate on full page load
if (document.readystate !== 'complete') {
$(window).on('load', function () {
cr.isInResize = false;
fnResize(cr);
});
}
}
}
cr.redrawBtns();
// On Breakpoint Change
if (cr.options.currentScreen != bp) {
cr.options.currentScreen = bp;
// Reinforce Static Height
if (Number.isFinite(cr.options.staticHeight)) {
cr.$el.css({height: cr.options.staticHeight + cr.options.staticHeightUnit});
} else if (typeof cr.options.staticHeight === 'string') {
cr.$el.css({height: cr.options.staticHeight});
}
// Display First Active Card
var card = cr.get('currentCard');
if (card % numColumns) {
card -= card % numColumns;
}
cr.changeTo(card);
// Start Animation
cr.restartAnimation();
}
cr.isInResize = false;
},
Plugin = function (element, options) {
var defaults = {
animation: {
type: 'fade',
auto: false
},
btnOffset: 0,
btnPos: 'bottom',
cardSelector: '.card',
carouselAllChildren: false,
hideAllBtns: false,
maxCarouselBreakPoint: 'none',
moveNumCards: 'columns',
pauseAnimationOnMouseOver: false,
staticHeight: true,
staticHeightUnit: 'px',
transitionTime: 500,
uniformCardHeight: false
},
cards = {},
self = this;
this.el = element;
this.$el = $(this.el);
this.isInResize = false;
this.options = $.extend({}, defaults, options);
this.options.currentScreen = bp();
if (!this.$el.attr('id')) {
this.$el.attr('id', guid());
}
// Save Cardrack Options
this.set('btnPos', this.options.btnPos);
if (this.options.maxCarouselBreakPoint !== 'none') {
this.set('maxCarouselBreakPoint', this.options.maxCarouselBreakPoint);
}
// Set Cardrack Options
if (this.options.hideAllBtns) {
this.$el.addClass('cr-no-btns');
}
if (!this.options.staticHeight) {
this.$el.addClass('fluid-height');
}
if (this.options.carouselAllChildren) {
this.$el.children().addClass('card');
}
cards = this.$el.find(this.options.cardSelector)
// Build Carousel Controls
if (this.$el.is('[class^="carousel-"]') || this.$el.is('[class*=" carousel-"]')) {
this.$el.off('click.prev').on('click.prev', '[data-action="prev"]', function () {
fnResize(self);
self.moveCards('left');
}).off('click.next').on('click.next', '[data-action="next"]', function () {
fnResize(self);
self.moveCards('next');
});
if (!this.$el.children('[data-action="prev"]').length) {
this.$el.append(
$('<div data-action="prev">')
);
}
if (!this.$el.children('[data-action="next"]').length) {
this.$el.append(
$('<div data-action="next">')
);
}
// Add DTM tracking to carousel buttons
$("[data-action='prev']", this.$el).attr({
'data-track-ui': 'carousel',
'data-track-elem': 'icon',
'data-track-name': 'Previous'
});
$("[data-action='next']", this.$el).attr({
'data-track-ui': 'carousel',
'data-track-elem': 'icon',
'data-track-name': 'Next'
});
if (!this.$el.children('.btns').length) {
this.$el.append($('<div class="btns">'));
cards.each(function (i) {
var $button = $('<input>', {
name: 'cardrack-' + self.$el.attr('id'),
'data-which': i,
id: 'card-' + i,
type: 'radio'
}),
$label = $('<label>', {
'for': 'card-' + i,
'data-which': i
});
if (i == 0) $button.prop('checked', true);
$label.on('click', function () {
self.changeTo($(this).data('which'));
});
self.$el.find('.btns').append($button).append($label);
});
}
}
// Display Starting Cards
this.changeTo(0);
// Set up responsive functionality
var resizeDelay = 0;
$(window).off('resize.cr-' + this.$el.attr('id')).on('resize.cr-' + this.$el.attr('id'), function () {
if (resizeDelay) clearTimeout(resizeDelay);
resizeDelay = setTimeout(function () {
fnResize(self);
}, 100);
});
$(window).on('load', function () {
self.isInResize = false;
fnResize(self);
});
};
Plugin.prototype.animate = function (type, time) {
if (['slide', 'fade'].indexOf(type) == -1) {
return false;
}
if (!time || time < 0 || Number.isNaN(time)) {
time = 0;
}
this.options.animation = $.extend({}, this.options.animation, {type: type, auto: time});
this.restartAnimation();
};
Plugin.prototype.restartAnimation = function () {
var self = this;
clearTimeout(this.options.animation.timer);
if (!this.options.animation.auto) {
return false;
}
if (this.options.pauseAnimationOnMouseOver) {
// Pause Animation on MouseOver
this.$el.off('mouseenter.stopAnimation').on('mouseenter.stopAnimation', function () {
clearTimeout(self.options.animation.timer);
}).one('mouseleave.startAnimation', this.restartAnimation);
}
// Set Timeout for Animation
;
(function (cr) {
if (cr.$el.is(':hidden')) return; // Can't animate what's hidden
cr.changeTo(cr.get('currentCard') + cr.get('columns'));
cr.options.animation.timer = setTimeout(
arguments.callee,
cr.options.animation.auto,
cr
);
})(this);
};
Plugin.prototype.redrawBtns = function () {
var numColumns = this.get('columns');
var self = this;
// Position and display Buttons
if (!this.$el.is('.cr-no-btns')) {
if (this.options.btnPos != 'none') {
this.$el.children('.btns').css({
bottom: '',
right: '',
left: '',
top: ''
}).css(this.options.btnPos, this.options.btnOffset * (this.options.btnPos == 'bottom' ? -1 : 1) + 'px')
.children('label').css('display', '').each(function (i) {
if (i % numColumns) $(this).hide();
});
}
// Waiting 1/4 second (1 sec for mobile) for the plugin to finish rendering so that the outerHeight()
// can be calculated correctly - necessary because of a rendering defect in Safari/Mac and iOS Phone
setTimeout(function () {
console.log('Redraw Buttons', self.$el, self.$el.height(), self.$el.outerHeight(false));
self.$el.find('[data-action="prev"], [data-action="next"]').css({
top: (self.$el.outerHeight(false) / 2) + 'px'
}).data('positioned', true);
}, 250);
}
// Hide Buttons if no scroll
if (numColumns >= this.$el.find(this.options.cardSelector).length) {
this.$el.find('.btns, [data-action="prev"], [data-action="next"]').hide();
} else {
this.$el.find('.btns, [data-action="prev"], [data-action="next"]').css({display: ''});
}
};
Plugin.prototype.moveCards = function (direction, num) {
if (Number.isFinite(direction)) {
direction = Math.sign(direction) || 1;
num = num || Math.abs(direction) || NaN;
} else switch (direction) {
case 'right':
case 'next':
direction = 1;
break;
case 'previous':
case 'left':
direction = -1;
break;
default:
return false;
}
if (!Number.isInteger(num) || num <= 0) {
if (Number.isInteger(this.options.moveNumCards) && this.options.moveNumCards > 0) {
num = this.options.moveNumCards;
} else if (this.options.moveNumCards === 'columns') {
num = this.get('columns');
} else if (typeof this.options.moveNumCards === 'string' && typeof this[this.options.moveNumCards] === 'function') {
num = this[this.options.moveNumCards]();
} else {
num = this.get('columns');
}
}
// TODO: Reset Animation Timeout
this.changeTo(this.$el.find('.btns input[type="radio"]:checked').data('which') + direction * (num || 1));
};
Plugin.prototype.changeTo = function (slidenum, animationType) {
// If in Table-esque mode, not Carousel, show all Cards
if (this.$el.css('display') == 'flex') {
this.$el.find(this.options.cardSelector).css('display', '');
return;
}
var numColumns = this.get('columns'),
$btns = this.$el.find('.btns input[type="radio"]'),
direction = 'left',
self = this;
animationType = animationType || this.options.animation.type;
// Set Direction before correcting for wrapping
if (slidenum < this.get('currentCard')) direction = 'right';
// Now correct for wrapping
if (slidenum >= $btns.length) {
slidenum = 0;
}
if (slidenum < 0) {
if ($btns.length % numColumns) {
slidenum = $btns.length - $btns.length % numColumns;
} else if ($btns.length < numColumns) {
slidenum = 0;
} else {
slidenum = $btns.length - numColumns;
}
}
// Save new position
$btns.filter('[data-which="' + slidenum + '"]').prop('checked', true);
// Now to animate change
switch (animationType) {
case 'slide':
var gutterRight = ' + ' + Number.parseFloat(this.$el.css('margin-right')) + 'px',
gutterLeft = ' - ' + Number.parseFloat(this.$el.css('margin-left')) + 'px',
isIE = window.navigator.userAgent.match(/msie/i) !== null || window.navigator.appVersion.match(/trident/i) !== null,
slideTransitionOut = {
transition: 'left ' + this.options.transitionTime + 'ms, opacity ' + this.options.transitionTime + 'ms ' + (this.options.transitionTime / 5) + 'ms'
},
slideTransitionIn = {
transition: 'left ' + this.options.transitionTime + 'ms, opacity 0ms'
},
// Turn off all transitions and store Cards' intrinsic left position
cardsAll = this.$el.find(this.options.cardSelector).css({
transition: 'none'
}).each(function () {
var $t = $(this);
// Hot-fix Transitions for IE bug
if (isIE) {
$t.css({transform: ''});
}
$t.data('intrinsic-left', Number.parseFloat($t.css({left: ''}).css('left')) + 'px');
}),
// Grab cardsToShow and fix width
cardsToShow = cardsAll.filter(function (i) {
if (i >= slidenum && i < slidenum + numColumns) {
var $t = $(this);
$t.css({width: $t.outerWidth(false)});
return true;
}
return false;
}),
cardsToHide = cardsAll.filter(function (i) {
// Exclude Cards in cardsToShow
if (i >= slidenum && i < slidenum + numColumns) {
return false;
}
return true;
}).filter('.active'),
// Hide Overflow
overflowElement = this.$el.is('.carousel-gutter') ? this.$el.parent() : this.$el;
overflowElement.css({'overflow': 'hidden'});
// Reset sliding transition on cardsToHide and slide away
if (!isIE)
cardsToHide.removeClass('active').css(slideTransitionOut).each(function () {
var $t = $(this),
dir = direction == 'left' ? '-' : '',
gutter = direction == 'left' ? gutterLeft : gutterRight;
$t.css({
width: $t.outerWidth(false),
left: 'calc(' + dir + '100% + ' + $t.data('intrinsic-left') + gutter + ')'
});
});
// Hot-fix Transitions for IE bug
// IE10-11 cannot run CSS transition on a property that uses calc()
// Fix is to use left in addition to transform translateX()
if (isIE) {
;
[slideTransitionOut, slideTransitionIn].forEach(function (t) {
t.transition += ', transform ' + self.options.transitionTime + 'ms';
});
// Reset sliding transition and positioning on cardsToHide
cardsToHide.each(function () {
var $t = $(this);
$t.css({
transform: 'translateX(' + Number.parseFloat($t.data('intrinsic-left')) + 'px)',
width: $t.outerWidth(false),
left: '0%'
});
});
// Now slide away cardsToHide
setTimeout(function () {
var dir = direction == 'left' ? '-' : '',
gutter = direction == 'left' ? gutterRight : gutterLeft;
gutter = Number.parseFloat(gutter.substr(3));
gutter *= direction == 'left' ? -1 : 1;
cardsToHide.css(slideTransitionOut).removeClass('active').each(function () {
var $t = $(this);
$t.css({
transform: 'translateX(' + (Number.parseFloat($t.data('intrinsic-left')) + gutter) + 'px)',
left: dir + '100%'
});
});
}, 1);
}
// Set left of cardsToShow
cardsToShow.addClass('active').each(function () {
var $t = $(this),
dir = direction == 'left' ? '' : '-';
gutter = direction == 'left' ? gutterRight : gutterLeft;
$t.css({
left: 'calc(' + dir + '100% + ' + $t.data('intrinsic-left') + gutter + ')'
});
// Hot-fix Transitions for IE bug
if (isIE) {
gutter = Number.parseFloat(gutter.substr(3));
gutter *= direction == 'left' ? 1 : -1;
$t.css({
transform: 'translateX(' + (Number.parseFloat($t.data('intrinsic-left')) + gutter) + 'px)',
left: dir + '105%'
});
}
});
// Now move cardsToShow into place
setTimeout(function () {
cardsToShow.css(slideTransitionIn).css({left: ''});
// Hot-fix Transitions for IE bug
if (isIE) {
cardsToShow.css(slideTransitionIn).each(function () {
var $t = $(this);
$t.css({
transform: 'translateX(' + $t.data('intrinsic-left') + ')',
left: '0%'
});
});
}
}, 1);
// Reset CSS to intrinsic values
clearTimeout(slideTimeout);
slideTimeout = setTimeout(function () {
overflowElement.css({'overflow': ''});
cardsAll.css({
transition: 'none',
width: ''
});
// Hot-fix Transitions for IE bug
if (isIE) {
cardsAll.css({
transform: '',
left: ''
});
}
}, this.options.transitionTime);
break;
// Simple fade effect using default CSS
case 'fade':
default:
this.$el.find(this.options.cardSelector).css({
transition: 'opacity ' + this.options.transitionTime + 'ms'
}).removeClass('active').each(function (i) {
if (i >= slidenum && i < slidenum + numColumns) {
$(this).addClass('active');
}
});
}
};
Plugin.prototype.set = function (option, val) {
var currentScreen = bp(),
self = this;
switch (option) {
case 'columns':
if (!Number.isInteger(val) || val <= 0) {
console.log('Set ' + option + ': Must supply positve integer');
return this.get('columns');
}
this.$el.attr('class').split(' ').forEach(function (c) {
var m = c.match(/^cr-(sm|md|lg|x{1,2}l)-(\d\d?)-cols$/)
if (!m) {
return;
}
if (arguments[2] && sizes.hasOwnProperty(arguments[2])) {
if (sizes[m[1]] == sizes[currentScreen]) {
self.$el.removeClass(c);
}
} else {
self.$el.removeClass(c);
}
});
this.$el.addClass('cr-' + (arguments[2] && sizes.hasOwnProperty(arguments[2]) ? sizeAbbr[currentScreen] : 'sm') + '-' + val + '-cols');
this.options.currentScreen = '';
fnResize(this);
break;
case 'btnPos':
this.options.btnPos = val;
this.$el.attr('data-btnPos', val);
if (arguments[2] && Number.isFinite(arguments[2])) {
this.set('btnOffset', arguments[2]);
}
this.redrawBtns();
break;
case 'btnOffset':
if (Number.isFinite(val)) {
this.options.btnOffset = val;
this.redrawBtns();
}
break;
case 'transitionTime':
if (!Number.isFinite(val) || val < 0) {
console.log('Set ' + option + ': Must supply nonnegative real number');
return false;
}
this.options.transitionTime = val;
this.$el.find(this.options.cardSelector).css({
transition: 'opacity ' + val + 'ms ease'
});
break;
case 'maxCarouselBreakPoint':
if (sizes.hasOwnProperty(val)) {
this.set('maxCarouselBreakPoint', 'none');
if (val.length > 3) {
val = sizeAbbr[val];
}
self.$el.addClass('carousel-' + val);
} else if (val === 'none') {
for (var size in sizes) {
self.$el.removeClass('carousel-' + size);
}
} else {
console.log('Set ' + option + ': Unknown value "' + val + '"');
return false;
}
break;
case 'hideAllBtns':
if (val) {
this.$el.addClass('cr-no-btns');
} else {
this.$el.removeClass('cr-no-btns');
}
break;
case 'uniformCardHeight':
if (typeof val === 'boolean') {
this.options.uniformCardHeight = val;
} else if (Number.isFinite(val) && val >= 0) {
this.options.uniformCardHeight = val;
} else {
console.log('Set ' + option + ': Must supply nonnegative real number or boolean');
return false;
}
if (document.readystate === 'complete') {
fnResize(self);
} else {
$(window).on('load', function () {
fnResize(self);
});
}
break;
}
return val;
};
Plugin.prototype.get = function (val) {
var currentScreen = bp();
switch (val) {
case 'columns':
var finished = false,
breakpoint = 0,
num = '';
this.$el.attr('class').split(' ').forEach(function (c) {
var m = c.match(/^cr-(sm|md|lg|x{1,2}l)-(\d\d?)-cols$/);
if (!m || !m.length || finished) return;
if (sizes[m[1]] == sizes[currentScreen]) {
breakpoint = sizes[m[1]];
finished = true;
num = m[2];
} else if (sizes[m[1]] < sizes[currentScreen] && sizes[m[1]] > breakpoint) {
breakpoint = sizes[m[1]];
num = m[2];
}
});
num = Number.parseInt(num, 10);
return !Number.isInteger(num) || num <= 0 ? 1 : num;
case 'currentCard':
var $btns = this.$el.find('.btns input[type="radio"]');
return $btns.index($btns.filter(':checked'));
}
};
$.fn[pluginName] = function () {
var returnValue = false,
args = Array.prototype.slice.call(arguments) || [],
options = {},
fn = false;
// Are we passing options for instantiation?
if (typeof arguments[0] === 'object' && !(arguments[0] instanceof Array)) {
options = args.shift();
}
// Are we calling a function?
if (typeof arguments[0] === 'string') {
fn = args.shift();
}
// Are we passing arguments in an Array?
if (args.length == 1 && args[0] instanceof Array) {
args = args[0];
}
this.each(function () {
// Check if the Cardrack has already been instantiated
var thePlugin = $(this).data('plugin_' + pluginName);
// If the Cardrack hasn't been instantiated on this, then instantiate it
if (!thePlugin) {
thePlugin = $(this).data('plugin_' + pluginName, new Plugin(this, options));
}
if (typeof fn === 'string' && typeof Plugin.prototype[fn] === 'function') {
returnValue = thePlugin[fn].apply(thePlugin, args);
}
});
if (fn !== 'get') {
returnValue = this;
}
return returnValue;
};
}(jQuery));
$(document).ready(function () {
$('.cardrack').Cardrack();
});
// Space between Cards on Cardrack
$card-spacing: 1rem;
// Colors
$white: #ffffff;
$grey: #666666;
$purple: #880088;
// Styling Cardrack and Carousel Controls
.cardrack {
display: flex;
position: relative;
transition: height 500ms ease;
overflow: hidden;
flex-flow: row wrap;
justify-content: flex-start;
align-items: stretch;
> h2,
> p {
flex-basis: 100%;
}
&.space-between {
justify-content: space-between;
}
&.space-around {
justify-content: space-around;
}
&.center {
justify-content: center;
}
&[class*="-cols"] {
float: none;
padding: 0;
}
.btns {
display: none;
line-height: 1;
[type=radio] {
display: none;
}
label {
display: inline-block;
transition: all 500ms ease;
z-index: 2;
margin: 0 5px;
border-radius: 50%;
box-shadow: inset 0 0 0 3px $grey, // Grey Ring
inset 0 0 0 6px $white; // White Ring
background-color: $white; // White Dot
cursor: pointer;
width: 20px;
height: 20px;
vertical-align: middle;
}
[type=radio]:checked + label {
box-shadow: inset 0 0 0 3px $grey,// Grey Ring
inset 0 0 0 6px $white; // White Ring
background-color: $grey; // Grey Dot
}
}
[data-action="next"],
[data-action="prev"] {
display: none;
top: 50%;
transform: translateY(-50%);
opacity: .7;
z-index: 2;
cursor: pointer;
width: 50px;
height: 50px;
text-shadow: -1px 0 whitesmoke, 1px 0 whitesmoke;
&::before {
font-size: 5rem;
display: block;
}
}
[data-action="prev"] {
left: 0;
text-align: left;
&::before {
content: '<';
}
}
[data-action="next"] {
right: 0;
text-align: right;
&::before {
content: '>';
}
}
}
// Styling Cards
.cardrack > .card {
position: relative;
margin: $card-spacing / 2;
padding: 1rem 4rem;
flex: 0 0 auto;
> h2 {
font-size: 3rem;
margin: 2rem 0;
text-align: center;
line-height: 3rem;
color: inherit;
font-weight: 300;
}
.center {
text-align: center;
}
img.logo {
display: block;
margin: 1rem auto;
max-height: 75px;
}
}
// Themes for cards
.cardrack {
&.rounded .card,
.card.rounded {
border-radius: 5px;
}
&.bg-white .card,
.card.bg-white {
background: $white;
}
}
// Styling Special Contact Cards
.cardrack > .card.contact-card {
$card-padding: 2rem;
background: $white;
padding: $card-padding;
&.with-img {
padding: 0 0 5px;
overflow: hidden;
> img {
width: 100%;
}
> :not(img) {
margin-right: $card-padding;
margin-left: $card-padding;
&:before {
display: none;
}
}
}
.contact-name {
font-size: 2rem;
margin-top: 1.6rem;
padding: 0 0 3px;
color: $purple;
font-weight: 500;
}
}
// Set Card Widths
@each $size, $m in sm $small, md $medium, lg $large, xl $xlarge {
@media #{$m}{
@for $i from 1 through $total-columns {
// Fit Cards into Set Columns
$card-width-per: percentage($total-columns / $i / $total-columns);
.cardrack.cr-#{$size}-#{$i}-cols > .card:not([class*="-col-"]) {
max-width: calc(#{$card-width-per} - #{$card-spacing});
flex-basis: calc(#{$card-width-per} - #{$card-spacing});
}
$card-width-per: percentage($i / $total-columns);
// Adjust Cards' Individual Min Width and NO Max Width
.cardrack > .card.#{$size}-col-#{$i} {
flex-basis: calc(#{$card-width-per} - #{$card-spacing});
}
// Set Max Width
.cardrack > .card.cr-#{$size}-col-max-#{$i} {
max-width: calc(#{$card-width-per} - #{$card-spacing});
flex-grow: 1;
}
}
}
}
.cardrack[class*="-cols"] {
// Expand to fit column
> .card:not([class*="-col-"]) {
flex-grow: 1;
}
// Let these span more columns
> .card.grow {
flex-grow: 1;
}
}
$gutter-width: 50px;
// Show as Carousel
@each $size, $m in sm $small-only, md $medium-max, lg $large-max, xl $xlarge-max, xxl $small {
@media #{$m} {
.cardrack.carousel-#{$size} {
display: block;
&:not(.cr-no-btns) [data-action="next"],
&:not(.cr-no-btns) [data-action="prev"],
&:not(.cr-no-btns):not([data-btnPos='none']) .btns {
display: block;
position: absolute;
}
// Position of Round Buttons
&[data-btnPos='bottom']:not(.cr-no-btns) {
padding-bottom: 4rem;
.btns {
right: 0;
bottom: 0;
left: 0;
text-align: center;
}
}
&[data-btnPos='top']:not(.cr-no-btns) .btns {
top: 0;
right: 0;
left: 0;
text-align: center;
}
// Space on the sides for the next/prev arrows
&.carousel-gutter {
margin-right: 50px;
margin-left: 50px;
width: calc(100% - 50px * 2);
overflow: visible;
[data-action="prev"] {
left: -50px;
}
[data-action="next"] {
right: -50px;
}
}
// Default Styling for Cards
> .card {
position: absolute;
// Default to one Column
top: 0;
right: 0;
left: 0;
transition: opacity 500ms ease;
opacity: 0;
}
> .card.active {
transition: opacity 500ms ease 300ms;
opacity: 1;
z-index: 1;
}
&.fluid-height > .card.active {
position: static;
margin-right: auto;
margin-left: auto;
}
> .card[class*="-col-"] {
float: none;
}
> .card[class*="-col-max-"] {
max-width: none;
}
}
}
}
// Calculate Widths of Cards to fit within Carousel at different break points
$sizes: sm md lg xl xxl;
$min-mqs: $small $medium $large $xlarge $xxlarge;
$max-mqs: $small-only $medium-max $large-max $xlarge-max $small;
@for $i from 1 through length($sizes) {
$break: false;
$max-crsl-size: nth($sizes, $i);
$media: nth($max-mqs, $i);
@media #{$media} {
@for $j from 1 through length($sizes){
$min-size-cols: nth($sizes, $j);
@if $break == false {
$media: nth($min-mqs, $j);
@media #{$media} {
@for $num-cols from 1 through $total-columns{
$col-span: $total-columns / $num-cols / $total-columns;
$col-span-per: percentage($col-span);
.cardrack.carousel-#{$max-crsl-size}.cr-#{$min-size-cols}-#{$num-cols}-cols > .card {
max-width: calc(#{$col-span-per} - #{$card-spacing});
}
.cardrack.carousel-#{$max-crsl-size}.carousel-gutter.cr-#{$min-size-cols}-#{$num-cols}-cols > .card.#{$min-size-cols}-col-12 {
max-width: 100%;
}
@for $col from 1 through $num-cols {
$col-left: $col-span * ($col - 1);
.cardrack.carousel-#{$max-crsl-size}.cr-#{$min-size-cols}-#{$num-cols}-cols > .card:nth-of-type(#{$num-cols}n + #{$col}) {
left: percentage($col-left);
}
}
}
}
}
@if $min-size-cols == $max-crsl-size {
$break: true;
}
}
}
}
// Update Z-Index on Sticky Nav
.sticky-nav.active {
z-index: 3;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment