├── .gitignore
├── LICENSE.md
├── README.md
├── bower.json
├── example-center.html
├── example-header.html
├── example-right.html
├── jquery.sticky.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | *.psd
2 | *~
3 | node_modules/
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2014-2016 Anthony Garand
2 | http://garand.me
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sticky
2 |
3 | Sticky is a jQuery plugin that gives you the ability to make any element on your page always stay visible.
4 |
5 | ## Sticky in brief
6 |
7 | This is how it works:
8 |
9 | - When the target element is about to be hidden, the plugin will add the class `className` to it (and to a wrapper added as its parent), set it to `position: fixed` and calculate its new `top`, based on the element's height, the page height and the `topSpacing` and `bottomSpacing` options.
10 | - That's it.
11 | In some cases you might need to set a fixed width to your element when it is "sticked".
12 | But by default (`widthFromWrapper == true`) sticky updates elements's width to the wrapper's width.
13 | Check the `example-*.html` files for some examples.
14 |
15 | ## Usage
16 |
17 | - Include jQuery & Sticky.
18 | - Call Sticky.
19 |
20 | ```html
21 |
22 |
23 |
28 | ```
29 |
30 | - Edit your css to position the elements (check the examples in `example-*.html`).
31 |
32 | - To unstick an object
33 |
34 | ```html
35 |
38 | ```
39 |
40 | ## Options
41 |
42 | - `topSpacing`: (default: `0`) Pixels between the page top and the element's top.
43 | - `bottomSpacing`: (default: `0`) Pixels between the page bottom and the element's bottom.
44 | - `className`: (default: `'is-sticky'`) CSS class added to the element's wrapper when "sticked".
45 | - `wrapperClassName`: (default: `'sticky-wrapper'`) CSS class added to the wrapper.
46 | - `center`: (default: `false`) Boolean determining whether the sticky element should be horizontally centered in the page.
47 | - `getWidthFrom`: (default: `''`) Selector of element referenced to set fixed width of "sticky" element.
48 | - `widthFromWrapper`: (default: `true`) Boolean determining whether width of the "sticky" element should be updated to match the wrapper's width. Wrapper is a placeholder for "sticky" element while it is fixed (out of static elements flow), and its width depends on the context and CSS rules. Works only as long `getWidthForm` isn't set.
49 | - `responsiveWidth`: (default: `false`) Boolean determining whether widths will be recalculated on window resize (using getWidthfrom).
50 | - `zIndex`: (default: `inherit`) controls z-index of the sticked element.
51 |
52 | ## Methods
53 |
54 | - `sticky(options)`: Initializer. `options` is optional.
55 | - `sticky('update')`: Recalculates the element's position.
56 |
57 | ## Events
58 |
59 | - `sticky-start`: When the element becomes sticky.
60 | - `sticky-end`: When the element returns to its original location
61 | - `sticky-update`: When the element is sticked but position must be updated for constraints reasons
62 | - `sticky-bottom-reached`: When the element reached the bottom space limit
63 | - `sticky-bottom-unreached`: When the element unreached the bottom space limit
64 |
65 | To subscribe to events use jquery:
66 |
67 | ```html
68 |
75 | ```
76 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-sticky",
3 | "description": "Sticky is a jQuery plugin that gives you the ability to make any element on your page always stay visible.",
4 | "categories": [ "UI", "DOM" ],
5 | "author": {
6 | "name": "Anthony Garand"
7 | },
8 | "contributors": [
9 | {
10 | "name": "German M. Bravo"
11 | },
12 | {
13 | "name": "Ruud Kamphuis"
14 | },
15 | {
16 | "name": "Leonardo C. Daronco"
17 | }
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/garand/sticky.git"
22 | },
23 | "bugs": {
24 | "web": "https://github.com/garand/sticky/issues"
25 | },
26 | "licenses": [
27 | {
28 | "type": "MIT",
29 | "url": "http://jquery.com/blob/master/MIT-LICENSE.txt"
30 | },
31 | {
32 | "type": "GPL",
33 | "url": "http://jquery.com/blob/master/GPL-LICENSE.txt"
34 | }
35 | ],
36 | "github": "https://github.com/garand/sticky",
37 |
38 | "main": "jquery.sticky.js",
39 | "dependencies": {
40 | "jquery": null
41 | },
42 | "ignore": []
43 | }
44 |
--------------------------------------------------------------------------------
/example-center.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sticky Plugin
5 |
6 |
7 |
12 |
34 |
35 |
36 | This is test this is text this is text at the top.
37 |
38 |
This is the sticky thingy that is really cool.
39 |
40 | This is test this is text this is text at the bottom.
41 |
42 |
43 |
--------------------------------------------------------------------------------
/example-header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sticky Plugin
5 |
6 |
7 |
12 |
33 |
34 |
35 | This is test this is text this is text at the top.
36 |
39 | This is test this is text this is text at the bottom.
40 |
41 |
42 |
--------------------------------------------------------------------------------
/example-right.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sticky Plugin
5 |
6 |
7 |
12 |
39 |
40 |
41 |
42 |
This is test this is text this is text at the top.
43 |
44 |
This is the sticky thingy that is really cool.
45 |
46 |
This is test this is text this is text at the bottom.
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/jquery.sticky.js:
--------------------------------------------------------------------------------
1 | // Sticky Plugin v1.0.4 for jQuery
2 | // =============
3 | // Author: Anthony Garand
4 | // Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk)
5 | // Improvements by Leonardo C. Daronco (daronco)
6 | // Created: 02/14/2011
7 | // Date: 07/20/2015
8 | // Website: http://stickyjs.com/
9 | // Description: Makes an element on the page stick on the screen as you scroll
10 | // It will only set the 'top' and 'position' of your element, you
11 | // might need to adjust the width in some cases.
12 |
13 | (function (factory) {
14 | if (typeof define === 'function' && define.amd) {
15 | // AMD. Register as an anonymous module.
16 | define(['jquery'], factory);
17 | } else if (typeof module === 'object' && module.exports) {
18 | // Node/CommonJS
19 | module.exports = factory(require('jquery'));
20 | } else {
21 | // Browser globals
22 | factory(jQuery);
23 | }
24 | }(function ($) {
25 | var slice = Array.prototype.slice; // save ref to original slice()
26 | var splice = Array.prototype.splice; // save ref to original slice()
27 |
28 | var defaults = {
29 | topSpacing: 0,
30 | bottomSpacing: 0,
31 | className: 'is-sticky',
32 | wrapperClassName: 'sticky-wrapper',
33 | center: false,
34 | getWidthFrom: '',
35 | widthFromWrapper: true, // works only when .getWidthFrom is empty
36 | responsiveWidth: false,
37 | zIndex: 'inherit'
38 | },
39 | $window = $(window),
40 | $document = $(document),
41 | sticked = [],
42 | windowHeight = $window.height(),
43 | scroller = function() {
44 | var scrollTop = $window.scrollTop(),
45 | documentHeight = $document.height(),
46 | dwh = documentHeight - windowHeight,
47 | extra = (scrollTop > dwh) ? dwh - scrollTop : 0;
48 |
49 | for (var i = 0, l = sticked.length; i < l; i++) {
50 | var s = sticked[i],
51 | elementTop = s.stickyWrapper.offset().top,
52 | etse = elementTop - s.topSpacing - extra;
53 |
54 | //update height in case of dynamic content
55 | s.stickyWrapper.css('height', s.stickyElement.outerHeight());
56 |
57 | if (scrollTop <= etse) {
58 | if (s.currentTop !== null) {
59 | s.stickyElement
60 | .css({
61 | 'width': '',
62 | 'position': '',
63 | 'top': '',
64 | 'z-index': ''
65 | });
66 | s.stickyElement.parent().removeClass(s.className);
67 | s.stickyElement.trigger('sticky-end', [s]);
68 | s.currentTop = null;
69 | }
70 | }
71 | else {
72 | var newTop = documentHeight - s.stickyElement.outerHeight()
73 | - s.topSpacing - s.bottomSpacing - scrollTop - extra;
74 | if (newTop < 0) {
75 | newTop = newTop + s.topSpacing;
76 | } else {
77 | newTop = s.topSpacing;
78 | }
79 | if (s.currentTop !== newTop) {
80 | var newWidth;
81 | if (s.getWidthFrom) {
82 | padding = s.stickyElement.innerWidth() - s.stickyElement.width();
83 | newWidth = $(s.getWidthFrom).width() - padding || null;
84 | } else if (s.widthFromWrapper) {
85 | newWidth = s.stickyWrapper.width();
86 | }
87 | if (newWidth == null) {
88 | newWidth = s.stickyElement.width();
89 | }
90 | s.stickyElement
91 | .css('width', newWidth)
92 | .css('position', 'fixed')
93 | .css('top', newTop)
94 | .css('z-index', s.zIndex);
95 |
96 | s.stickyElement.parent().addClass(s.className);
97 |
98 | if (s.currentTop === null) {
99 | s.stickyElement.trigger('sticky-start', [s]);
100 | } else {
101 | // sticky is started but it have to be repositioned
102 | s.stickyElement.trigger('sticky-update', [s]);
103 | }
104 |
105 | if (s.currentTop === s.topSpacing && s.currentTop > newTop || s.currentTop === null && newTop < s.topSpacing) {
106 | // just reached bottom || just started to stick but bottom is already reached
107 | s.stickyElement.trigger('sticky-bottom-reached', [s]);
108 | } else if(s.currentTop !== null && newTop === s.topSpacing && s.currentTop < newTop) {
109 | // sticky is started && sticked at topSpacing && overflowing from top just finished
110 | s.stickyElement.trigger('sticky-bottom-unreached', [s]);
111 | }
112 |
113 | s.currentTop = newTop;
114 | }
115 |
116 | // Check if sticky has reached end of container and stop sticking
117 | var stickyWrapperContainer = s.stickyWrapper.parent();
118 | var unstick = (s.stickyElement.offset().top + s.stickyElement.outerHeight() >= stickyWrapperContainer.offset().top + stickyWrapperContainer.outerHeight()) && (s.stickyElement.offset().top <= s.topSpacing);
119 |
120 | if( unstick ) {
121 | s.stickyElement
122 | .css('position', 'absolute')
123 | .css('top', '')
124 | .css('bottom', 0)
125 | .css('z-index', '');
126 | } else {
127 | s.stickyElement
128 | .css('position', 'fixed')
129 | .css('top', newTop)
130 | .css('bottom', '')
131 | .css('z-index', s.zIndex);
132 | }
133 | }
134 | }
135 | },
136 | resizer = function() {
137 | windowHeight = $window.height();
138 |
139 | for (var i = 0, l = sticked.length; i < l; i++) {
140 | var s = sticked[i];
141 | var newWidth = null;
142 | if (s.getWidthFrom) {
143 | if (s.responsiveWidth) {
144 | newWidth = $(s.getWidthFrom).width();
145 | }
146 | } else if(s.widthFromWrapper) {
147 | newWidth = s.stickyWrapper.width();
148 | }
149 | if (newWidth != null) {
150 | s.stickyElement.css('width', newWidth);
151 | }
152 | }
153 | },
154 | methods = {
155 | init: function(options) {
156 | return this.each(function() {
157 | var o = $.extend({}, defaults, options);
158 | var stickyElement = $(this);
159 |
160 | var stickyId = stickyElement.attr('id');
161 | var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName;
162 | var wrapper = $('')
163 | .attr('id', wrapperId)
164 | .addClass(o.wrapperClassName);
165 |
166 | stickyElement.wrapAll(function() {
167 | if ($(this).parent("#" + wrapperId).length == 0) {
168 | return wrapper;
169 | }
170 | });
171 |
172 | var stickyWrapper = stickyElement.parent();
173 |
174 | if (o.center) {
175 | stickyWrapper.css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"});
176 | }
177 |
178 | if (stickyElement.css("float") === "right") {
179 | stickyElement.css({"float":"none"}).parent().css({"float":"right"});
180 | }
181 |
182 | o.stickyElement = stickyElement;
183 | o.stickyWrapper = stickyWrapper;
184 | o.currentTop = null;
185 |
186 | sticked.push(o);
187 |
188 | methods.setWrapperHeight(this);
189 | methods.setupChangeListeners(this);
190 | });
191 | },
192 |
193 | setWrapperHeight: function(stickyElement) {
194 | var element = $(stickyElement);
195 | var stickyWrapper = element.parent();
196 | if (stickyWrapper) {
197 | stickyWrapper.css('height', element.outerHeight());
198 | }
199 | },
200 |
201 | setupChangeListeners: function(stickyElement) {
202 | if (window.MutationObserver) {
203 | var mutationObserver = new window.MutationObserver(function(mutations) {
204 | if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) {
205 | methods.setWrapperHeight(stickyElement);
206 | }
207 | });
208 | mutationObserver.observe(stickyElement, {subtree: true, childList: true});
209 | } else {
210 | if (window.addEventListener) {
211 | stickyElement.addEventListener('DOMNodeInserted', function() {
212 | methods.setWrapperHeight(stickyElement);
213 | }, false);
214 | stickyElement.addEventListener('DOMNodeRemoved', function() {
215 | methods.setWrapperHeight(stickyElement);
216 | }, false);
217 | } else if (window.attachEvent) {
218 | stickyElement.attachEvent('onDOMNodeInserted', function() {
219 | methods.setWrapperHeight(stickyElement);
220 | });
221 | stickyElement.attachEvent('onDOMNodeRemoved', function() {
222 | methods.setWrapperHeight(stickyElement);
223 | });
224 | }
225 | }
226 | },
227 | update: scroller,
228 | unstick: function(options) {
229 | return this.each(function() {
230 | var that = this;
231 | var unstickyElement = $(that);
232 |
233 | var removeIdx = -1;
234 | var i = sticked.length;
235 | while (i-- > 0) {
236 | if (sticked[i].stickyElement.get(0) === that) {
237 | splice.call(sticked,i,1);
238 | removeIdx = i;
239 | }
240 | }
241 | if(removeIdx !== -1) {
242 | unstickyElement.unwrap();
243 | unstickyElement
244 | .css({
245 | 'width': '',
246 | 'position': '',
247 | 'top': '',
248 | 'float': '',
249 | 'z-index': ''
250 | })
251 | ;
252 | }
253 | });
254 | }
255 | };
256 |
257 | // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer):
258 | if (window.addEventListener) {
259 | window.addEventListener('scroll', scroller, false);
260 | window.addEventListener('resize', resizer, false);
261 | } else if (window.attachEvent) {
262 | window.attachEvent('onscroll', scroller);
263 | window.attachEvent('onresize', resizer);
264 | }
265 |
266 | $.fn.sticky = function(method) {
267 | if (methods[method]) {
268 | return methods[method].apply(this, slice.call(arguments, 1));
269 | } else if (typeof method === 'object' || !method ) {
270 | return methods.init.apply( this, arguments );
271 | } else {
272 | $.error('Method ' + method + ' does not exist on jQuery.sticky');
273 | }
274 | };
275 |
276 | $.fn.unstick = function(method) {
277 | if (methods[method]) {
278 | return methods[method].apply(this, slice.call(arguments, 1));
279 | } else if (typeof method === 'object' || !method ) {
280 | return methods.unstick.apply( this, arguments );
281 | } else {
282 | $.error('Method ' + method + ' does not exist on jQuery.sticky');
283 | }
284 | };
285 | $(function() {
286 | setTimeout(scroller, 0);
287 | });
288 | }));
289 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-sticky",
3 | "version": "1.0.4",
4 | "description": "Sticky is a jQuery plugin that gives you the ability to make any element on your page always stay visible.",
5 | "main": "jquery.sticky.js",
6 | "files": [
7 | "jquery.sticky.js"
8 | ],
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": "garand/sticky",
13 | "keywords": [
14 | "dom",
15 | "jquery-plugin",
16 | "ui"
17 | ],
18 | "author": "Anthony Garand",
19 | "license": "(MIT OR GPL-3.0)",
20 | "bugs": {
21 | "url": "https://github.com/garand/sticky/issues"
22 | },
23 | "homepage": "https://github.com/garand/sticky#readme",
24 | "peerDependencies": {
25 | "jquery": "*"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------