Last active
May 15, 2017 14:58
-
-
Save YodasWs/131fac4d5c302e48de5c7e1a5f81e929 to your computer and use it in GitHub Desktop.
Cardrack: cards on big screens, carousel on small screens
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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–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–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') | |
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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