sticky-kit
Version:
A jQuery plugin for creating smart sticky elements
266 lines (205 loc) • 7.09 kB
text/coffeescript
@license Sticky-kit v1.1.3 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
$ = @jQuery or window.jQuery
win = $ window
$.fn.stick_in_parent = (opts={}) ->
{
sticky_class
inner_scrolling
recalc_every
parent: parent_selector
offset_top
spacer: manual_spacer
bottoming: enable_bottoming
} = opts
offset_top ?= 0
parent_selector ?= undefined
inner_scrolling ?= true
sticky_class ?= "is_stuck"
doc = $(document)
enable_bottoming = true unless enable_bottoming?
outer_width = (el) ->
if window.getComputedStyle
_el = el[0]
computed = window.getComputedStyle el[0]
w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"))
if computed.getPropertyValue("box-sizing") != "border-box"
w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"))
w
else
el.outerWidth true
for elm in @
((elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) ->
return if elm.data "sticky_kit"
elm.data "sticky_kit", true
last_scroll_height = doc.height()
parent = elm.parent()
parent = parent.closest(parent_selector) if parent_selector?
throw "failed to find stick parent" unless parent.length
fixed = false
bottomed = false
spacer = if manual_spacer?
manual_spacer && elm.closest manual_spacer
else
$("<div />")
spacer.css('position', elm.css('position')) if spacer
recalc = ->
return if detached
last_scroll_height = doc.height()
border_top = parseInt parent.css("border-top-width"), 10
padding_top = parseInt parent.css("padding-top"), 10
padding_bottom = parseInt parent.css("padding-bottom"), 10
parent_top = parent.offset().top + border_top + padding_top
parent_height = parent.height()
if fixed
fixed = false
bottomed = false
unless manual_spacer?
elm.insertAfter(spacer)
spacer.detach()
elm.css({
position: ""
top: ""
width: ""
bottom: ""
}).removeClass(sticky_class)
restore = true
top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) or 0) - offset_top
height = elm.outerHeight true
el_float = elm.css "float"
spacer.css({
width: outer_width elm
height: height
display: elm.css "display"
"vertical-align": elm.css "vertical-align"
"float": el_float
}) if spacer
if restore
tick()
recalc()
return if height == parent_height
last_pos = undefined
offset = offset_top
recalc_counter = recalc_every
tick = ->
return if detached
recalced = false
if recalc_counter?
recalc_counter -= 1
if recalc_counter <= 0
recalc_counter = recalc_every
recalc()
recalced = true
if !recalced && doc.height() != last_scroll_height
recalc()
recalced = true
scroll = win.scrollTop()
if last_pos?
delta = scroll - last_pos
last_pos = scroll
if fixed
if enable_bottoming
will_bottom = scroll + height + offset > parent_height + parent_top
if bottomed && !will_bottom
bottomed = false
elm.css({
position: "fixed"
bottom: ""
top: offset
}).trigger("sticky_kit:unbottom")
if scroll < top
fixed = false
offset = offset_top
unless manual_spacer?
if el_float == "left" || el_float == "right"
elm.insertAfter spacer
spacer.detach()
css = {
position: ""
width: ""
top: ""
}
elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick")
if inner_scrolling
win_height = win.height()
if height + offset_top > win_height
unless bottomed
offset -= delta
offset = Math.max win_height - height, offset
offset = Math.min offset_top, offset
if fixed
elm.css {
top: offset + "px"
}
else
if scroll > top
fixed = true
css = {
position: "fixed"
top: offset
}
css.width = if elm.css("box-sizing") == "border-box"
elm.outerWidth() + "px"
else
elm.width() + "px"
elm.css(css).addClass(sticky_class)
unless manual_spacer?
elm.after(spacer)
if el_float == "left" || el_float == "right"
spacer.append elm
elm.trigger("sticky_kit:stick")
if fixed && enable_bottoming
will_bottom ?= scroll + height + offset > parent_height + parent_top
if !bottomed && will_bottom
bottomed = true
if parent.css("position") == "static"
parent.css {
position: "relative"
}
elm.css({
position: "absolute"
bottom: padding_bottom
top: "auto"
}).trigger("sticky_kit:bottom")
recalc_and_tick = ->
recalc()
tick()
detach = ->
detached = true
win.off "touchmove", tick
win.off "scroll", tick
win.off "resize", recalc_and_tick
$(document.body).off "sticky_kit:recalc", recalc_and_tick
elm.off "sticky_kit:detach", detach
elm.removeData "sticky_kit"
elm.css {
position: ""
bottom: ""
top: ""
width: ""
}
parent.position "position", ""
if fixed
unless manual_spacer?
if el_float == "left" || el_float == "right"
elm.insertAfter spacer
spacer.remove()
elm.removeClass sticky_class
win.on "touchmove", tick
win.on "scroll", tick
win.on "resize", recalc_and_tick
$(document.body).on "sticky_kit:recalc", recalc_and_tick
elm.on "sticky_kit:detach", detach
setTimeout tick, 0
) $ elm
@