'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } !function ($) { /** * Sticky module. * @module foundation.sticky * @requires foundation.util.triggers * @requires foundation.util.mediaQuery */ var Sticky = function () { /** * Creates a new instance of a sticky thing. * @class * @param {jQuery} element - jQuery object to make sticky. * @param {Object} options - options object passed when creating the element programmatically. */ function Sticky(element, options) { _classCallCheck(this, Sticky); this.$element = element; this.options = $.extend({}, Sticky.defaults, this.$element.data(), options); this._init(); Foundation.registerPlugin(this, 'Sticky'); } /** * Initializes the sticky element by adding classes, getting/setting dimensions, breakpoints and attributes * @function * @private */ _createClass(Sticky, [{ key: '_init', value: function _init() { var $parent = this.$element.parent('[data-sticky-container]'), id = this.$element[0].id || Foundation.GetYoDigits(6, 'sticky'), _this = this; if (!$parent.length) { this.wasWrapped = true; } this.$container = $parent.length ? $parent : $(this.options.container).wrapInner(this.$element); this.$container.addClass(this.options.containerClass); this.$element.addClass(this.options.stickyClass).attr({ 'data-resize': id }); this.scrollCount = this.options.checkEvery; this.isStuck = false; $(window).one('load.zf.sticky', function () { if (_this.options.anchor !== '') { _this.$anchor = $('#' + _this.options.anchor); } else { _this._parsePoints(); } _this._setSizes(function () { _this._calc(false); }); _this._events(id.split('-').reverse().join('-')); }); } /** * If using multiple elements as anchors, calculates the top and bottom pixel values the sticky thing should stick and unstick on. * @function * @private */ }, { key: '_parsePoints', value: function _parsePoints() { var top = this.options.topAnchor == "" ? 1 : this.options.topAnchor, btm = this.options.btmAnchor == "" ? document.documentElement.scrollHeight : this.options.btmAnchor, pts = [top, btm], breaks = {}; for (var i = 0, len = pts.length; i < len && pts[i]; i++) { var pt; if (typeof pts[i] === 'number') { pt = pts[i]; } else { var place = pts[i].split(':'), anchor = $('#' + place[0]); pt = anchor.offset().top; if (place[1] && place[1].toLowerCase() === 'bottom') { pt += anchor[0].getBoundingClientRect().height; } } breaks[i] = pt; } this.points = breaks; return; } /** * Adds event handlers for the scrolling element. * @private * @param {String} id - psuedo-random id for unique scroll event listener. */ }, { key: '_events', value: function _events(id) { var _this = this, scrollListener = this.scrollListener = 'scroll.zf.' + id; if (this.isOn) { return; } if (this.canStick) { this.isOn = true; $(window).off(scrollListener).on(scrollListener, function (e) { if (_this.scrollCount === 0) { _this.scrollCount = _this.options.checkEvery; _this._setSizes(function () { _this._calc(false, window.pageYOffset); }); } else { _this.scrollCount--; _this._calc(false, window.pageYOffset); } }); } this.$element.off('resizeme.zf.trigger').on('resizeme.zf.trigger', function (e, el) { _this._setSizes(function () { _this._calc(false); if (_this.canStick) { if (!_this.isOn) { _this._events(id); } } else if (_this.isOn) { _this._pauseListeners(scrollListener); } }); }); } /** * Removes event handlers for scroll and change events on anchor. * @fires Sticky#pause * @param {String} scrollListener - unique, namespaced scroll listener attached to `window` */ }, { key: '_pauseListeners', value: function _pauseListeners(scrollListener) { this.isOn = false; $(window).off(scrollListener); /** * Fires when the plugin is paused due to resize event shrinking the view. * @event Sticky#pause * @private */ this.$element.trigger('pause.zf.sticky'); } /** * Called on every `scroll` event and on `_init` * fires functions based on booleans and cached values * @param {Boolean} checkSizes - true if plugin should recalculate sizes and breakpoints. * @param {Number} scroll - current scroll position passed from scroll event cb function. If not passed, defaults to `window.pageYOffset`. */ }, { key: '_calc', value: function _calc(checkSizes, scroll) { if (checkSizes) { this._setSizes(); } if (!this.canStick) { if (this.isStuck) { this._removeSticky(true); } return false; } if (!scroll) { scroll = window.pageYOffset; } if (scroll >= this.topPoint) { if (scroll <= this.bottomPoint) { if (!this.isStuck) { this._setSticky(); } } else { if (this.isStuck) { this._removeSticky(false); } } } else { if (this.isStuck) { this._removeSticky(true); } } } /** * Causes the $element to become stuck. * Adds `position: fixed;`, and helper classes. * @fires Sticky#stuckto * @function * @private */ }, { key: '_setSticky', value: function _setSticky() { var _this = this, stickTo = this.options.stickTo, mrgn = stickTo === 'top' ? 'marginTop' : 'marginBottom', notStuckTo = stickTo === 'top' ? 'bottom' : 'top', css = {}; css[mrgn] = this.options[mrgn] + 'em'; css[stickTo] = 0; css[notStuckTo] = 'auto'; css['left'] = this.$container.offset().left + parseInt(window.getComputedStyle(this.$container[0])["padding-left"], 10); this.isStuck = true; this.$element.removeClass('is-anchored is-at-' + notStuckTo).addClass('is-stuck is-at-' + stickTo).css(css) /** * Fires when the $element has become `position: fixed;` * Namespaced to `top` or `bottom`, e.g. `sticky.zf.stuckto:top` * @event Sticky#stuckto */ .trigger('sticky.zf.stuckto:' + stickTo); this.$element.on("transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd", function () { _this._setSizes(); }); } /** * Causes the $element to become unstuck. * Removes `position: fixed;`, and helper classes. * Adds other helper classes. * @param {Boolean} isTop - tells the function if the $element should anchor to the top or bottom of its $anchor element. * @fires Sticky#unstuckfrom * @private */ }, { key: '_removeSticky', value: function _removeSticky(isTop) { var stickTo = this.options.stickTo, stickToTop = stickTo === 'top', css = {}, anchorPt = (this.points ? this.points[1] - this.points[0] : this.anchorHeight) - this.elemHeight, mrgn = stickToTop ? 'marginTop' : 'marginBottom', notStuckTo = stickToTop ? 'bottom' : 'top', topOrBottom = isTop ? 'top' : 'bottom'; css[mrgn] = 0; css['bottom'] = 'auto'; if (isTop) { css['top'] = 0; } else { css['top'] = anchorPt; } css['left'] = ''; this.isStuck = false; this.$element.removeClass('is-stuck is-at-' + stickTo).addClass('is-anchored is-at-' + topOrBottom).css(css) /** * Fires when the $element has become anchored. * Namespaced to `top` or `bottom`, e.g. `sticky.zf.unstuckfrom:bottom` * @event Sticky#unstuckfrom */ .trigger('sticky.zf.unstuckfrom:' + topOrBottom); } /** * Sets the $element and $container sizes for plugin. * Calls `_setBreakPoints`. * @param {Function} cb - optional callback function to fire on completion of `_setBreakPoints`. * @private */ }, { key: '_setSizes', value: function _setSizes(cb) { this.canStick = Foundation.MediaQuery.atLeast(this.options.stickyOn); if (!this.canStick) { cb(); } var _this = this, newElemWidth = this.$container[0].getBoundingClientRect().width, comp = window.getComputedStyle(this.$container[0]), pdng = parseInt(comp['padding-right'], 10); if (this.$anchor && this.$anchor.length) { this.anchorHeight = this.$anchor[0].getBoundingClientRect().height; } else { this._parsePoints(); } this.$element.css({ 'max-width': newElemWidth - pdng + 'px' }); var newContainerHeight = this.$element[0].getBoundingClientRect().height || this.containerHeight; if (this.$element.css("display") == "none") { newContainerHeight = 0; } this.containerHeight = newContainerHeight; this.$container.css({ height: newContainerHeight }); this.elemHeight = newContainerHeight; if (this.isStuck) { this.$element.css({ "left": this.$container.offset().left + parseInt(comp['padding-left'], 10) }); } this._setBreakPoints(newContainerHeight, function () { if (cb) { cb(); } }); } /** * Sets the upper and lower breakpoints for the element to become sticky/unsticky. * @param {Number} elemHeight - px value for sticky.$element height, calculated by `_setSizes`. * @param {Function} cb - optional callback function to be called on completion. * @private */ }, { key: '_setBreakPoints', value: function _setBreakPoints(elemHeight, cb) { if (!this.canStick) { if (cb) { cb(); } else { return false; } } var mTop = emCalc(this.options.marginTop), mBtm = emCalc(this.options.marginBottom), topPoint = this.points ? this.points[0] : this.$anchor.offset().top, bottomPoint = this.points ? this.points[1] : topPoint + this.anchorHeight, // topPoint = this.$anchor.offset().top || this.points[0], // bottomPoint = topPoint + this.anchorHeight || this.points[1], winHeight = window.innerHeight; if (this.options.stickTo === 'top') { topPoint -= mTop; bottomPoint -= elemHeight + mTop; } else if (this.options.stickTo === 'bottom') { topPoint -= winHeight - (elemHeight + mBtm); bottomPoint -= winHeight - mBtm; } else { //this would be the stickTo: both option... tricky } this.topPoint = topPoint; this.bottomPoint = bottomPoint; if (cb) { cb(); } } /** * Destroys the current sticky element. * Resets the element to the top position first. * Removes event listeners, JS-added css properties and classes, and unwraps the $element if the JS added the $container. * @function */ }, { key: 'destroy', value: function destroy() { this._removeSticky(true); this.$element.removeClass(this.options.stickyClass + ' is-anchored is-at-top').css({ height: '', top: '', bottom: '', 'max-width': '' }).off('resizeme.zf.trigger'); if (this.$anchor && this.$anchor.length) { this.$anchor.off('change.zf.sticky'); } $(window).off(this.scrollListener); if (this.wasWrapped) { this.$element.unwrap(); } else { this.$container.removeClass(this.options.containerClass).css({ height: '' }); } Foundation.unregisterPlugin(this); } }]); return Sticky; }(); Sticky.defaults = { /** * Customizable container template. Add your own classes for styling and sizing. * @option * @example '<div data-sticky-container class="small-6 columns"></div>' */ container: '
', /** * Location in the view the element sticks to. * @option * @example 'top' */ stickTo: 'top', /** * If anchored to a single element, the id of that element. * @option * @example 'exampleId' */ anchor: '', /** * If using more than one element as anchor points, the id of the top anchor. * @option * @example 'exampleId:top' */ topAnchor: '', /** * If using more than one element as anchor points, the id of the bottom anchor. * @option * @example 'exampleId:bottom' */ btmAnchor: '', /** * Margin, in `em`'s to apply to the top of the element when it becomes sticky. * @option * @example 1 */ marginTop: 1, /** * Margin, in `em`'s to apply to the bottom of the element when it becomes sticky. * @option * @example 1 */ marginBottom: 1, /** * Breakpoint string that is the minimum screen size an element should become sticky. * @option * @example 'medium' */ stickyOn: 'medium', /** * Class applied to sticky element, and removed on destruction. Foundation defaults to `sticky`. * @option * @example 'sticky' */ stickyClass: 'sticky', /** * Class applied to sticky container. Foundation defaults to `sticky-container`. * @option * @example 'sticky-container' */ containerClass: 'sticky-container', /** * Number of scroll events between the plugin's recalculating sticky points. Setting it to `0` will cause it to recalc every scroll event, setting it to `-1` will prevent recalc on scroll. * @option * @example 50 */ checkEvery: -1 }; /** * Helper function to calculate em values * @param Number {em} - number of em's to calculate into pixels */ function emCalc(em) { return parseInt(window.getComputedStyle(document.body, null).fontSize, 10) * em; } // Window exports Foundation.plugin(Sticky, 'Sticky'); }(jQuery);