relatively-sticky

A jQuery plugin for creating smart sticky elements
git clone https://git.ce9e.org/relatively-sticky.git

commit
1279d6917f019d7e1584bda0e3aa9ddd1e020f67
parent
b76110db73cefb81107daa954efe41725f9d97e8
Author
Tobias Bengfort <tobias.bengfort@gmx.net>
Date
2014-12-29 20:36
rewrite

Diffstat

M jquery.sticky-kit.coffee 268 ++++++++++++++++++++++---------------------------------------

1 files changed, 97 insertions, 171 deletions


diff --git a/jquery.sticky-kit.coffee b/jquery.sticky-kit.coffee

@@ -5,205 +5,133 @@
    5     5 $ = @jQuery or window.jQuery
    6     6 
    7     7 win = $ window
   -1     8 
   -1     9 # get top offset relative to any other element
   -1    10 #
   -1    11 # this function has the the following properties:
   -1    12 #
   -1    13 # -  top(a, b) == -top(b, a)
   -1    14 # -  top(a, b) + top(a, c) == top(a, c)
   -1    15 top = (element, context) ->
   -1    16   if context == document
   -1    17     if element == document
   -1    18       return 0
   -1    19     else if element == win
   -1    20       return win.scrollTop()
   -1    21     else
   -1    22       return element.offset().top
   -1    23   else
   -1    24     return top(element, document) - top(context, document)
   -1    25 
   -1    26 
    8    27 $.fn.stick_in_parent = (opts={}) ->
    9    28   {
   10    29     sticky_class
   11    -1     inner_scrolling
   12    -1     recalc_every
   13    30     parent: parent_selector
   -1    31     scrollable: scrollable_selector
   14    32     offset_top
   15    -1     spacer: manual_spacer
   16    33     bottoming: enable_bottoming
   17    34   } = opts
   18    35 
   19    36   offset_top ?= 0
   20    37   parent_selector ?= undefined
   21    -1   inner_scrolling ?= true
   22    38   sticky_class ?= "is_stuck"
   23    39 
   24    40   enable_bottoming = true unless enable_bottoming?
   25    41 
   26    42   for elm in @
   27    -1     ((elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) ->
   -1    43     ((elm, detached, fixed, bottomed) ->
   28    44       return if elm.data "sticky_kit"
   29    45       elm.data "sticky_kit", true
   30    46 
   -1    47       scrollable = win
   -1    48       scrollable = elm.parent().closest(scrollable_selector) if scrollable_selector?
   -1    49       throw "failed to find stick scrollable" unless scrollable.length
   -1    50 
   31    51       parent = elm.parent()
   32    52       parent = parent.closest(parent_selector) if parent_selector?
   33    53       throw "failed to find stick parent" unless parent.length
   34    54 
   35    -1       fixed = false
   36    -1       bottomed = false
   37    -1       spacer = if manual_spacer?
   38    -1         manual_spacer && elm.closest manual_spacer
   39    -1       else
   40    -1         $("<div />")
   -1    55       _top = 0
   41    56 
   42    -1       spacer.css('position', elm.css('position')) if spacer
   -1    57       elm.css("position", "relative")
   43    58 
   44    -1       recalc = ->
   -1    59       tick = ->
   45    60         return if detached
   46    -1         border_top = parseInt parent.css("border-top-width"), 10
   47    -1         padding_top = parseInt parent.css("padding-top"), 10
   48    -1         padding_bottom = parseInt parent.css("padding-bottom"), 10
   49    -1 
   50    -1         parent_top = parent.offset().top + border_top + padding_top
   51    -1         parent_height = parent.height()
   52    -1 
   53    -1         if fixed
   54    -1           fixed = false
   55    -1           bottomed = false
   56    -1 
   57    -1           unless manual_spacer?
   58    -1             elm.insertAfter(spacer)
   59    -1             spacer.detach()
   60    -1 
   61    -1           elm.css({
   62    -1             position: ""
   63    -1             top: ""
   64    -1             width: ""
   65    -1             bottom: ""
   66    -1           }).removeClass(sticky_class)
   67    -1 
   68    -1           restore = true
   69    -1 
   70    -1         top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) or 0) - offset_top
   71    61 
   72    -1         height = elm.outerHeight true
   -1    62         #           | +--------------+
   -1    63         #           | |    border    |
   -1    64         #  original | +--------------+
   -1    65         #    top    | |   padding    |
   -1    66         #           | +--------------+
   -1    67         #           | |              |
   -1    68         #           v |              |
   -1    69         #         |   |+------------+|
   -1    70         #         |   ||  original  ||
   -1    71         #   top   |   ||  element   ||
   -1    72         # maximum |   |+------------+|
   -1    73         #         |   |              |
   -1    74         #         |   |    parent    |
   -1    75         #         |   |              |
   -1    76         #         v   |              |
   -1    77         #             |+------------+|
   -1    78         #             ||   actual   ||
   -1    79         #             ||  element   ||
   -1    80         #             |+------------+|
   -1    81         #             +--------------+
   -1    82         #             |   padding    |
   -1    83         #             +--------------+
   -1    84         #             |    border    |
   -1    85         #             +--------------+
   -1    86 
   -1    87         fixed_old = fixed
   -1    88         bottomed_old = bottomed
   73    89 
   74    -1         el_float = elm.css "float"
   75    -1         spacer.css({
   76    -1           width: elm.outerWidth true
   77    -1           height: height
   78    -1           display: elm.css "display"
   79    -1           "vertical-align": elm.css "vertical-align"
   80    -1           "float": el_float
   81    -1         }) if spacer
   82    -1 
   83    -1         if restore
   84    -1           tick()
   85    -1 
   86    -1       recalc()
   87    -1       return if height == parent_height
   -1    90         padding_bottom = parseInt parent.css("padding-bottom"), 10
   -1    91         border_bottom = parseInt parent.css("border-bottom-width"), 10
   88    92 
   89    -1       last_pos = undefined
   90    -1       offset = offset_top
   -1    93         margin_top = parseInt elm.css("margin-top"), 10
   91    94 
   92    -1       recalc_counter = recalc_every
   -1    95         original_top = top(elm, parent) - _top
   93    96 
   94    -1       tick = ->
   95    -1         return if detached
   96    -1         if recalc_counter?
   97    -1           recalc_counter -= 1
   98    -1           if recalc_counter <= 0
   99    -1             recalc_counter = recalc_every
  100    -1             recalc()
   -1    97         _top = top(scrollable, parent) + offset_top + margin_top - original_top
   -1    98         if enable_bottoming
   -1    99           old = _top
   -1   100           _top = Math.min(_top, parent.outerHeight() - original_top - elm.outerHeight(true) + margin_top - padding_bottom - border_bottom)
   -1   101           bottomed = _top != old
   -1   102         _top = Math.max(_top, 0)
  101   103 
  102    -1         scroll = win.scrollTop()
  103    -1         if last_pos?
  104    -1           delta = scroll - last_pos
  105    -1         last_pos = scroll
   -1   104         fixed = _top != 0
  106   105 
  107   106         if fixed
  108    -1           if enable_bottoming
  109    -1             will_bottom = scroll + height + offset > parent_height + parent_top
  110    -1 
  111    -1             # unbottom
  112    -1             if bottomed && !will_bottom
  113    -1               bottomed = false
  114    -1               elm.css({
  115    -1                 position: "fixed"
  116    -1                 bottom: ""
  117    -1                 top: offset
  118    -1               }).trigger("sticky_kit:unbottom")
  119    -1 
  120    -1           # unfixing
  121    -1           if scroll < top
  122    -1             fixed = false
  123    -1             offset = offset_top
  124    -1 
  125    -1             unless manual_spacer?
  126    -1               if el_float == "left" || el_float == "right"
  127    -1                 elm.insertAfter spacer
  128    -1 
  129    -1               spacer.detach()
  130    -1 
  131    -1             css = {
  132    -1               position: ""
  133    -1               width: ""
  134    -1               top: ""
  135    -1             }
  136    -1             elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick")
  137    -1 
  138    -1           # updated offset
  139    -1           if inner_scrolling
  140    -1             win_height = win.height()
  141    -1             if height + offset_top > win_height # bigger than viewport
  142    -1               unless bottomed
  143    -1                 offset -= delta
  144    -1                 offset = Math.max win_height - height, offset
  145    -1                 offset = Math.min offset_top, offset
  146    -1 
  147    -1                 if fixed
  148    -1                   elm.css {
  149    -1                     top: offset + "px"
  150    -1                   }
  151    -1 
   -1   107             elm.addClass(sticky_class)
  152   108         else
  153    -1           # fixing
  154    -1           if scroll > top
  155    -1             fixed = true
  156    -1             css = {
  157    -1               position: "fixed"
  158    -1               top: offset
  159    -1             }
  160    -1 
  161    -1             css.width = if elm.css("box-sizing") == "border-box"
  162    -1               elm.outerWidth() + "px"
  163    -1             else
  164    -1               elm.width() + "px"
  165    -1 
  166    -1             elm.css(css).addClass(sticky_class)
  167    -1 
  168    -1             unless manual_spacer?
  169    -1               elm.after(spacer)
  170    -1 
  171    -1               if el_float == "left" || el_float == "right"
  172    -1                 spacer.append elm
  173    -1 
  174    -1             elm.trigger("sticky_kit:stick")
  175    -1 
  176    -1         # this is down here because we can fix and bottom in same step when
  177    -1         # scrolling huge
  178    -1         if fixed && enable_bottoming
  179    -1           will_bottom ?= scroll + height + offset > parent_height + parent_top
  180    -1 
  181    -1           # bottomed
  182    -1           if !bottomed && will_bottom
  183    -1             # bottomed out
  184    -1             bottomed = true
  185    -1             if parent.css("position") == "static"
  186    -1               parent.css {
  187    -1                 position: "relative"
  188    -1               }
  189    -1 
  190    -1             elm.css({
  191    -1               position: "absolute"
  192    -1               bottom: padding_bottom
  193    -1               top: "auto"
  194    -1             }).trigger("sticky_kit:bottom")
  195    -1 
  196    -1       recalc_and_tick = ->
  197    -1         recalc()
  198    -1         tick()
   -1   109             elm.removeClass(sticky_class)
   -1   110 
   -1   111         elm.css("top", _top)
   -1   112 
   -1   113         # trigger events
   -1   114         if !fixed && fixed_old
   -1   115           elm.trigger("sticky_kit:unstick")
   -1   116         if !bottomed && bottomed_old
   -1   117           elm.trigger("sticky_kit:unbottom")
   -1   118         if fixed && !fixed_old
   -1   119           elm.trigger("sticky_kit:stick")
   -1   120         else if bottomed && !bottomed_old
   -1   121           elm.trigger("sticky_kit:bottom")
  199   122 
  200   123       detach = ->
  201   124         detached = true
  202   125         win.off "touchmove", tick
  203   126         win.off "scroll", tick
  204    -1         win.off "resize", recalc_and_tick
  205    -1 
  206    -1         $(document.body).off "sticky_kit:recalc", recalc_and_tick
   -1   127         win.off "resize", tick
   -1   128         if scrollable_selector
   -1   129           scrollable.off "touchmove", tick
   -1   130           scrollable.off "scroll", tick
   -1   131           elm.parentsUntil(scrollable).off "touchmove", tick
   -1   132           elm.parentsUntil(scrollable).off "scroll", tick
   -1   133 
   -1   134         $(document.body).off "sticky_kit:recalc", tick
  207   135         elm.off "sticky_kit:detach", detach
  208   136         elm.removeData "sticky_kit"
  209   137 
