├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── Makefile ├── README.md ├── bower.json ├── dist ├── sticky-kit.js └── sticky-kit.min.js ├── package.json ├── site ├── .gitignore ├── example.coffee ├── example.scss ├── examples │ ├── 1.html │ ├── 2.html │ ├── 3.html │ └── 4.html ├── index.html ├── main.coffee ├── main.scss ├── site.moon ├── templates │ ├── example.html │ └── index.html └── www │ ├── .gitignore │ ├── images │ └── sticky_water.png │ └── src │ ├── sticky-kit.js │ └── sticky-kit.min.js ├── spec ├── README.md ├── Tupfile ├── index.coffee └── index.html ├── sticky-kit.coffee ├── sticky-kit.jquery.json └── test ├── README.md ├── index.html ├── style.css ├── tile.png └── two.html /.gitignore: -------------------------------------------------------------------------------- 1 | .tup 2 | node_modules 3 | spec/*.js 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /site 2 | Tupfile 3 | /.tup 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Sticky-Kit 2 | 3 | Before submitting the issue you must acknowledge that you've read the following: 4 | 5 | * [sticky-kit reference manual](http://leafo.net/sticky-kit/#reference) 6 | * [troubleshooting guide](https://github.com/leafo/sticky-kit/wiki/Troubleshooting) 7 | 8 | 9 | Thanks for reporting an issue. Sticky-kit is actually pretty complicated and 10 | handles a lot of sticky scenarios. If the issue isn't immediately obvious when 11 | written in text then you must include a minimal case example of the problem on 12 | or something similar to help save us some time. 13 | 14 | If you're opening a pull request, remember Sticky-kit is written in 15 | [CoffeeScript](http://coffeescript.org/), so you should be applying your patch 16 | to the CoffeeScript file and not the JavaScript files. Don't worry about 17 | building/minifying the JS files, it will automatically be done on the next versioned release. In any case, you 18 | can see how it's done in the 19 | [Makefile](https://github.com/leafo/sticky-kit/blob/master/Makefile). 20 | 21 | Thanks 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | dist/sticky-kit.min.js: dist/sticky-kit.js 3 | closure-compiler --language_in=ECMASCRIPT5 $< > $@ 4 | 5 | dist/sticky-kit.js: sticky-kit.coffee 6 | coffee -p -c $< > $@ 7 | 8 | copy: 9 | cp dist/sticky-kit.js site/www/src/ 10 | cp dist/sticky-kit.min.js site/www/src/ 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sticky-kit 2 | 3 | A jQuery plugin for making smart sticky elements. 4 | 5 | See the homepage for directions and examples: 6 | 7 | Are you having trouble getting something to work? Consult the [troubleshooting guide](https://github.com/leafo/sticky-kit/wiki/Troubleshooting). 8 | 9 | # Installation 10 | 11 | ```bash 12 | $ npm install sticky-kit --save 13 | ``` 14 | 15 | Find `sticky-kit.js` in the `dist/` directory. Make sure to include it after 16 | jQuery. 17 | 18 | ## Bower 19 | 20 | ```bash 21 | $ bower install sticky-kit --save 22 | ``` 23 | 24 | ## Copying 25 | 26 | Include `sticky-kit.js` after you've included jQuery on the page. 27 | 28 | * [sticky-kit.min.js](https://raw.githubusercontent.com/leafo/sticky-kit/master/dist/sticky-kit.min.js) 29 | * [sticky-kit.js](https://raw.githubusercontent.com/leafo/sticky-kit/master/dist/sticky-kit.js) 30 | 31 | # Documentation 32 | 33 | Refer to the homepage: http://leafo.net/sticky-kit/#reference 34 | 35 | # License 36 | 37 | MIT 38 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sticky-kit", 3 | "version": "1.1.3", 4 | "homepage": "http://leafo.net/sticky-kit/", 5 | "authors": [ 6 | "leaf corcoran " 7 | ], 8 | "description": "A jQuery plugin for making smart sticky elements", 9 | "main": "dist/sticky-kit.js", 10 | "keywords": [ 11 | "sticky" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "site", 17 | "test" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /dist/sticky-kit.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | 3 | /** 4 | @license Sticky-kit v1.1.3 | MIT | Leaf Corcoran 2015 | http://leafo.net 5 | */ 6 | 7 | (function() { 8 | var $, win; 9 | 10 | $ = window.jQuery; 11 | 12 | win = $(window); 13 | 14 | $.fn.stick_in_parent = function(opts) { 15 | var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class; 16 | if (opts == null) { 17 | opts = {}; 18 | } 19 | sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming; 20 | if (offset_top == null) { 21 | offset_top = 0; 22 | } 23 | if (parent_selector == null) { 24 | parent_selector = void 0; 25 | } 26 | if (inner_scrolling == null) { 27 | inner_scrolling = true; 28 | } 29 | if (sticky_class == null) { 30 | sticky_class = "is_stuck"; 31 | } 32 | doc = $(document); 33 | if (enable_bottoming == null) { 34 | enable_bottoming = true; 35 | } 36 | outer_width = function(el) { 37 | var _el, computed, w; 38 | if (window.getComputedStyle) { 39 | _el = el[0]; 40 | computed = window.getComputedStyle(el[0]); 41 | w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right")); 42 | if (computed.getPropertyValue("box-sizing") !== "border-box") { 43 | w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right")); 44 | } 45 | return w; 46 | } else { 47 | return el.outerWidth(true); 48 | } 49 | }; 50 | fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) { 51 | var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick; 52 | if (elm.data("sticky_kit")) { 53 | return; 54 | } 55 | elm.data("sticky_kit", true); 56 | last_scroll_height = doc.height(); 57 | parent = elm.parent(); 58 | if (parent_selector != null) { 59 | parent = parent.closest(parent_selector); 60 | } 61 | if (!parent.length) { 62 | throw "failed to find stick parent"; 63 | } 64 | fixed = false; 65 | bottomed = false; 66 | spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("
"); 67 | if (spacer) { 68 | spacer.css('position', elm.css('position')); 69 | } 70 | recalc = function() { 71 | var border_top, padding_top, restore; 72 | if (detached) { 73 | return; 74 | } 75 | last_scroll_height = doc.height(); 76 | border_top = parseInt(parent.css("border-top-width"), 10); 77 | padding_top = parseInt(parent.css("padding-top"), 10); 78 | padding_bottom = parseInt(parent.css("padding-bottom"), 10); 79 | parent_top = parent.offset().top + border_top + padding_top; 80 | parent_height = parent.height(); 81 | if (fixed) { 82 | fixed = false; 83 | bottomed = false; 84 | if (manual_spacer == null) { 85 | elm.insertAfter(spacer); 86 | spacer.detach(); 87 | } 88 | elm.css({ 89 | position: "", 90 | top: "", 91 | width: "", 92 | bottom: "" 93 | }).removeClass(sticky_class); 94 | restore = true; 95 | } 96 | top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top; 97 | height = elm.outerHeight(true); 98 | el_float = elm.css("float"); 99 | if (spacer) { 100 | spacer.css({ 101 | width: outer_width(elm), 102 | height: height, 103 | display: elm.css("display"), 104 | "vertical-align": elm.css("vertical-align"), 105 | "float": el_float 106 | }); 107 | } 108 | if (restore) { 109 | return tick(); 110 | } 111 | }; 112 | recalc(); 113 | if (height === parent_height) { 114 | return; 115 | } 116 | last_pos = void 0; 117 | offset = offset_top; 118 | recalc_counter = recalc_every; 119 | tick = function() { 120 | var css, delta, recalced, scroll, will_bottom, win_height; 121 | if (detached) { 122 | return; 123 | } 124 | recalced = false; 125 | if (recalc_counter != null) { 126 | recalc_counter -= 1; 127 | if (recalc_counter <= 0) { 128 | recalc_counter = recalc_every; 129 | recalc(); 130 | recalced = true; 131 | } 132 | } 133 | if (!recalced && doc.height() !== last_scroll_height) { 134 | recalc(); 135 | recalced = true; 136 | } 137 | scroll = win.scrollTop(); 138 | if (last_pos != null) { 139 | delta = scroll - last_pos; 140 | } 141 | last_pos = scroll; 142 | if (fixed) { 143 | if (enable_bottoming) { 144 | will_bottom = scroll + height + offset > parent_height + parent_top; 145 | if (bottomed && !will_bottom) { 146 | bottomed = false; 147 | elm.css({ 148 | position: "fixed", 149 | bottom: "", 150 | top: offset 151 | }).trigger("sticky_kit:unbottom"); 152 | } 153 | } 154 | if (scroll < top) { 155 | fixed = false; 156 | offset = offset_top; 157 | if (manual_spacer == null) { 158 | if (el_float === "left" || el_float === "right") { 159 | elm.insertAfter(spacer); 160 | } 161 | spacer.detach(); 162 | } 163 | css = { 164 | position: "", 165 | width: "", 166 | top: "" 167 | }; 168 | elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick"); 169 | } 170 | if (inner_scrolling) { 171 | win_height = win.height(); 172 | if (height + offset_top > win_height) { 173 | if (!bottomed) { 174 | offset -= delta; 175 | offset = Math.max(win_height - height, offset); 176 | offset = Math.min(offset_top, offset); 177 | if (fixed) { 178 | elm.css({ 179 | top: offset + "px" 180 | }); 181 | } 182 | } 183 | } 184 | } 185 | } else { 186 | if (scroll > top) { 187 | fixed = true; 188 | css = { 189 | position: "fixed", 190 | top: offset 191 | }; 192 | css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px"; 193 | elm.css(css).addClass(sticky_class); 194 | if (manual_spacer == null) { 195 | elm.after(spacer); 196 | if (el_float === "left" || el_float === "right") { 197 | spacer.append(elm); 198 | } 199 | } 200 | elm.trigger("sticky_kit:stick"); 201 | } 202 | } 203 | if (fixed && enable_bottoming) { 204 | if (will_bottom == null) { 205 | will_bottom = scroll + height + offset > parent_height + parent_top; 206 | } 207 | if (!bottomed && will_bottom) { 208 | bottomed = true; 209 | if (parent.css("position") === "static") { 210 | parent.css({ 211 | position: "relative" 212 | }); 213 | } 214 | return elm.css({ 215 | position: "absolute", 216 | bottom: padding_bottom, 217 | top: "auto" 218 | }).trigger("sticky_kit:bottom"); 219 | } 220 | } 221 | }; 222 | recalc_and_tick = function() { 223 | recalc(); 224 | return tick(); 225 | }; 226 | detach = function() { 227 | detached = true; 228 | win.off("touchmove", tick); 229 | win.off("scroll", tick); 230 | win.off("resize", recalc_and_tick); 231 | $(document.body).off("sticky_kit:recalc", recalc_and_tick); 232 | elm.off("sticky_kit:detach", detach); 233 | elm.removeData("sticky_kit"); 234 | elm.css({ 235 | position: "", 236 | bottom: "", 237 | top: "", 238 | width: "" 239 | }); 240 | parent.position("position", ""); 241 | if (fixed) { 242 | if (manual_spacer == null) { 243 | if (el_float === "left" || el_float === "right") { 244 | elm.insertAfter(spacer); 245 | } 246 | spacer.remove(); 247 | } 248 | return elm.removeClass(sticky_class); 249 | } 250 | }; 251 | win.on("touchmove", tick); 252 | win.on("scroll", tick); 253 | win.on("resize", recalc_and_tick); 254 | $(document.body).on("sticky_kit:recalc", recalc_and_tick); 255 | elm.on("sticky_kit:detach", detach); 256 | return setTimeout(tick, 0); 257 | }; 258 | for (i = 0, len = this.length; i < len; i++) { 259 | elm = this[i]; 260 | fn($(elm)); 261 | } 262 | return this; 263 | }; 264 | 265 | }).call(this); 266 | -------------------------------------------------------------------------------- /dist/sticky-kit.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sticky-kit v1.1.3 | MIT | Leaf Corcoran 2015 | http://leafo.net 3 | */ 4 | (function(){var c,f;c=window.jQuery;f=c(window);c.fn.stick_in_parent=function(b){var A,w,J,n,B,K,p,q,L,k,E,t;null==b&&(b={});t=b.sticky_class;B=b.inner_scrolling;E=b.recalc_every;k=b.parent;q=b.offset_top;p=b.spacer;w=b.bottoming;null==q&&(q=0);null==k&&(k=void 0);null==B&&(B=!0);null==t&&(t="is_stuck");A=c(document);null==w&&(w=!0);L=function(a){var b;return window.getComputedStyle?(a=window.getComputedStyle(a[0]),b=parseFloat(a.getPropertyValue("width"))+parseFloat(a.getPropertyValue("margin-left"))+ 5 | parseFloat(a.getPropertyValue("margin-right")),"border-box"!==a.getPropertyValue("box-sizing")&&(b+=parseFloat(a.getPropertyValue("border-left-width"))+parseFloat(a.getPropertyValue("border-right-width"))+parseFloat(a.getPropertyValue("padding-left"))+parseFloat(a.getPropertyValue("padding-right"))),b):a.outerWidth(!0)};J=function(a,b,n,C,F,u,r,G){var v,H,m,D,I,d,g,x,y,z,h,l;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);I=A.height();g=a.parent();null!=k&&(g=g.closest(k));if(!g.length)throw"failed to find stick parent"; 6 | v=m=!1;(h=null!=p?p&&a.closest(p):c("
"))&&h.css("position",a.css("position"));x=function(){var d,f,e;if(!G&&(I=A.height(),d=parseInt(g.css("border-top-width"),10),f=parseInt(g.css("padding-top"),10),b=parseInt(g.css("padding-bottom"),10),n=g.offset().top+d+f,C=g.height(),m&&(v=m=!1,null==p&&(a.insertAfter(h),h.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(t),e=!0),F=a.offset().top-(parseInt(a.css("margin-top"),10)||0)-q,u=a.outerHeight(!0),r=a.css("float"),h&&h.css({width:L(a), 7 | height:u,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),e))return l()};x();if(u!==C)return D=void 0,d=q,z=E,l=function(){var c,l,e,k;if(!G&&(e=!1,null!=z&&(--z,0>=z&&(z=E,x(),e=!0)),e||A.height()===I||x(),e=f.scrollTop(),null!=D&&(l=e-D),D=e,m?(w&&(k=e+u+d>C+n,v&&!k&&(v=!1,a.css({position:"fixed",bottom:"",top:d}).trigger("sticky_kit:unbottom"))),ec&&!v&&(d-=l,d=Math.max(c-u,d),d=Math.min(q,d),m&&a.css({top:d+"px"})))):e>F&&(m=!0,c={position:"fixed",top:d},c.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(c).addClass(t),null==p&&(a.after(h),"left"!==r&&"right"!==r||h.append(a)),a.trigger("sticky_kit:stick")),m&&w&&(null==k&&(k=e+u+d>C+n),!v&&k)))return v=!0,"static"===g.css("position")&&g.css({position:"relative"}),a.css({position:"absolute",bottom:b,top:"auto"}).trigger("sticky_kit:bottom")}, 9 | y=function(){x();return l()},H=function(){G=!0;f.off("touchmove",l);f.off("scroll",l);f.off("resize",y);c(document.body).off("sticky_kit:recalc",y);a.off("sticky_kit:detach",H);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});g.position("position","");if(m)return null==p&&("left"!==r&&"right"!==r||a.insertAfter(h),h.remove()),a.removeClass(t)},f.on("touchmove",l),f.on("scroll",l),f.on("resize",y),c(document.body).on("sticky_kit:recalc",y),a.on("sticky_kit:detach",H),setTimeout(l, 10 | 0)}};n=0;for(K=this.length;n", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/leafo/sticky-kit/issues" 20 | }, 21 | "homepage": "https://github.com/leafo/sticky-kit#readme", 22 | "keywords": [ 23 | "jquery", 24 | "jquery-plugin", 25 | "sticky" 26 | ], 27 | "devDependencies": { 28 | "jasmine": "^2.5.2", 29 | "jquery": "^3.1.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | .sitegen_cache 2 | www/*.js 3 | www/*.css -------------------------------------------------------------------------------- /site/example.coffee: -------------------------------------------------------------------------------- 1 | 2 | # make it sticky 3 | $ -> 4 | $("[data-sticky_column]").stick_in_parent(parent: "[data-sticky_parent]") 5 | 6 | 7 | reset_scroll = -> 8 | scroller = $("body,html") 9 | scroller.stop(true) 10 | 11 | if $(window).scrollTop() != 0 12 | scroller.animate({ scrollTop: 0}, "fast") 13 | 14 | scroller 15 | 16 | window.scroll_it = -> 17 | max = $(document).height() - $(window).height() 18 | reset_scroll() 19 | .animate({ scrollTop: max }, max*3) 20 | .delay(100) 21 | .animate({ scrollTop: 0 }, max*3) 22 | 23 | window.scroll_it_wobble = -> 24 | max = $(document).height() - $(window).height() 25 | third = Math.floor max / 3 26 | reset_scroll() 27 | .animate({ scrollTop: third * 2 }, max*3) 28 | .delay(100) 29 | .animate({ scrollTop: third }, max*3) 30 | .delay(100) 31 | .animate({ scrollTop: max }, max*3) 32 | .delay(100) 33 | .animate({ scrollTop: 0 }, max*3) 34 | 35 | 36 | $(window).on "resize", (e) => 37 | $(document.body).trigger("sticky_kit:recalc") 38 | -------------------------------------------------------------------------------- /site/example.scss: -------------------------------------------------------------------------------- 1 | 2 | $viewport_height: 200px; 3 | 4 | body { 5 | margin: 0; 6 | font-family: sans-serif; 7 | font-size: 16px; 8 | } 9 | 10 | h1 { 11 | font-size: 30px; 12 | margin: 10px; 13 | } 14 | 15 | .content { 16 | overflow: hidden; 17 | 18 | &.right { 19 | .sidebar { 20 | float: right; 21 | margin: 10px; 22 | margin-left: 0; 23 | } 24 | 25 | .main { 26 | margin: 10px; 27 | margin-right: 220px; 28 | } 29 | } 30 | 31 | &.double { 32 | .main { 33 | margin-left: 430px + 4; 34 | } 35 | } 36 | 37 | .sidebar { 38 | width: 200px; 39 | height: floor($viewport_height / 3); 40 | margin: 10px; 41 | margin-right: 0; 42 | 43 | border: 1px solid red; 44 | float: left; 45 | 46 | overflow: hidden; 47 | font-family: sans-serif; 48 | 49 | &.alt { 50 | height: floor($viewport_height * (2/3)); 51 | } 52 | 53 | &.tall { 54 | height: $viewport_height * 2; 55 | } 56 | 57 | &.medium { 58 | height: $viewport_height * 1.5; 59 | } 60 | 61 | &.flat { 62 | border: 0; 63 | height: auto; 64 | } 65 | } 66 | 67 | .inner { 68 | border: 1px solid red; 69 | height: floor($viewport_height / 3); 70 | margin: 10px 0; 71 | 72 | &.static { 73 | margin-top: 0; 74 | border: 1px solid blue; 75 | } 76 | } 77 | 78 | .item { 79 | display: inline-block; 80 | vertical-align: top; 81 | width: 120px; 82 | border: 1px solid blue; 83 | font-size: 16px; 84 | margin: 10px; 85 | 86 | overflow: hidden; 87 | 88 | &.sticky { 89 | border: 1px solid red; 90 | height: 100px; 91 | } 92 | } 93 | 94 | .inline_columns { 95 | font-size: 0; 96 | } 97 | 98 | .main { 99 | margin: 10px; 100 | margin-left: 220px + 2; 101 | 102 | border: 1px solid blue; 103 | height: $viewport_height * 2; 104 | 105 | overflow: hidden; 106 | 107 | &.short { 108 | height: floor($viewport_height * (2/3)); 109 | } 110 | 111 | &.tall { 112 | height: $viewport_height * 3; 113 | } 114 | } 115 | } 116 | 117 | .footer { 118 | margin: 10px; 119 | text-align: center; 120 | font-size: 13px; 121 | border-top: 1px dashed #dadada; 122 | color: #666; 123 | padding-top: 10px; 124 | min-height: floor($viewport_height * (2/3)); 125 | } 126 | 127 | .sub { 128 | color: #999; 129 | } 130 | 131 | 132 | @media all and (max-width: 500px) { 133 | .content { 134 | .sidebar { 135 | width: 100px; 136 | } 137 | 138 | .item { 139 | width: 60px; 140 | } 141 | 142 | .main { 143 | margin-left: 120px + 2; 144 | } 145 | 146 | &.double { 147 | .main { 148 | margin-left: 230px + 4; 149 | } 150 | } 151 | 152 | &.right { 153 | .main { 154 | margin-right: 120px; 155 | } 156 | } 157 | 158 | 159 | 160 | } 161 | } 162 | 163 | -------------------------------------------------------------------------------- /site/examples/1.html: -------------------------------------------------------------------------------- 1 | 2 |

My Site

3 |
4 | 7 | 8 |
9 | This is the main column 10 |

11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus id 12 | leo et aliquam. Proin consectetur ligula vel neque cursus laoreet. Nullam 13 | dignissim, augue at consectetur pellentesque, metus ipsum interdum 14 | sapien, quis ornare quam enim vel ipsum. 15 |

16 | 17 |

18 | In congue nunc vitae magna 19 | tempor ultrices. Cras ultricies posuere elit. Nullam ultrices purus ante, 20 | at mattis leo placerat ac. Nunc faucibus ligula nec lorem sodales 21 | venenatis. Curabitur nec est condimentum, blandit tellus nec, semper 22 | arcu. Nullam in porta ipsum, non consectetur mi. Sed pharetra sapien 23 | nisl. Aliquam ac lectus sed elit vehicula scelerisque ut vel sem. Ut ut 24 | semper nisl. 25 | 26 | 27 |

28 | Curabitur rhoncus, arcu at placerat volutpat, felis elit sollicitudin ante, sed 29 | tempus justo nibh sed massa. Integer vestibulum non ante ornare eleifend. In 30 | vel mollis dolor. 31 |

32 | 33 | 34 |
35 |
36 | 39 | -------------------------------------------------------------------------------- /site/examples/2.html: -------------------------------------------------------------------------------- 1 | 2 |

My Better Site

3 | 4 |
5 | 6 | 9 | 10 | 14 | 15 |
16 | This is the main column 17 |

18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus id 19 | leo et aliquam. Proin consectetur ligula vel neque cursus laoreet. Nullam 20 | dignissim, augue at consectetur pellentesque, metus ipsum interdum 21 | sapien, quis ornare quam enim vel ipsum. 22 |

23 | 24 |

25 | In congue nunc vitae magna 26 | tempor ultrices. Cras ultricies posuere elit. Nullam ultrices purus ante, 27 | at mattis leo placerat ac. Nunc faucibus ligula nec lorem sodales 28 | venenatis. 29 | 30 | 31 |

32 |
33 | 34 |
35 | 38 | 39 |
40 |
41 | Hello 42 | 43 |

44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus id 45 | leo et aliquam. Proin consectetur ligula vel neque cursus laoreet. 46 |

47 | 48 |
49 |
Sticky
50 | 51 | 52 |
53 | World! 54 | 55 |

56 | In congue nunc vitae magna tempor ultrices. Cras ultricies posuere 57 | elit. Nullam ultrices purus ante, at mattis leo placerat ac. 58 | 59 | 60 |

61 |
62 |
63 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /site/examples/3.html: -------------------------------------------------------------------------------- 1 | 2 |

My Other Site

3 |
4 | 16 | 17 |
18 | This is the main column 19 |

20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus id 21 | leo et aliquam. Proin consectetur ligula vel neque cursus laoreet. Nullam 22 | dignissim, augue at consectetur pellentesque, metus ipsum interdum 23 | sapien, quis ornare quam enim vel ipsum. 24 |

25 |
26 |
27 | 30 | -------------------------------------------------------------------------------- /site/examples/4.html: -------------------------------------------------------------------------------- 1 | 2 |

My Last Site

3 |
4 | 22 | 23 |
24 | This is the main column 25 |

26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus id 27 | leo et aliquam. Proin consectetur ligula vel neque cursus laoreet. Nullam 28 | dignissim, augue at consectetur pellentesque, metus ipsum interdum 29 | sapien, quis ornare quam enim vel ipsum. 30 |

31 |

32 | In congue nunc vitae magna 33 | tempor ultrices. Cras ultricies posuere elit. Nullam ultrices purus ante, 34 | at mattis leo placerat ac. Nunc faucibus ligula nec lorem sodales 35 | venenatis. Curabitur nec est condimentum, blandit tellus nec, semper 36 | arcu. Nullam in porta ipsum, non consectetur mi. Sed pharetra sapien 37 | nisl. Aliquam ac lectus sed elit vehicula scelerisque ut vel sem. Ut ut 38 | semper nisl. 39 | 40 | 41 |

42 | Curabitur rhoncus, arcu at placerat volutpat, felis elit sollicitudin ante, sed 43 | tempus justo nibh sed massa. Integer vestibulum non ante ornare eleifend. In 44 | vel mollis dolor. Curabitur sed est felis. Nam luctus dapibus leo, vitae porta 45 | erat feugiat id. Nullam nulla diam, laoreet a nisl nec, porta sodales quam. 46 | Aenean in sem vitae neque aliquam commodo vitae sit amet sem. Ut commodo 47 | imperdiet lorem non lacinia. Suspendisse fringilla mi enim, at imperdiet sem 48 | tincidunt et. Vivamus sit amet aliquam leo. Nullam cursus ante sed urna 49 | bibendum blandit. Quisque fringilla metus et nisi vehicula, et ultricies ante 50 | ultrices. 51 |

52 |
53 |
54 | 57 | 58 | 61 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | $markdown{[[ 15 | Sticky-kit provides an easy way to attach elements to the page when the user 16 | scrolls such that the element is always visible. The source can be found on 17 | [GitHub](https://github.com/leafo/sticky-kit). 18 | ]]} 19 | 20 |
21 | 22 | 23 |
24 | 25 |

26 | Examples 27 | 28 |

29 | 30 |

Basic Sticking

31 | 32 |
33 | 34 | $markdown{[[ 35 | Just call `stick_in_parent` on the elements you want to be stuck inside of 36 | their parent. Sticky elements "bottom out" so they never leave the container, 37 | no more worrying if a sticky element will accidentally cover your footer. 38 | ]]} 39 | 40 |
41 | 42 | $markdown{[[ 43 | ```js 44 | $("#sidebar").stick_in_parent(); 45 | ``` 46 | ]]} 47 |
48 | 49 |
50 |
Demo Browser
51 |
52 | 53 |
54 |
55 |
56 | 57 | 58 |

Many Sticky Items

59 | 60 |
61 | 62 | $markdown{[[ 63 | Have a lot of sticky columns, or different sticky portions of the page? Call 64 | `stick_in_parent` on all the elements at once. 65 | ]]} 66 | 67 |
68 | 69 | $markdown{[[ 70 | ```js 71 | $(".sticky_column").stick_in_parent(); 72 | ``` 73 | ]]} 74 |
75 | 76 |
77 |
Demo Browser
78 |
79 | 80 |
81 |
82 |
83 | 84 | 85 |
86 | $markdown{[[ 87 | Not sure if your sidebar or your main content is taller? Doesn't matter, just 88 | call `stick_in_parent` on all columns. Sticky-kit will only stick items if they 89 | don't take up the entire height of their parent. 90 | ]]} 91 | 92 |
93 | 94 | $markdown{[[ 95 | ```js 96 | $("#sidebar, #main_column").stick_in_parent(); 97 | ``` 98 | ]]} 99 |
100 | 101 |
102 |
Demo Browser
103 |
104 | 105 |
106 |
107 |
108 | 109 |

Scrollable Sticky Element

110 | 111 |
112 | $markdown{[[ 113 | Sticky elements taller than the viewport can scroll independently up and down, 114 | meaning you don't have to worry about your content being cut off should the 115 | sticky element be too tall or the user's resolution too small. 116 | ]]} 117 | 118 |
119 | 120 | $markdown{[[ 121 | ```js 122 | $("#sidebar").stick_in_parent(); 123 | ``` 124 | ]]} 125 |
126 |
127 |
Demo Browser
128 |
129 | 130 |
131 |
132 |
133 | 134 | 135 |

136 | Reference 137 | 138 |

139 | 140 | $markdown{[[ 141 | 142 | To install include `jquery.sticky-kit.js` after including jQuery. 143 | 144 | Usage: 145 | 146 | ```js 147 | $("#sticky_item").stick_in_parent(); 148 | // or 149 | $("#sticky_item").stick_in_parent(options); 150 | ``` 151 | 152 | You can pass a hash of options to configure how Sticky Kit works. The following 153 | options are accepted, each one is optional: 154 | 155 | * `parent` -- The element will be the parent of the sticky item. The 156 | dimensions of the parent control when the sticky element bottoms out. Defaults 157 | to the closest parent of the sticky element. Can be a selector. 158 | * `inner_scrolling` -- Boolean to enable or disable the ability of the sticky 159 | element to scroll independently of the scrollbar when it's taller than the 160 | viewport. Defaults to `true` for enabled. 161 | * `sticky_class` -- The name of the CSS class to apply to elements when they 162 | have become stuck. Defaults to `"is_stuck"`. 163 | * `offset_top` -- offsets the initial sticking position by of number of pixels, 164 | can be either negative or positive 165 | * `spacer` -- either a selector to use for the spacer element, or `false` to 166 | disable the spacer. The selector is passed to 167 | [`closest`](http://api.jquery.com/closest/), so you should nest the sticky 168 | element within the spacer. Defaults to Stiky Kit creating its own spacer. 169 | * `bottoming` -- Boolean to control whether elements bottom out. Defaults to `true` 170 | * `recalc_every` -- Integer specifying that a recalc should automatically 171 | take place between that many ticks. A tick takes place on every scroll event. 172 | Defaults to never calling recalc on a tick. 173 | 174 | ### Events 175 | 176 | Various events are triggered from a sticky element when its state changes. 177 | They are: 178 | 179 | * `sticky_kit:stick` -- Triggered when element becomes stuck. 180 | * `sticky_kit:unstick` -- Triggered when element becomes unstuck. (Note: an 181 | element is still considered stuck when it has bottomed out) 182 | * `sticky_kit:bottom` -- Triggered when element bottoms out. 183 | * `sticky_kit:unbottom` -- Triggered when element is no longer bottomed out. 184 | 185 | 186 | For example, if we want to log when an element sticks and unsticks we might do: 187 | 188 | ```js 189 | $("#sticky_item").stick_in_parent() 190 | .on("sticky_kit:stick", function(e) { 191 | console.log("has stuck!", e.target); 192 | }) 193 | .on("sticky_kit:unstick", function(e) { 194 | console.log("has unstuck!", e.target); 195 | }); 196 | ``` 197 | 198 | Sticky Kit listens to one event on `document.body`. 199 | 200 | * `sticky_kit:recalc` -- trigger this event to cause all sticky elements to be 201 | recalculated. More information below. 202 | 203 | 204 | Sticky Kit also listens to an event on the sticky elements: 205 | 206 | * `sticky_kit:detach` -- remove sticky kit and restore element to original 207 | position 208 | 209 | If you want to remove sticky kit from an element after applying it you can send 210 | that element a `sticky_kit:detach` event. 211 | 212 | For example: 213 | 214 | ```js 215 | $("#sticky_item").trigger("sticky_kit:detach"); 216 | ``` 217 | 218 | ### Scrolling Performance 219 | 220 | StickyKit takes scrolling performance very seriously. It's built from the 221 | ground up to let you have sticky elements without incurring scroll lag or jank. 222 | 223 | Probably the biggest cause of scrolling lag is `onscroll` handlers that do too much 224 | work, trigger page reflows, etc. StickyKit avoids this by having a very light 225 | scroll handler that operates on cached values. 226 | 227 | If you notice that your sticky element is acting strange, like it pops to the 228 | bottom of the page, or jumps around, then your cached value are most likely 229 | outdated. Luckily it's easy to fix, read *Recalculating Sticky Elements* 230 | below. 231 | 232 | Sticky Kit has two internal callbacks: 233 | 234 | * `recalc` -- Updates the cached sizes of the elements it checks 235 | * `tick` -- Checks and updates if necessary the sticky state from the cached values 236 | 237 | A `tick` happens on every scroll event. It's designed to be as fast as possible. 238 | 239 | A `recalc` happens whenever the cached values need to be updated. It's 240 | (comparatively) slower, so you don't want to run this on every scroll event. A 241 | `recalc` can happen automatically in the following cases: Sticky kit is first 242 | initialized, on the following tick after the height of the document changes. 243 | 244 | You can also manually trigger a `recalc` by sending an event, see below. 245 | 246 | ### Recalculating Sticky Elements 247 | 248 | If you're changing the markup of your page on the fly by removing, adding or 249 | resizing elements then you most likely need to tell Sticky Kit to recalculate 250 | the sticky elements to guarantee they're positioned correctly. 251 | 252 | You can manually cause a recalculation to happen by triggering an event on 253 | `document.body`: 254 | 255 | ```js 256 | $(document.body).trigger("sticky_kit:recalc"); 257 | ``` 258 | 259 | Typically you only need to trigger a recalculation if you are changing the 260 | positions/sizes of elements above the sticky element, adjacent to it, or the 261 | sticky element itself. 262 | 263 | 264 | Instead of manually calling `sticky_kit:recalc` you can use the `recalc_every` 265 | option described above to periodically do a recalculation between ticks. 266 | Setting it to 1 will cause a recalculation to happen on every scroll event, 267 | preventing the state from ever being out of date. 268 | 269 | ```js 270 | $("#sticky_item").stick_in_parent({recalc_every: 1}); 271 | ``` 272 | 273 | ### About Columns 274 | 275 | If you're familiar with HTML and CSS then you probably know there are a handful 276 | of different ways to make columns. Sticky kit works automatically with floated 277 | columns, `inline-block` columns, or absolutely positioned elements. (Non column 278 | elements like toolbars work great as well, for example the toolbar on this 279 | site.) 280 | 281 | ### Browser Support 282 | 283 | Sticky Kit works with all modern browsers, and IE7+. 284 | 285 | Note: only floated columns work in IE7. 286 | 287 | ]]} 288 | 289 |

290 | Changelog 291 | 292 |

293 | 294 | $markdown{[[ 295 | * **v1.1.2** - *Apr 27, 2015* -- Automatically recalc on body scroll height 296 | change, support undefined/auto margin-top in IE. (Norman Chen) 297 | * **v1.1.1** - *Nov 23, 2014* -- Fix issue where tick/recalc could get called 298 | after detach which would cause sticky element to be removed. Full height 299 | calculation takes offset into account (johnwchadwick) 300 | * **v1.1.0** - *Nov 13, 2014* -- Add `recalc_every`, `bottoming`, and 301 | `spacer` options. Fix bug where some events weren't getting removed on 302 | detach. Fixed bug where sticky class was not removed on detach (poziworld), 303 | pull `jQuery` from `window` if it can't be found on `this` (Connor Peet) 304 | * **v1.0.4** - *Mar 29, 2014* -- `touchmove` events for mobile (Alfredo 305 | Motta), support absolutely positioned element (Pierre Spring), `border-box` 306 | element sets spacer correctly (jasonpolito), bug fix for bottomed state (Pierre 307 | Spring) 308 | * **v1.0.2** *Nov 16, 2013* -- Add `sticky_kit:detach`, Bug fixes: remove 309 | stray top attribute when unfixing, fix issue with top when inner scrolling, 310 | handle variable width elements correctly, recalc is called on window resize 311 | * **v1.0.1** *Sept 11, 2013* -- Added offset_top option, fixed recaclc when items are 312 | already stuck 313 | * **v1.0.0** *Aug 1, 2013* -- Initial release 314 | ]]} 315 | 316 | -------------------------------------------------------------------------------- /site/main.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ -> 3 | $("body").on "click", ".example_controls button", (e) => 4 | $(e.currentTarget) 5 | .closest(".example").find("iframe")[0].contentWindow.scroll_it() 6 | 7 | 8 | $(".nav").stick_in_parent().on("sticky_kit:stick", (e) => 9 | setTimeout => 10 | $(e.target).addClass "show_hidden" 11 | , 0 12 | ).on("sticky_kit:unstick", (e) => 13 | setTimeout => 14 | $(e.target).removeClass "show_hidden" 15 | , 0 16 | ) 17 | -------------------------------------------------------------------------------- /site/main.scss: -------------------------------------------------------------------------------- 1 | 2 | $site_width: 700px; 3 | $narrow_site_width: 500px; 4 | $font_size: 18px; 5 | 6 | ::selection { 7 | background: red; 8 | color: white; 9 | } 10 | 11 | @mixin unselectable { 12 | -moz-user-select: none; 13 | -webkit-user-select: none; 14 | user-select: none; 15 | } 16 | 17 | @mixin grad($top, $bottom) { 18 | background-color: mix($top, $bottom); 19 | background-image: linear-gradient(bottom, $bottom 0%, $top 100%); 20 | background-image: -webkit-linear-gradient(bottom, $bottom 0%, $top 100%); 21 | background-image: -moz-linear-gradient(bottom, $bottom 0%, $top 100%); 22 | background-image: -o-linear-gradient(bottom, $bottom 0%, $top 100%); 23 | background-image: -ms-linear-gradient(bottom, $bottom 0%, $top 100%); 24 | } 25 | 26 | @mixin autograd($color, $amount: 10%) { 27 | @include grad($color, darken($color, $amount)); 28 | } 29 | 30 | 31 | body { 32 | font-family: Lato, sans-serif; 33 | font-size: $font_size; 34 | color: #222; 35 | margin: 0; 36 | } 37 | 38 | a { 39 | color: #E73E1D; 40 | &:hover { 41 | color: lighten(#E73E1D, 10%); 42 | } 43 | 44 | &.anchor { 45 | visibility: hidden; 46 | display: block; 47 | position: relative; 48 | top: -100px; 49 | } 50 | } 51 | 52 | h1 { 53 | margin: 40px 0; 54 | } 55 | 56 | p, ul { 57 | line-height: 26px; 58 | margin: 20px 0; 59 | 60 | code { 61 | background: #eee; 62 | padding: 2px 4px; 63 | border-radius: 4px; 64 | border: 1px solid darken(#eee, 5%); 65 | } 66 | } 67 | 68 | 69 | li { 70 | margin: 15px 0; 71 | } 72 | 73 | .header { 74 | margin: 30px 0; 75 | white-space: nowrap; 76 | 77 | h1 { 78 | font-size: 100px; 79 | margin: 0; 80 | } 81 | 82 | h2 { 83 | font-size: 18px; 84 | font-weight: normal; 85 | margin: 0; 86 | color: #666; 87 | } 88 | 89 | .downloader { 90 | $color: #90C497; 91 | 92 | margin-top: 2px; 93 | float: right; 94 | text-align: center; 95 | width: 220px; 96 | 97 | .download_label { 98 | color: #999; 99 | font-size: 14px; 100 | margin-bottom: 10px; 101 | } 102 | 103 | .download_outer { 104 | @include autograd($color, 20%); 105 | border-radius: 10px; 106 | border: 1px solid desaturate(darken($color, 20%), 10%); 107 | box-shadow: inset 0 -1px 0 $color, 0 0 0 4px white, 0 0 0 5px #dadada; 108 | font-size: 0; 109 | } 110 | 111 | a { 112 | font-size: 16px; 113 | color: white; 114 | text-decoration: none; 115 | text-shadow: 0 0 4px darken($color, 20%); 116 | font-weight: bold; 117 | } 118 | 119 | .size { 120 | font-weight: normal; 121 | } 122 | 123 | 124 | .download_row { 125 | display: block; 126 | padding: 10px; 127 | } 128 | 129 | .top_row { 130 | background-color: rgba(255,255,255,0.1); 131 | box-shadow: 0 1px 0 rgba(0,0,0,0.1); 132 | } 133 | } 134 | 135 | .twitter_buttons { 136 | margin-top: 15px; 137 | } 138 | } 139 | 140 | 141 | .nav { 142 | $height: 50px; 143 | 144 | color: white; 145 | height: $height; 146 | line-height: $height; 147 | background: #222; 148 | padding: 0 10px; 149 | z-index: 3; 150 | 151 | box-shadow: 0 0px 0 rgba(0,0,0, 0.1); 152 | 153 | -webkit-transition: box-shadow 0.25s ease-in-out; 154 | -moz-transition: box-shadow 0.25s ease-in-out; 155 | transition: box-shadow 0.25s ease-in-out; 156 | 157 | &.show_hidden { 158 | box-shadow: 0 3px 0 rgba(0,0,0, 0.1); 159 | 160 | .hidden { 161 | opacity: 1; 162 | } 163 | } 164 | 165 | .hidden { 166 | opacity: 0; 167 | -webkit-transition: opacity 0.25s ease-in-out; 168 | -moz-transition: opacity 0.25s ease-in-out; 169 | transition: opacity 0.25s ease-in-out; 170 | } 171 | 172 | .nav_social { 173 | float: right; 174 | } 175 | 176 | a { 177 | display: inline-block; 178 | height: $height; 179 | line-height: $height; 180 | padding: 0 10px; 181 | 182 | color: white; 183 | text-decoration: none; 184 | margin-right: 20px; 185 | 186 | -webkit-transition: background 0.15s ease-in-out; 187 | -moz-transition: background 0.15s ease-in-out; 188 | transition: background 0.15s ease-in-out; 189 | 190 | &:hover { 191 | background-color: #444; 192 | } 193 | } 194 | } 195 | 196 | .body { 197 | width: $site_width; 198 | margin-left: auto; 199 | margin-right: auto; 200 | position: relative; 201 | z-index: 2; 202 | 203 | &:before { 204 | z-index: 1; 205 | position: absolute; 206 | width: 290px; 207 | height: 655px; 208 | left: -290px; 209 | content: " "; 210 | background: url("images/sticky_water.png") 211 | } 212 | } 213 | 214 | 215 | .example { 216 | margin: 40px 0; 217 | 218 | .example_controls { 219 | button { 220 | margin: 13px 10px 0 0; 221 | float: right; 222 | } 223 | } 224 | 225 | iframe { 226 | display: block; 227 | width: $site_width - 8px; 228 | margin: 0 auto; 229 | margin-bottom: 1px; 230 | border: 1px solid silver; 231 | height: 200px; 232 | background: white; 233 | } 234 | 235 | } 236 | 237 | .footer { 238 | background: #eee; 239 | border-top: 1px dashed #dadada; 240 | overflow: hidden; 241 | margin-top: 40px; 242 | font-size: 14px; 243 | color: #444; 244 | box-shadow: inset 0 1px 0 white; 245 | 246 | .inner { 247 | width: $site_width; 248 | margin: 0 auto; 249 | 250 | div { 251 | margin: 10px; 252 | } 253 | } 254 | 255 | a { 256 | color: #222; 257 | &:hover { 258 | color: black; 259 | } 260 | } 261 | } 262 | 263 | 264 | // code 265 | 266 | .highlight { 267 | border: 1px dashed #c7c7c7; 268 | border-radius: 6px; 269 | padding: 10px; 270 | margin: 20px 0; 271 | line-height: 26px; 272 | 273 | box-shadow: 0 0 0 1px #FFF, 0 0 7px #B3B3B3; 274 | } 275 | 276 | /* builtins */ 277 | .nb { 278 | color: #707A34; 279 | } 280 | 281 | /* strings */ 282 | .s, .s1, .s2, .se { 283 | color: #A16D43; 284 | } 285 | 286 | /* proper names, self */ 287 | .nc, .vc, .bp, .kc { 288 | color: #30A0BD; 289 | } 290 | 291 | /* function lit, braces, parens */ 292 | .nf, .kt, .p { 293 | color: #8E4681; 294 | } 295 | 296 | /* operators */ 297 | .o, .si { 298 | color: #B50C0C; 299 | } 300 | 301 | .nv { 302 | color: #ff9898; 303 | color: #893D8C; 304 | } 305 | 306 | /* keywords */ 307 | .k, .kd { 308 | color: #0CB56C; 309 | } 310 | 311 | .c1, .c2 { 312 | color: #D74DBF; 313 | } 314 | 315 | .m, .mi, .mf, .mh { 316 | color: #4958C3; 317 | } 318 | 319 | .window_frame { 320 | overflow: hidden; 321 | margin: 20px 0; 322 | 323 | background: #EDECEB; 324 | 325 | box-shadow: inset 0 0 0 1px #6D6A68, inset 2px 0 0 white, inset 0 0 0 2px #D2D0CE, 0 0 10px rgba(0,0,0, 0.3); 326 | border-radius: 4px 4px 0 0; 327 | 328 | .window_inner { 329 | padding: 2px; 330 | } 331 | 332 | .window_title { 333 | $border: #455D7C; 334 | $top_highlight: #B2CCED; 335 | $left_side_highlight: #9EBDE5; 336 | $right_side_highlight: #7BA2D3; 337 | $bottom_shadow: #4E76A8; 338 | $text_outline: #4E77AB; 339 | 340 | border: 1px solid $border; 341 | border-bottom: 0; 342 | 343 | border-radius: 4px 4px 0 0; 344 | font-size: 14px; 345 | font-weight: bold; 346 | text-align: center; 347 | height: 22px; 348 | line-height: 22px; 349 | background: #99BAE3; 350 | @include grad(#99BAE3, #7AA1D1); 351 | 352 | box-shadow: inset 0 1px 0 0 $top_highlight, inset 1px 0 0 $left_side_highlight, inset -1px 0 0 $right_side_highlight, 0 2px 0 -1px $bottom_shadow; 353 | color: white; 354 | text-shadow: 1px 1px 0 $text_outline, 1px -1px $text_outline, -1px -1px $text_outline, -1px 1px $text_outline; 355 | } 356 | 357 | &.pink { 358 | .window_title { 359 | $border: #94487A; 360 | $top_highlight: #F3B3DD; 361 | $left_side_highlight: #EDA1D3; 362 | $right_side_highlight: #DE7EBD; 363 | $bottom_shadow: #BA5096; 364 | $text_outline: #BD5198; 365 | 366 | border: 1px solid $border; 367 | border-bottom: 0; 368 | 369 | background: #EB99CF; 370 | @include grad(#EB99CF, #DE7EBD); 371 | 372 | box-shadow: inset 0 1px 0 0 $top_highlight, inset 1px 0 0 $left_side_highlight, inset -1px 0 0 $right_side_highlight, 0 2px 0 -1px $bottom_shadow; 373 | text-shadow: 1px 1px 0 $text_outline, 1px -1px $text_outline, -1px -1px $text_outline, -1px 1px $text_outline; 374 | } 375 | } 376 | } 377 | 378 | @media all and (max-width: 1015px) { 379 | .forkme { 380 | display: none; 381 | } 382 | } 383 | 384 | @media all and (max-width: 730px) { 385 | .body, .footer .inner { 386 | width: $narrow_site_width; 387 | } 388 | 389 | .header h1 { 390 | font-size: 52px; 391 | padding-top: 100px; 392 | } 393 | 394 | .nav .nav_social { 395 | display: none; 396 | } 397 | 398 | .example iframe { 399 | width: $narrow_site_width - 8px; 400 | } 401 | 402 | .long_example { 403 | .highlight { 404 | font-size: 15px; 405 | } 406 | } 407 | 408 | } 409 | 410 | -------------------------------------------------------------------------------- /site/site.moon: -------------------------------------------------------------------------------- 1 | require "sitegen" 2 | 3 | tools = require"sitegen.tools" 4 | 5 | exec = (cmd) -> 6 | f = io.popen(cmd) 7 | with f\read "*a" 8 | f\close! 9 | 10 | sitegen.create => 11 | @title = "Sticky-Kit | jQuery plugin for sticky elements" 12 | @version = exec("git tag | tail -1")\lower! 13 | 14 | @full_size = exec("du -bh www/src/sticky-kit.js | cut -f 1")\lower! 15 | @compressed_size = exec("du -bh www/src/sticky-kit.min.js | cut -f 1")\lower! 16 | 17 | deploy_to "leaf@leafo.net", "www/sticky-kit" 18 | 19 | scss = tools.system_command "sassc -I scss < %s > %s", "css" 20 | coffeescript = tools.system_command "coffee -c -s < %s > %s", "js" 21 | 22 | build scss, "main.scss" 23 | build scss, "example.scss" 24 | 25 | build coffeescript, "main.coffee" 26 | build coffeescript, "example.coffee" 27 | 28 | add "index.html" 29 | add "examples/1.html", "examples/2.html", "examples/3.html", 30 | "examples/4.html", template: "example" 31 | -------------------------------------------------------------------------------- /site/templates/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | $body 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /site/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $title 6 | 7 | 8 | 9 | 10 | 11 | 12 | $analytics{"UA-136625-1"} 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 |
Download $version
21 | 31 | 32 | 36 | 37 |
38 |

Sticky-Kit

39 |

A jQuery plugin for making smart sticky elements

40 |
41 | 42 | $body 43 | 44 |
45 | 46 | 56 | 57 | 58 | Fork me on GitHub 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /site/www/.gitignore: -------------------------------------------------------------------------------- 1 | examples/1.html 2 | examples/2.html 3 | examples/3.html 4 | examples/4.html 5 | index.html -------------------------------------------------------------------------------- /site/www/images/sticky_water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leafo/sticky-kit/f306732c28a18ea6b86897ae4f2e5a04c3690e56/site/www/images/sticky_water.png -------------------------------------------------------------------------------- /site/www/src/sticky-kit.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.2 2 | /** 3 | @license Sticky-kit v1.1.2 | MIT | Leaf Corcoran 2015 | http://leafo.net 4 | */ 5 | 6 | 7 | (function() { 8 | var $, win; 9 | 10 | $ = this.jQuery || window.jQuery; 11 | 12 | win = $(window); 13 | 14 | $.fn.stick_in_parent = function(opts) { 15 | var doc, elm, enable_bottoming, inner_scrolling, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class, _fn, _i, _len; 16 | 17 | if (opts == null) { 18 | opts = {}; 19 | } 20 | sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming; 21 | if (offset_top == null) { 22 | offset_top = 0; 23 | } 24 | if (parent_selector == null) { 25 | parent_selector = void 0; 26 | } 27 | if (inner_scrolling == null) { 28 | inner_scrolling = true; 29 | } 30 | if (sticky_class == null) { 31 | sticky_class = "is_stuck"; 32 | } 33 | doc = $(document); 34 | if (enable_bottoming == null) { 35 | enable_bottoming = true; 36 | } 37 | outer_width = function(el) { 38 | var computed, w, _el; 39 | 40 | if (window.getComputedStyle) { 41 | _el = el[0]; 42 | computed = window.getComputedStyle(el[0]); 43 | w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right")); 44 | if (computed.getPropertyValue("box-sizing") !== "border-box") { 45 | w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right")); 46 | } 47 | return w; 48 | } else { 49 | return el.outerWidth(true); 50 | } 51 | }; 52 | _fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) { 53 | var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick; 54 | 55 | if (elm.data("sticky_kit")) { 56 | return; 57 | } 58 | elm.data("sticky_kit", true); 59 | last_scroll_height = doc.height(); 60 | parent = elm.parent(); 61 | if (parent_selector != null) { 62 | parent = parent.closest(parent_selector); 63 | } 64 | if (!parent.length) { 65 | throw "failed to find stick parent"; 66 | } 67 | fixed = false; 68 | bottomed = false; 69 | spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("
"); 70 | if (spacer) { 71 | spacer.css('position', elm.css('position')); 72 | } 73 | recalc = function() { 74 | var border_top, padding_top, restore; 75 | 76 | if (detached) { 77 | return; 78 | } 79 | last_scroll_height = doc.height(); 80 | border_top = parseInt(parent.css("border-top-width"), 10); 81 | padding_top = parseInt(parent.css("padding-top"), 10); 82 | padding_bottom = parseInt(parent.css("padding-bottom"), 10); 83 | parent_top = parent.offset().top + border_top + padding_top; 84 | parent_height = parent.height(); 85 | if (fixed) { 86 | fixed = false; 87 | bottomed = false; 88 | if (manual_spacer == null) { 89 | elm.insertAfter(spacer); 90 | spacer.detach(); 91 | } 92 | elm.css({ 93 | position: "", 94 | top: "", 95 | width: "", 96 | bottom: "" 97 | }).removeClass(sticky_class); 98 | restore = true; 99 | } 100 | top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top; 101 | height = elm.outerHeight(true); 102 | el_float = elm.css("float"); 103 | if (spacer) { 104 | spacer.css({ 105 | width: outer_width(elm), 106 | height: height, 107 | display: elm.css("display"), 108 | "vertical-align": elm.css("vertical-align"), 109 | "float": el_float 110 | }); 111 | } 112 | if (restore) { 113 | return tick(); 114 | } 115 | }; 116 | recalc(); 117 | if (height === parent_height) { 118 | return; 119 | } 120 | last_pos = void 0; 121 | offset = offset_top; 122 | recalc_counter = recalc_every; 123 | tick = function() { 124 | var css, delta, recalced, scroll, will_bottom, win_height; 125 | 126 | if (detached) { 127 | return; 128 | } 129 | recalced = false; 130 | if (recalc_counter != null) { 131 | recalc_counter -= 1; 132 | if (recalc_counter <= 0) { 133 | recalc_counter = recalc_every; 134 | recalc(); 135 | recalced = true; 136 | } 137 | } 138 | if (!recalced && doc.height() !== last_scroll_height) { 139 | recalc(); 140 | recalced = true; 141 | } 142 | scroll = win.scrollTop(); 143 | if (last_pos != null) { 144 | delta = scroll - last_pos; 145 | } 146 | last_pos = scroll; 147 | if (fixed) { 148 | if (enable_bottoming) { 149 | will_bottom = scroll + height + offset > parent_height + parent_top; 150 | if (bottomed && !will_bottom) { 151 | bottomed = false; 152 | elm.css({ 153 | position: "fixed", 154 | bottom: "", 155 | top: offset 156 | }).trigger("sticky_kit:unbottom"); 157 | } 158 | } 159 | if (scroll < top) { 160 | fixed = false; 161 | offset = offset_top; 162 | if (manual_spacer == null) { 163 | if (el_float === "left" || el_float === "right") { 164 | elm.insertAfter(spacer); 165 | } 166 | spacer.detach(); 167 | } 168 | css = { 169 | position: "", 170 | width: "", 171 | top: "" 172 | }; 173 | elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick"); 174 | } 175 | if (inner_scrolling) { 176 | win_height = win.height(); 177 | if (height + offset_top > win_height) { 178 | if (!bottomed) { 179 | offset -= delta; 180 | offset = Math.max(win_height - height, offset); 181 | offset = Math.min(offset_top, offset); 182 | if (fixed) { 183 | elm.css({ 184 | top: offset + "px" 185 | }); 186 | } 187 | } 188 | } 189 | } 190 | } else { 191 | if (scroll > top) { 192 | fixed = true; 193 | css = { 194 | position: "fixed", 195 | top: offset 196 | }; 197 | css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px"; 198 | elm.css(css).addClass(sticky_class); 199 | if (manual_spacer == null) { 200 | elm.after(spacer); 201 | if (el_float === "left" || el_float === "right") { 202 | spacer.append(elm); 203 | } 204 | } 205 | elm.trigger("sticky_kit:stick"); 206 | } 207 | } 208 | if (fixed && enable_bottoming) { 209 | if (will_bottom == null) { 210 | will_bottom = scroll + height + offset > parent_height + parent_top; 211 | } 212 | if (!bottomed && will_bottom) { 213 | bottomed = true; 214 | if (parent.css("position") === "static") { 215 | parent.css({ 216 | position: "relative" 217 | }); 218 | } 219 | return elm.css({ 220 | position: "absolute", 221 | bottom: padding_bottom, 222 | top: "auto" 223 | }).trigger("sticky_kit:bottom"); 224 | } 225 | } 226 | }; 227 | recalc_and_tick = function() { 228 | recalc(); 229 | return tick(); 230 | }; 231 | detach = function() { 232 | detached = true; 233 | win.off("touchmove", tick); 234 | win.off("scroll", tick); 235 | win.off("resize", recalc_and_tick); 236 | $(document.body).off("sticky_kit:recalc", recalc_and_tick); 237 | elm.off("sticky_kit:detach", detach); 238 | elm.removeData("sticky_kit"); 239 | elm.css({ 240 | position: "", 241 | bottom: "", 242 | top: "", 243 | width: "" 244 | }); 245 | parent.position("position", ""); 246 | if (fixed) { 247 | if (manual_spacer == null) { 248 | if (el_float === "left" || el_float === "right") { 249 | elm.insertAfter(spacer); 250 | } 251 | spacer.remove(); 252 | } 253 | return elm.removeClass(sticky_class); 254 | } 255 | }; 256 | win.on("touchmove", tick); 257 | win.on("scroll", tick); 258 | win.on("resize", recalc_and_tick); 259 | $(document.body).on("sticky_kit:recalc", recalc_and_tick); 260 | elm.on("sticky_kit:detach", detach); 261 | return setTimeout(tick, 0); 262 | }; 263 | for (_i = 0, _len = this.length; _i < _len; _i++) { 264 | elm = this[_i]; 265 | _fn($(elm)); 266 | } 267 | return this; 268 | }; 269 | 270 | }).call(this); 271 | -------------------------------------------------------------------------------- /site/www/src/sticky-kit.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sticky-kit v1.1.2 | MIT | Leaf Corcoran 2015 | http://leafo.net 3 | */ 4 | (function(){var c,f;c=this.jQuery||window.jQuery;f=c(window);c.fn.stick_in_parent=function(b){var A,w,B,n,p,J,k,E,t,K,q,L;null==b&&(b={});t=b.sticky_class;B=b.inner_scrolling;E=b.recalc_every;k=b.parent;p=b.offset_top;n=b.spacer;w=b.bottoming;null==p&&(p=0);null==k&&(k=void 0);null==B&&(B=!0);null==t&&(t="is_stuck");A=c(document);null==w&&(w=!0);J=function(a){var b;return window.getComputedStyle?(a=window.getComputedStyle(a[0]),b=parseFloat(a.getPropertyValue("width"))+parseFloat(a.getPropertyValue("margin-left"))+ 5 | parseFloat(a.getPropertyValue("margin-right")),"border-box"!==a.getPropertyValue("box-sizing")&&(b+=parseFloat(a.getPropertyValue("border-left-width"))+parseFloat(a.getPropertyValue("border-right-width"))+parseFloat(a.getPropertyValue("padding-left"))+parseFloat(a.getPropertyValue("padding-right"))),b):a.outerWidth(!0)};K=function(a,b,q,C,F,u,r,G){var v,H,m,D,I,d,g,x,y,z,h,l;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);I=A.height();g=a.parent();null!=k&&(g=g.closest(k));if(!g.length)throw"failed to find stick parent"; 6 | v=m=!1;(h=null!=n?n&&a.closest(n):c("
"))&&h.css("position",a.css("position"));x=function(){var d,f,e;if(!G&&(I=A.height(),d=parseInt(g.css("border-top-width"),10),f=parseInt(g.css("padding-top"),10),b=parseInt(g.css("padding-bottom"),10),q=g.offset().top+d+f,C=g.height(),m&&(v=m=!1,null==n&&(a.insertAfter(h),h.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(t),e=!0),F=a.offset().top-(parseInt(a.css("margin-top"),10)||0)-p,u=a.outerHeight(!0),r=a.css("float"),h&&h.css({width:J(a), 7 | height:u,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),e))return l()};x();if(u!==C)return D=void 0,d=p,z=E,l=function(){var c,l,e,k;if(!G&&(e=!1,null!=z&&(--z,0>=z&&(z=E,x(),e=!0)),e||A.height()===I||x(),e=f.scrollTop(),null!=D&&(l=e-D),D=e,m?(w&&(k=e+u+d>C+q,v&&!k&&(v=!1,a.css({position:"fixed",bottom:"",top:d}).trigger("sticky_kit:unbottom"))),ec&&!v&&(d-=l,d=Math.max(c-u,d),d=Math.min(p,d),m&&a.css({top:d+"px"})))):e>F&&(m=!0,c={position:"fixed",top:d},c.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(c).addClass(t),null==n&&(a.after(h),"left"!==r&&"right"!==r||h.append(a)),a.trigger("sticky_kit:stick")),m&&w&&(null==k&&(k=e+u+d>C+q),!v&&k)))return v=!0,"static"===g.css("position")&&g.css({position:"relative"}),a.css({position:"absolute",bottom:b,top:"auto"}).trigger("sticky_kit:bottom")}, 9 | y=function(){x();return l()},H=function(){G=!0;f.off("touchmove",l);f.off("scroll",l);f.off("resize",y);c(document.body).off("sticky_kit:recalc",y);a.off("sticky_kit:detach",H);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});g.position("position","");if(m)return null==n&&("left"!==r&&"right"!==r||a.insertAfter(h),h.remove()),a.removeClass(t)},f.on("touchmove",l),f.on("scroll",l),f.on("resize",y),c(document.body).on("sticky_kit:recalc",y),a.on("sticky_kit:detach",H),setTimeout(l, 10 | 0)}};q=0;for(L=this.length;q cat %f | coffee -c -s > %o |> index.js 2 | : ../sticky-kit.coffee |> cat %f | coffee -c -s > %o |> sticky-kit.js 3 | -------------------------------------------------------------------------------- /spec/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | animate_scroll = false 3 | at = Array 4 | top = (el) -> el[0].getBoundingClientRect().top 5 | smoothstep = (t) -> t*t*t*(t*(t*6 - 15) + 10) 6 | 7 | describe "sticky columns", -> 8 | ["inline-block", "float"].forEach (type) => 9 | describe type, -> 10 | [ 11 | 12 | [ 13 | "right stick, default align" 14 | """ 15 |
16 |
17 |
18 |
19 | 22 | """ 23 | ] 24 | 25 | [ 26 | "left stick, default align" 27 | """ 28 |
29 |
30 |
31 |
32 | 35 | """ 36 | ] 37 | 38 | [ 39 | "right stick, right align" 40 | """ 41 |
42 |
43 |
44 |
45 | 48 | """ 49 | ] 50 | 51 | [ 52 | "left stick, right align" 53 | """ 54 |
55 |
56 |
57 |
58 | 61 | """ 62 | ] 63 | 64 | ].forEach ([name, html]) => 65 | it name, (done) -> 66 | write_iframe(html).then (f) => 67 | cell = f.find(".stick_cell") 68 | 69 | expect(top cell).toBe 2 70 | expect(cell.css("position")).toBe "static" 71 | expect(cell.is(".is_stuck")).toBe false 72 | 73 | scroll_each f, done, [ 74 | at 1, => 75 | expect(top cell).toBe 1 76 | expect(cell.css("position")).toBe "static" 77 | expect(cell.is(".is_stuck")).toBe false 78 | 79 | at 200, => 80 | expect(top cell).toBe 0 81 | expect(cell.css("position")).toBe "fixed" 82 | expect(cell.css("top")).toBe "0px" 83 | expect(cell.is(".is_stuck")).toBe true 84 | 85 | at 480, => 86 | expect(top cell).toBe -18 # 500 - 480 - 2 87 | expect(cell.css("position")).toBe "absolute" 88 | expect(cell.is(".is_stuck")).toBe true 89 | 90 | at 200, => 91 | expect(top cell).toBe 0 92 | expect(cell.css("position")).toBe "fixed" 93 | expect(cell.css("top")).toBe "0px" 94 | expect(cell.is(".is_stuck")).toBe true 95 | 96 | at 0, => 97 | expect(top cell).toBe 2 98 | expect(cell.css("position")).toBe "static" 99 | expect(cell.is(".is_stuck")).toBe false 100 | ] 101 | 102 | it "multiple", (done) -> 103 | write_iframe(""" 104 |
105 |
106 |
107 |
108 |
109 | 112 | """).then (f) => 113 | a = f.find(".stick_cell.a") 114 | b = f.find(".stick_cell.b") 115 | 116 | scroll_each f, done, [ 117 | at 40, => 118 | [a,b].forEach (el) => 119 | expect(top el).toBe 0 120 | expect(el.css("position")).toBe "fixed" 121 | expect(el.css("top")).toBe "0px" 122 | 123 | at 430, => 124 | expect(top a).toBe 0 125 | expect(a.css("position")).toBe "fixed" 126 | expect(a.css("top")).toBe "0px" 127 | 128 | # b has bottomed 129 | expect(top b).toBe -3 130 | expect(b.css("position")).toBe "absolute" 131 | expect(b.css("bottom")).toBe "0px" 132 | 133 | at 485, => 134 | # both are bottomed 135 | [a, b].forEach (el) => 136 | expect(el.css("position")).toBe "absolute" 137 | expect(el.css("bottom")).toBe "0px" 138 | 139 | expect(top a).toBe -23 140 | expect(top b).toBe -58 141 | 142 | at 440, => 143 | expect(top a).toBe 0 144 | expect(a.css("position")).toBe "fixed" 145 | expect(a.css("top")).toBe "0px" 146 | 147 | # b has bottomed 148 | expect(top b).toBe -13 149 | expect(b.css("position")).toBe "absolute" 150 | expect(b.css("bottom")).toBe "0px" 151 | 152 | at 0, => 153 | [a,b].forEach (el) => 154 | expect(top el).toBe 2 155 | expect(el.css("position")).toBe "static" 156 | expect(el.css("top")).toBe "auto" 157 | ] 158 | 159 | it "padding, margin, border", (done) -> 160 | write_iframe(""" 161 |
162 |
163 |
164 |
165 | 168 | """).then (f) => 169 | cell = f.find(".stick_cell") 170 | 171 | expect(top cell).toBe 24 172 | expect(cell.css("position")).toBe "static" 173 | expect(cell.is(".is_stuck")).toBe false 174 | 175 | scroll_each f, done, [ 176 | at 5, => 177 | expect(top cell).toBe 19 178 | expect(cell.css("position")).toBe "static" 179 | expect(cell.is(".is_stuck")).toBe false 180 | 181 | at 200, => 182 | expect(top cell).toBe 0 183 | expect(cell.css("position")).toBe "fixed" 184 | expect(cell.css("top")).toBe "0px" 185 | expect(cell.is(".is_stuck")).toBe true 186 | 187 | at 490, => 188 | expect(top cell).toBe -6 189 | expect(cell.css("position")).toBe "absolute" 190 | expect(cell.is(".is_stuck")).toBe true 191 | 192 | at 200, => 193 | expect(top cell).toBe 0 194 | expect(cell.css("position")).toBe "fixed" 195 | expect(cell.css("top")).toBe "0px" 196 | expect(cell.is(".is_stuck")).toBe true 197 | 198 | at 0, => 199 | expect(top cell).toBe 24 200 | expect(cell.css("position")).toBe "static" 201 | expect(cell.is(".is_stuck")).toBe false 202 | ] 203 | 204 | 205 | it "inner scrolling", (done) -> 206 | write_iframe(""" 207 |
208 |
209 |
210 |
211 | 214 | """).then (f) => 215 | cell = f.find(".stick_cell") 216 | 217 | expect(top cell).toBe 2 218 | expect(cell.css("position")).toBe "static" 219 | expect(cell.is(".is_stuck")).toBe false 220 | 221 | # f.on "scroll", => console.warn f.scrollTop(), top cell 222 | 223 | scroll_each f, done, [ 224 | # partially scrolled, stuck 225 | at 50, => 226 | expect(top cell).toBe 0 227 | expect(cell.css("position")).toBe "fixed" 228 | expect(cell.is(".is_stuck")).toBe true 229 | 230 | # bottomed to viewport 231 | at 170, => 232 | expect(top cell).toBe -100 233 | expect(cell.css("position")).toBe "fixed" 234 | expect(cell.is(".is_stuck")).toBe true 235 | 236 | # bottomed in container 237 | at 410, => 238 | expect(top cell).toBe -108 239 | expect(cell.css("position")).toBe "absolute" 240 | expect(cell.is(".is_stuck")).toBe true 241 | 242 | # partially scrolled up 243 | at 350, => 244 | expect(top cell).toBe -40 245 | expect(cell.css("position")).toBe "fixed" 246 | expect(cell.is(".is_stuck")).toBe true 247 | 248 | # topped in viewport 249 | at 230, => 250 | expect(top cell).toBe 0 251 | expect(cell.css("position")).toBe "fixed" 252 | expect(cell.is(".is_stuck")).toBe true 253 | 254 | # topped in container 255 | at 0, => 256 | expect(top cell).toBe 2 257 | expect(cell.css("position")).toBe "static" 258 | expect(cell.is(".is_stuck")).toBe false 259 | ] 260 | 261 | 262 | it "recalc", (done) -> 263 | write_iframe(""" 264 |
265 |
266 |
267 |
268 | 274 | """).then (f) => 275 | cell = f.find(".stick_cell") 276 | tall = f.find(".static_cell") 277 | scroll_to f, 125, => 278 | # change the page in a way that sticky kit won't notice 279 | tall.css height: "150vh" 280 | 281 | # element is still incorrectly positioned 282 | expect(top cell).toBe 0 283 | expect(cell.css("position")).toBe "fixed" 284 | expect(cell.css("top")).toBe "0px" 285 | 286 | # fix it 287 | cell.trigger("sticky_kit:recalc") 288 | 289 | # check repiared state 290 | expect(top cell).toBe -13 291 | expect(cell.css("position")).toBe "absolute" 292 | expect(cell.css("bottom")).toBe "0px" 293 | 294 | done() 295 | 296 | 297 | describe "flexbox", -> 298 | # TODO: 299 | ### 300 | it "right stick", (done) -> 301 | write_iframe(""" 302 |
303 |
304 |
305 |
306 | 309 | """).then (f) => 310 | expect(1).toBe 1 311 | done() 312 | ### 313 | 314 | describe "header", -> 315 | it "sticks a header row", (done) -> 316 | write_iframe(""" 317 |
318 |
319 |
320 |
321 | 324 | """).then (f) => 325 | cell = f.find(".stick_cell") 326 | 327 | # f.on "scroll", => console.warn f.scrollTop(), top cell 328 | 329 | expect(top cell).toBe 2 330 | expect(cell.css("position")).toBe "static" 331 | expect(cell.css("top")).toBe "auto" 332 | expect(cell.is(".is_stuck")).toBe false 333 | 334 | scroll_each f, done, [ 335 | at 20, => 336 | expect(top cell).toBe 0 337 | expect(cell.css("position")).toBe "fixed" 338 | expect(cell.css("top")).toBe "0px" 339 | expect(cell.is(".is_stuck")).toBe true 340 | 341 | at 90, => 342 | expect(top cell).toBe 0 343 | expect(cell.css("position")).toBe "fixed" 344 | expect(cell.css("top")).toBe "0px" 345 | expect(cell.is(".is_stuck")).toBe true 346 | 347 | at 125, => 348 | expect(top cell).toBe -23 349 | expect(cell.css("position")).toBe "absolute" 350 | expect(cell.css("top")).toBe "100px" 351 | expect(cell.css("bottom")).toBe "0px" 352 | expect(cell.is(".is_stuck")).toBe true 353 | 354 | at 0, => 355 | expect(top cell).toBe 2 356 | expect(cell.css("position")).toBe "static" 357 | expect(cell.css("top")).toBe "auto" 358 | expect(cell.is(".is_stuck")).toBe false 359 | ] 360 | 361 | 362 | it "sticks with custom spacer", (done) -> 363 | write_iframe(""" 364 |
365 |
366 |
367 |
368 |
369 |
370 | 376 | """).then (f) => 377 | cell = f.find(".stick_cell") 378 | 379 | expect(top cell).toBe 7 380 | expect(cell.css("position")).toBe "static" 381 | expect(cell.css("top")).toBe "auto" 382 | expect(cell.is(".is_stuck")).toBe false 383 | 384 | scroll_each f, done, [ 385 | at 20, => 386 | expect(top cell).toBe 0 387 | expect(cell.css("position")).toBe "fixed" 388 | expect(cell.css("top")).toBe "0px" 389 | expect(cell.is(".is_stuck")).toBe true 390 | 391 | at 100, => 392 | expect(top cell).toBe 0 393 | expect(cell.css("position")).toBe "fixed" 394 | expect(cell.css("top")).toBe "0px" 395 | expect(cell.is(".is_stuck")).toBe true 396 | 397 | at 125, => 398 | expect(top cell).toBe -13 399 | expect(cell.css("position")).toBe "absolute" 400 | expect(cell.css("bottom")).toBe "0px" 401 | expect(cell.is(".is_stuck")).toBe true 402 | ] 403 | 404 | 405 | 406 | describe "options", -> 407 | it "uses custom sticky class", (done) -> 408 | write_iframe(""" 409 |
410 |
411 |
412 |
413 | 416 | 417 | """).then (f) -> 418 | cell = f.find(".stick_cell") 419 | 420 | scroll_each f, done, [ 421 | at 20, => 422 | expect(cell.is(".really_stick")).toBe true 423 | ] 424 | 425 | it "disables bottoming", (done) -> 426 | write_iframe(""" 427 |
428 |
429 |
430 |
431 | 434 | 435 | """).then (f) -> 436 | cell = f.find(".stick_cell") 437 | 438 | # f.on "scroll", => console.warn f.scrollTop(), top cell 439 | 440 | scroll_each f, done, [ 441 | at 2, => 442 | expect(top cell).toBe 0 443 | expect(cell.is(".is_stuck")).toBe false 444 | 445 | at 62, => 446 | expect(top cell).toBe 0 447 | expect(cell.is(".is_stuck")).toBe true 448 | 449 | at 180, => 450 | expect(top cell).toBe 0 451 | expect(cell.is(".is_stuck")).toBe true 452 | 453 | at 62, => 454 | expect(top cell).toBe 0 455 | expect(cell.is(".is_stuck")).toBe true 456 | 457 | at 0, => 458 | expect(top cell).toBe 2 459 | expect(cell.is(".is_stuck")).toBe false 460 | ] 461 | 462 | it "uses offset top", (done) -> 463 | write_iframe(""" 464 |
465 |
466 |
467 |
468 | 471 | 472 | """).then (f) -> 473 | cell = f.find(".stick_cell") 474 | 475 | # f.on "scroll", => console.warn f.scrollTop(), top cell 476 | 477 | scroll_each f, done, [ 478 | at 5, => 479 | expect(top cell).toBe 17 480 | expect(cell.is(".is_stuck")).toBe false 481 | 482 | at 15, => 483 | expect(top cell).toBe 10 484 | expect(cell.is(".is_stuck")).toBe true 485 | 486 | at 40, => 487 | expect(top cell).toBe 10 488 | expect(cell.is(".is_stuck")).toBe true 489 | 490 | at 125, => 491 | expect(top cell).toBe -3 492 | expect(cell.is(".is_stuck")).toBe true 493 | 494 | at 15, => 495 | expect(top cell).toBe 10 496 | expect(cell.is(".is_stuck")).toBe true 497 | 498 | at 0, => 499 | expect(top cell).toBe 22 500 | expect(cell.is(".is_stuck")).toBe false 501 | ] 502 | 503 | 504 | describe "events", -> 505 | it "detects events when scrolling sticky header", (done) -> 506 | write_iframe(""" 507 |
508 |
509 |
510 |
511 | 514 | """).then (f) => 515 | cell = f.find(".stick_cell") 516 | event_log = [] 517 | 518 | f.on "sticky_kit:stick", (e) => event_log.push "stick" 519 | f.on "sticky_kit:unstick", (e) => event_log.push "unstick" 520 | f.on "sticky_kit:bottom", (e) => event_log.push "bottom" 521 | f.on "sticky_kit:unbottom", (e) => event_log.push "unbottom" 522 | 523 | 524 | scroll_each f, done, [ 525 | at 1, => 526 | expect(event_log).toEqual [] 527 | 528 | at 20, => 529 | expect(event_log).toEqual ["stick"] 530 | 531 | at 1, => 532 | expect(event_log).toEqual ["stick", "unstick"] 533 | event_log = [] 534 | 535 | at 20, => 536 | expect(event_log).toEqual ["stick"] 537 | 538 | at 90, => 539 | expect(event_log).toEqual ["stick"] 540 | 541 | at 125, => 542 | expect(event_log).toEqual ["stick", "bottom"] 543 | 544 | at 145, => 545 | expect(event_log).toEqual ["stick", "bottom"] 546 | 547 | at 20, => 548 | expect(event_log).toEqual ["stick", "bottom", "unbottom"] 549 | 550 | at 0, => 551 | expect(event_log).toEqual ["stick", "bottom", "unbottom", "unstick"] 552 | 553 | ] 554 | 555 | 556 | iframe_template = (content) -> """ 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 638 | 639 | 640 | #{content} 641 | 642 | 643 | """ 644 | 645 | write_iframe = (contents, opts={}) -> 646 | { 647 | width 648 | height 649 | } = opts 650 | 651 | width ?= 200 652 | height ?= 100 653 | 654 | drop = $ ".iframe_drop" 655 | drop.html "" 656 | 657 | frame = $ "" 658 | 659 | frame.css { 660 | width: "#{width}px" 661 | height: "#{height}px" 662 | } 663 | 664 | frame.appendTo drop 665 | frame = frame[0] 666 | 667 | out = $.Deferred (d) => 668 | frame.onload = => 669 | contents = $(frame).contents() 670 | # switch to inside jquery 671 | contents = frame.contentWindow.$ contents 672 | d.resolve contents, frame 673 | 674 | frame.contentWindow.document.open() 675 | 676 | frame.contentWindow.onerror = (e) => expect(e).toBe null 677 | 678 | frame.contentWindow.document.write iframe_template contents 679 | frame.contentWindow.document.close() 680 | 681 | out 682 | 683 | animate = (a, b, duration, callback) -> 684 | start = window.performance.now() 685 | tick = -> 686 | window.requestAnimationFrame -> 687 | now = window.performance.now() 688 | p = smoothstep Math.min 1, (now - start) / duration 689 | callback p * (b - a) + a, p 690 | if p < 1 691 | tick() 692 | 693 | tick() 694 | 695 | scroll_to = (f, position, callback) -> 696 | if animate_scroll 697 | start_position = f.scrollTop() 698 | space = Math.abs position - start_position 699 | 700 | animate start_position, position, space, (scroll, p) -> 701 | f.scrollTop Math.floor scroll 702 | if p == 1 703 | setTimeout -> 704 | callback() 705 | , 50 706 | else 707 | win = f[0].defaultView 708 | $(win).one "scroll", => callback() 709 | f.scrollTop position 710 | 711 | scroll_each = (f, done, points) -> 712 | scroll_to_next = -> 713 | next = points.shift() 714 | if next 715 | scroll_to f, next[0], -> 716 | next[1]?() 717 | scroll_to_next() 718 | else 719 | done() 720 | 721 | scroll_to_next() 722 | 723 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sticky kit specs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /sticky-kit.coffee: -------------------------------------------------------------------------------- 1 | ###* 2 | @license Sticky-kit v1.1.4 | MIT | Leaf Corcoran 2015 | http://leafo.net 3 | ### 4 | 5 | $ = window.jQuery 6 | 7 | win = $ window 8 | doc = $ document 9 | 10 | $.fn.stick_in_parent = (opts={}) -> 11 | { 12 | sticky_class 13 | inner_scrolling 14 | recalc_every 15 | parent: parent_selector 16 | offset_top 17 | spacer: manual_spacer 18 | bottoming: enable_bottoming 19 | } = opts 20 | 21 | win_height = win.height() 22 | doc_height = doc.height() 23 | 24 | offset_top ?= 0 25 | parent_selector ?= undefined 26 | inner_scrolling ?= true 27 | sticky_class ?= "is_stuck" 28 | 29 | enable_bottoming = true unless enable_bottoming? 30 | 31 | # we need this because jquery's version (along with css()) rounds everything 32 | outer_width = (el) -> 33 | if window.getComputedStyle 34 | _el = el[0] 35 | computed = window.getComputedStyle el[0] 36 | 37 | w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right")) 38 | 39 | if computed.getPropertyValue("box-sizing") != "border-box" 40 | w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right")) 41 | w 42 | else 43 | el.outerWidth true 44 | 45 | for elm in @ 46 | ((elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) -> 47 | return if elm.data "sticky_kit" 48 | elm.data "sticky_kit", true 49 | 50 | last_scroll_height = doc_height 51 | 52 | parent = elm.parent() 53 | parent = parent.closest(parent_selector) if parent_selector? 54 | throw "failed to find stick parent" unless parent.length 55 | 56 | fixed = false 57 | bottomed = false 58 | spacer = if manual_spacer? 59 | manual_spacer && elm.closest manual_spacer 60 | else 61 | $("
") 62 | 63 | spacer.css('position', elm.css('position')) if spacer 64 | 65 | recalc = -> 66 | return if detached 67 | win_height = win.height(); 68 | doc_height = doc.height(); 69 | last_scroll_height = doc_height 70 | 71 | border_top = parseInt parent.css("border-top-width"), 10 72 | padding_top = parseInt parent.css("padding-top"), 10 73 | padding_bottom = parseInt parent.css("padding-bottom"), 10 74 | 75 | parent_top = parent.offset().top + border_top + padding_top 76 | parent_height = parent.height() 77 | 78 | if fixed 79 | fixed = false 80 | bottomed = false 81 | 82 | unless manual_spacer? 83 | elm.insertAfter(spacer) 84 | spacer.detach() 85 | 86 | elm.css({ 87 | position: "" 88 | top: "" 89 | width: "" 90 | bottom: "" 91 | }).removeClass(sticky_class) 92 | 93 | restore = true 94 | 95 | top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) or 0) - offset_top 96 | 97 | height = elm.outerHeight true 98 | 99 | el_float = elm.css "float" 100 | spacer.css({ 101 | width: outer_width elm 102 | height: height 103 | display: elm.css "display" 104 | "vertical-align": elm.css "vertical-align" 105 | "float": el_float 106 | }) if spacer 107 | 108 | if restore 109 | tick() 110 | 111 | recalc() 112 | return if height == parent_height 113 | 114 | last_pos = undefined 115 | offset = offset_top 116 | 117 | recalc_counter = recalc_every 118 | 119 | tick = -> 120 | return if detached 121 | recalced = false 122 | 123 | if recalc_counter? 124 | recalc_counter -= 1 125 | if recalc_counter <= 0 126 | recalc_counter = recalc_every 127 | recalc() 128 | recalced = true 129 | 130 | if !recalced && doc_height != last_scroll_height 131 | recalc() 132 | recalced = true 133 | 134 | scroll = win.scrollTop() 135 | if last_pos? 136 | delta = scroll - last_pos 137 | last_pos = scroll 138 | 139 | if fixed 140 | if enable_bottoming 141 | will_bottom = scroll + height + offset > parent_height + parent_top 142 | 143 | # unbottom 144 | if bottomed && !will_bottom 145 | bottomed = false 146 | elm.css({ 147 | position: "fixed" 148 | bottom: "" 149 | top: offset 150 | }).trigger("sticky_kit:unbottom") 151 | 152 | # unfixing 153 | if scroll < top 154 | fixed = false 155 | offset = offset_top 156 | 157 | unless manual_spacer? 158 | if el_float == "left" || el_float == "right" 159 | elm.insertAfter spacer 160 | 161 | spacer.detach() 162 | 163 | css = { 164 | position: "" 165 | width: "" 166 | top: "" 167 | } 168 | elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick") 169 | 170 | # updated offset 171 | if inner_scrolling 172 | if height + offset_top > win_height # bigger than viewport 173 | unless bottomed 174 | offset -= delta 175 | offset = Math.max win_height - height, offset 176 | offset = Math.min offset_top, offset 177 | 178 | if fixed 179 | elm.css { 180 | top: offset + "px" 181 | } 182 | 183 | else 184 | # fixing 185 | if scroll > top 186 | fixed = true 187 | css = { 188 | position: "fixed" 189 | top: offset 190 | } 191 | 192 | css.width = if elm.css("box-sizing") == "border-box" 193 | elm.outerWidth() + "px" 194 | else 195 | elm.width() + "px" 196 | 197 | elm.css(css).addClass(sticky_class) 198 | 199 | unless manual_spacer? 200 | elm.after(spacer) 201 | 202 | if el_float == "left" || el_float == "right" 203 | spacer.append elm 204 | 205 | elm.trigger("sticky_kit:stick") 206 | 207 | # this is down here because we can fix and bottom in same step when 208 | # scrolling huge 209 | if fixed && enable_bottoming 210 | will_bottom ?= scroll + height + offset > parent_height + parent_top 211 | 212 | # bottomed 213 | if !bottomed && will_bottom 214 | # bottomed out 215 | bottomed = true 216 | if parent.css("position") == "static" 217 | parent.css { 218 | position: "relative" 219 | } 220 | 221 | elm.css({ 222 | position: "absolute" 223 | bottom: padding_bottom 224 | top: "auto" 225 | }).trigger("sticky_kit:bottom") 226 | 227 | recalc_and_tick = -> 228 | recalc() 229 | tick() 230 | 231 | detach = -> 232 | detached = true 233 | win.off "touchmove", tick 234 | win.off "scroll", tick 235 | win.off "resize", recalc_and_tick 236 | 237 | $(document.body).off "sticky_kit:recalc", recalc_and_tick 238 | elm.off "sticky_kit:detach", detach 239 | elm.removeData "sticky_kit" 240 | 241 | elm.css { 242 | position: "" 243 | bottom: "" 244 | top: "" 245 | width: "" 246 | } 247 | 248 | parent.position "position", "" 249 | 250 | if fixed 251 | unless manual_spacer? 252 | if el_float == "left" || el_float == "right" 253 | elm.insertAfter spacer 254 | spacer.remove() 255 | 256 | elm.removeClass sticky_class 257 | 258 | win.on "touchmove", tick 259 | win.on "scroll", tick 260 | win.on "resize", recalc_and_tick 261 | $(document.body).on "sticky_kit:recalc", recalc_and_tick 262 | elm.on "sticky_kit:detach", detach 263 | 264 | setTimeout tick, 0 265 | 266 | ) $ elm 267 | @ 268 | 269 | 270 | -------------------------------------------------------------------------------- /sticky-kit.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sticky-kit", 3 | "version": "1.1.3", 4 | 5 | "title": "Sticky-kit", 6 | "homepage": "http://leafo.net/sticky-kit", 7 | "description": "A jQuery plugin for making smart sticky elements.", 8 | 9 | "demo": "http://leafo.net/sticky-kit", 10 | 11 | "keywords": [ 12 | "sticky", 13 | "fixed", 14 | "scrolling", 15 | "ui" 16 | ], 17 | 18 | "author": { 19 | "name": "Leaf Corcoran", 20 | "email": "leafot@gmail.com", 21 | "url": "http://leafo.net" 22 | }, 23 | 24 | "licenses": [ 25 | { 26 | "type": "MIT" 27 | } 28 | ], 29 | 30 | "dependencies": { 31 | "jquery": ">=1.8" 32 | } 33 | } -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | 2 | # `test` 3 | 4 | This is the old manual test suite, see `spec` for the updated jasmine one. Once 5 | all tests have been ported this directory will be removed. 6 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sticky kit test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 |

Basic Stick

20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 |

Variable

30 |
31 |
32 |
33 |
34 |
35 | 36 |

Inner Scroll

37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 |

Borders/Margins/Padding

45 |
46 |
47 |
48 |
49 |
50 | 51 |

Right Align

52 |
53 |
54 |
55 |
56 |
57 | 58 |

Events

59 |
60 |
61 |
62 |
63 |
64 | 65 |

Floating Columns

66 |
67 |
68 |
69 |
70 |
71 | 72 |

Floating Columns Right

73 |
74 |
75 |
76 |
77 |
78 | 79 |

Floating Columns Right2

80 |
81 |
82 |
83 |
84 |
85 | 86 |

Floating w/ Margin

87 |
88 |
89 |
90 |
91 |
92 | 93 |

Float & Div

94 |
95 |
96 |
97 |
98 | 99 |

No Stick

100 |
101 |
102 |
103 |
104 | 105 | 106 |

Custom Spacer Block

107 |
108 |
109 |
110 |
111 |
112 |
113 | 114 |

Custom Spacer Float

115 |
116 |
117 |
118 | 119 |
120 |
121 | 122 |
123 | 124 |
125 | 126 |
127 |
128 | 129 |
130 | 131 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /test/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | border: 1px solid red; 3 | } 4 | 5 | .manual_spacer { 6 | background: red; 7 | padding: 10px; 8 | } 9 | 10 | .item, .static { 11 | border: 1px solid blue; 12 | min-width: 200px; 13 | min-height: 200px; 14 | background-image: url("tile.png"); 15 | box-sizing: border-box; 16 | box-shadow: inset 0 30px 20px -20px rgba(0,0,0,0.2), 17 | inset 0 -30px 20px -20px rgba(0,0,0,0.2); 18 | } 19 | 20 | .item { 21 | display: inline-block; 22 | zoom: 1; 23 | *display: inline; 24 | vertical-align: top; 25 | } 26 | 27 | .item.float { 28 | display: block; 29 | border: 1px solid rgb(255,255,0); 30 | float: left; 31 | } 32 | 33 | .item.float.right { 34 | float: right; 35 | } 36 | 37 | .item.block { 38 | display: block; 39 | border: 1px solid rgb(0,255,255); 40 | } 41 | 42 | .clearfix { 43 | overflow: hidden; 44 | display: block; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | } 50 | 51 | .borders { 52 | padding: 40px; 53 | border: 60px solid green; 54 | margin: 80px; 55 | } 56 | 57 | .item.abs { 58 | } 59 | 60 | .is_stuck { 61 | box-shadow: inset 0 30px 20px -20px rgba(0,0,0,0.2), 62 | inset 0 -30px 20px -20px rgba(0,0,0,0.2), 0 0 10px red; 63 | } 64 | 65 | .buttons { 66 | position: fixed; 67 | top: 10px; 68 | right: 10px; 69 | background: rgba(0,0,0, 0.2); 70 | padding: 10px; 71 | z-index: 100; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /test/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leafo/sticky-kit/f306732c28a18ea6b86897ae4f2e5a04c3690e56/test/tile.png -------------------------------------------------------------------------------- /test/two.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 54 | 55 | 56 | 57 | 58 |
59 | hello 60 | 61 | 62 |
63 | 64 |
65 |
66 | The Header 67 |
68 | 69 |
70 |
Hello world
71 |
72 |
Piss World
73 |
Piss World
74 |
Piss World
75 |
Piss World
76 |
Piss World
77 |
Piss World
78 |
Piss World
79 |
Piss World
80 |
Piss World
81 |
Piss World
82 |
Piss World
83 |
Piss World
84 |
Piss World
85 |
Piss World
86 |
Piss World
87 |
Piss World
88 |
Piss World
89 |
Piss World
90 |
Piss World
91 |
Piss World
92 |
Piss World
93 |
Piss World
94 |
Piss World
95 |
Piss World
96 |
97 |
98 |
99 | 111 | 112 | 113 | 114 | --------------------------------------------------------------------------------