From 9910758fe482188d435fa8c10ce61a84139badd9 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Sat, 12 Mar 2016 20:21:47 +0100 Subject: [PATCH] use affix plugin for reliable sticky detection --- assets/_src/js/_faq.js | 20 ++-- assets/_src/js/vendor/affix.js | 162 +++++++++++++++++++++++++++++ assets/_src/less/ascribe/_faq.less | 13 ++- content-main.php | 2 + 4 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 assets/_src/js/vendor/affix.js diff --git a/assets/_src/js/_faq.js b/assets/_src/js/_faq.js index 8438faf..7d93797 100644 --- a/assets/_src/js/_faq.js +++ b/assets/_src/js/_faq.js @@ -1,5 +1,6 @@ //=include vendor/toc.js +//=include vendor/affix.js var Faq = (function(w, d, $) { @@ -27,29 +28,26 @@ var Faq = (function(w, d, $) { _config.faqToc.toc({ 'selectors': _config.faqTitle, // elements to use as headings 'container': 'body', // element to find all selectors in - 'highlightOffset': 200, // offset to trigger the next headline + 'highlightOffset': 100, // offset to trigger the next headline }); }, faqTocSticky: function() { var win = $(window); - win.on('load resize scroll',function(e) { + win.on('load resize',function(e) { if (_private.isWide()) { - if ( win.scrollTop() > _config.faqTocColumn.offset().top) { - _config.faqToc.addClass('sticky'); - } else { - _config.faqToc.removeClass('sticky'); - } - - // TODO: remove sticky when bottom of content has been reached + _config.faqToc.affix({ + offset: { + top: ( _config.faqTocColumn.offset().top + 20 ), + bottom: ( $('.section-faq:last-child').outerHeight(true) + 40 ) + } + }); } }); }, isWide: function() { return $(window).width() >= _config.minWidth; } - - // TODO: actually make section jumps work } app = { diff --git a/assets/_src/js/vendor/affix.js b/assets/_src/js/vendor/affix.js new file mode 100644 index 0000000..b1d8d2c --- /dev/null +++ b/assets/_src/js/vendor/affix.js @@ -0,0 +1,162 @@ +/* ======================================================================== + * Bootstrap: affix.js v3.3.6 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.6' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/assets/_src/less/ascribe/_faq.less b/assets/_src/less/ascribe/_faq.less index 805442c..e88826a 100644 --- a/assets/_src/less/ascribe/_faq.less +++ b/assets/_src/less/ascribe/_faq.less @@ -70,9 +70,8 @@ #toc { margin-top: @spacer; - - &.sticky { - position: fixed; + + &.affix { top: 0; padding-top: @spacer; } @@ -117,3 +116,11 @@ } } } + + +// +// Affix.js helpers +// +.affix { + position: fixed !important; +} diff --git a/content-main.php b/content-main.php index c08d6da..3a8bbbc 100644 --- a/content-main.php +++ b/content-main.php @@ -18,7 +18,9 @@ loopSubtemplates(); + } ?>