@@ -211,28 +139,26 @@ $.fn.stick_in_parent = (opts={}) ->
  211   139           position: ""
  212   140           bottom: ""
  213   141           top: ""
  214    -1           width: ""
  215   142         }
  216   143 
  217    -1         parent.position "position", ""
   -1   144         parent.css "position", ""
  218   145 
  219   146         if fixed
  220    -1           unless manual_spacer?
  221    -1             if el_float == "left" || el_float == "right"
  222    -1               elm.insertAfter spacer
  223    -1             spacer.remove()
  224    -1 
  225   147           elm.removeClass sticky_class
  226   148 
   -1   149       parent.css "position", "relative"
  227   150       win.on "touchmove", tick
  228   151       win.on "scroll", tick
  229    -1       win.on "resize", recalc_and_tick
  230    -1       $(document.body).on "sticky_kit:recalc", recalc_and_tick
   -1   152       win.on "resize", tick
   -1   153       if scrollable_selector
   -1   154         scrollable.on "touchmove", tick
   -1   155         scrollable.on "scroll", tick
   -1   156         elm.parentsUntil(scrollable).on "touchmove", tick
   -1   157         elm.parentsUntil(scrollable).on "scroll", tick
   -1   158       $(document.body).on "sticky_kit:recalc", tick
  231   159       elm.on "sticky_kit:detach", detach
  232   160 
  233   161       setTimeout tick, 0
  234   162 
  235   163     ) $ elm
  236   164   @
  237    -1 
  238    -1