Last active
December 23, 2015 21:39
-
-
Save corford/6697919 to your computer and use it in GitHub Desktop.
Jquery plugin to fake 'position: sticky'
This file contains hidden or 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
// Jquery plugin to fake 'position: sticky' (note: relies on underscore.js for throttling, debouncing and passing in options) | |
(function ($, _) { | |
$.fn.fakeSticky = function (options) { | |
var opts = _.extend({ | |
width_guide_id: 'fake-sticky-width-guide', // This allows the sticky element to support having a % based width value (useful for responsive designs) | |
pin_boundary_offset: 0, // Increase this if you want to prevent the bottom edge of the sticky element getting too close to the bottom edge of its container | |
scroll_throttle_ms: 15, | |
debounce_timer_ms: 25 | |
}, options); | |
return this.each(function () { | |
var $this = $(this), | |
$offset_parent = $this.offsetParent(), | |
$window = $(window), | |
$document = $(document), | |
$width_guide_el = $('<div id=' + opts.width_guide_id + '></div>').insertBefore($this), | |
initial_top_pos = Math.floor($this.offset().top - parseFloat($this.css('marginTop').replace(/auto/, 0))), | |
pin_boundary_offset = opts.pin_boundary_offset, | |
standard = true, | |
floating = false, | |
pinned = false, | |
scroll = function() { | |
var pos = $this[0].getBoundingClientRect(), | |
top_margin = parseFloat($this.css('marginTop').replace(/auto/, 0)), | |
top = pos.top - top_margin, | |
height = $this.outerHeight(true), | |
parent_height = $offset_parent.height(), | |
parent_top_padding = parseFloat($offset_parent.css('paddingTop').replace(/auto/, 0)), | |
parent_offset_top = $offset_parent.offset().top; | |
if ($offset_parent.height() > $this.outerHeight(true)) { | |
var pin_boundary = Math.ceil(parent_offset_top + parent_top_padding + parent_height - height - pin_boundary_offset), | |
scroll_pos_y = $window.scrollTop(); | |
if ((scroll_pos_y > initial_top_pos) && (scroll_pos_y < pin_boundary) && (floating === false) && (pinned === false)) { | |
floatIt(0); | |
} else if ((scroll_pos_y > initial_top_pos) && (scroll_pos_y < pin_boundary) && (pinned) && (top >= 0)) { | |
floatIt(top); | |
} else if ((scroll_pos_y >= pin_boundary) && (pinned === false)) { | |
pinIt(); | |
} else if ((scroll_pos_y <= initial_top_pos) && (floating)) { | |
restoreIt(); | |
} else { | |
// Noop | |
} | |
} | |
}, | |
resize = function() { | |
$this.css({width: $width_guide_el.width()}); | |
}; | |
function floatIt(top) { | |
var width = $width_guide_el.width(); | |
floating = true; | |
standard = false; | |
pinned = false; | |
$this.css({ | |
position: 'fixed', | |
top: top, | |
bottom: '', | |
width: width | |
}); | |
} | |
function pinIt() { | |
var width = $width_guide_el.width(), | |
bottom_edge = $this.offset().top + $this.outerHeight(), | |
parent_bottom_edge = $offset_parent.offset().top + $offset_parent.outerHeight(), | |
bottom = parent_bottom_edge - bottom_edge; | |
$this.css({ | |
position: 'absolute', | |
top: '', | |
bottom: (bottom >= pin_boundary_offset) ? bottom : pin_boundary_offset, | |
width: width | |
}); | |
pinned = true; | |
} | |
function restoreIt() { | |
standard = true; | |
floating = false; | |
pinned = false; | |
$this.css({ | |
position: '', | |
top: '', | |
bottom: '', | |
width: '' | |
}); | |
} | |
$window.scroll(_.throttle(scroll, opts.scroll_throttle_ms)); | |
$window.resize(_.debounce(resize, opts.debounce_timer_ms)); | |
}); | |
}; | |
})(jQuery, _); | |
// Example: Make a 'filters' div appear to stick to the right of a main results div | |
// and ensure the bottom edge of the filters div never gets closer than 50px to the | |
// bottom edge of its container | |
$('#filters').fakeSticky({pin_boundary_offset: 50}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment