├── src ├── docs │ ├── index.md │ └── _templates │ │ ├── _footer.html │ │ └── _header.html └── js │ └── sticky-footer.js ├── .travis.yml ├── .gitignore ├── package.json ├── docs ├── index.html └── dist │ └── js │ ├── sticky-footer.min.js │ └── sticky-footer.js ├── LICENSE.md ├── dist └── js │ ├── sticky-footer.min.js │ └── sticky-footer.js ├── README.md └── gulpfile.js /src/docs/index.md: -------------------------------------------------------------------------------- 1 |

Some short body content.

-------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - npm install -g gulp 6 | script: gulp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules 3 | test/results 4 | test/coverage 5 | 6 | ## OS X 7 | .DS_Store 8 | ._* 9 | .Spotlight-V100 10 | .Trashes -------------------------------------------------------------------------------- /src/docs/_templates/_footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/docs/_templates/_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sticky Footer 7 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 35 | 36 |
37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sticky-footer", 3 | "version": "4.2.0", 4 | "description": "Responsive sticky footers", 5 | "main": "./dist/js/sticky-footer.min.js", 6 | "author": { 7 | "name": "Chris Ferdinandi", 8 | "url": "http://gomakethings.com" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/cferdinandi/sticky-footer" 14 | }, 15 | "devDependencies": { 16 | "gulp": "^3.9.1", 17 | "node-fs": "^0.1.7", 18 | "del": "^2.2.0", 19 | "lazypipe": "^1.0.1", 20 | "gulp-plumber": "^1.1.0", 21 | "gulp-flatten": "^0.3.1", 22 | "gulp-tap": "^0.1.3", 23 | "gulp-rename": "^1.2.2", 24 | "gulp-header": "^1.8.8", 25 | "gulp-footer": "^1.0.5", 26 | "gulp-watch": "^4.3.9", 27 | "gulp-livereload": "^3.8.1", 28 | "gulp-jshint": "^2.0.1", 29 | "jshint-stylish": "^2.2.1", 30 | "gulp-concat": "^2.6.0", 31 | "gulp-uglify": "^2.0.0", 32 | "gulp-optimize-js": "^1.0.2", 33 | "gulp-markdown": "^1.2.0", 34 | "gulp-file-include": "^0.14.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sticky Footer 7 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 35 | 36 |
37 |

Some short body content.

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

Footer content

45 |
46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Go Make Things, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /dist/js/sticky-footer.min.js: -------------------------------------------------------------------------------- 1 | /*! sticky-footer v4.2.0 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/sticky-footer */ 2 | !(function(e,t){"function"==typeof define&&define.amd?define([],t(e)):"object"==typeof exports?module.exports=t(e):e.stickyFooter=t(e)})("undefined"!=typeof global?global:this.window||this.global,(function(e){"use strict";var t,n,o,i,r={},c="querySelector"in document&&"addEventListener"in e,l={selectorWrap:"[data-sticky-wrap]",selectorFooter:"[data-sticky-footer]",callback:function(){}},u=function(){var e={},t=!1,n=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(t=arguments[0],n++);for(var i=function(n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t&&"[object Object]"===Object.prototype.toString.call(n[o])?e[o]=u(!0,e[o],n[o]):e[o]=n[o])};n 8 | 9 | ### Want to learn how to write your own vanilla JS plugins? Check out ["The Vanilla JS Guidebook"](https://gomakethings.com/vanilla-js-guidebook/) and level-up as a web developer. 🚀 10 | 11 |
12 | 13 | 14 | 15 | ## Getting Started 16 | 17 | Compiled and production-ready code can be found in the `dist` directory. The `src` directory contains development code. 18 | 19 | ### 1. Include Sticky Footer on your site. 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | ### 2. Add the markup to your HTML. 26 | 27 | ```html 28 |
29 | Body content 30 |
31 |
32 | Footer content 33 |
34 | ``` 35 | 36 | Add the `data-sticky-wrap` attribute to a parent `
` that contains all of your page content. Add the `data-sticky-footer` attribute to the parent `
` that contains all of your footer content. 37 | 38 | ### 3. Initialize Sticky Footer. 39 | 40 | ```html 41 | 44 | ``` 45 | 46 | In the footer of your page, after the content, initialize Sticky Footer. And that's it, you're done. Nice work! 47 | 48 | 49 | 50 | ## Installing with Package Managers 51 | 52 | You can install Sticky Footer with your favorite package manager. 53 | 54 | * **NPM:** `npm install cferdinandi/sticky-footer` 55 | * **Bower:** `bower install https://github.com/cferdinandi/sticky-footer.git` 56 | * **Component:** `component install cferdinandi/sticky-footer` 57 | 58 | 59 | 60 | ## Working with the Source Files 61 | 62 | If you would prefer, you can work with the development code in the `src` directory using the included [Gulp build system](http://gulpjs.com/). This compiles, lints, and minifies code. 63 | 64 | ### Dependencies 65 | Make sure these are installed first. 66 | 67 | * [Node.js](http://nodejs.org) 68 | * [Gulp](http://gulpjs.com) `sudo npm install -g gulp` 69 | 70 | ### Quick Start 71 | 72 | 1. In bash/terminal/command line, `cd` into your project directory. 73 | 2. Run `npm install` to install required files. 74 | 3. When it's done installing, run one of the task runners to get going: 75 | * `gulp` manually compiles files. 76 | * `gulp watch` automatically compiles files when changes are made and applies changes using [LiveReload](http://livereload.com/). 77 | 78 | 79 | 80 | ## Options and Settings 81 | 82 | Sticky Footer includes smart defaults and works right out of the box. But if you want to customize things, it also has a robust API that provides multiple ways for you to adjust the default options and settings. 83 | 84 | ### Global Settings 85 | 86 | You can pass callbacks into Sticky Footer through the `init()` function: 87 | 88 | ```javascript 89 | stickyFooter.init({ 90 | selectorWrap: '[data-sticky-wrap]', // Selector for the wrap container (must use a valid CSS selector) 91 | selectorFooter: '[data-sticky-footer]', // Selector for the footer (must use a valid CSS selector) 92 | callback: function () {}, // Runs after the footer is stuck 93 | }); 94 | ``` 95 | 96 | ### Use Sticky Footer events in your own scripts 97 | 98 | You can also call Sticky Footer events in your own scripts. 99 | 100 | #### destroy() 101 | Destroy the current `stickyFooter.init()`. This is called automatically during the `init` function to remove any existing initializations. 102 | 103 | ```javascript 104 | stickyFooter.destroy(); 105 | ``` 106 | 107 | 108 | 109 | ## Browser Compatibility 110 | 111 | Sticky Footer works in all modern browsers, and IE 9 and above. 112 | 113 | Sticky Footer is built with modern JavaScript APIs, and uses progressive enhancement. If the JavaScript file fails to load, or if your site is viewed on older and less capable browsers, footers will simply float up against the content like they normally would. 114 | 115 | 116 | 117 | ## How to Contribute 118 | 119 | In lieu of a formal style guide, take care to maintain the existing coding style. Please apply fixes to both the development and production code. Don't forget to update the version number, and when applicable, the documentation. 120 | 121 | 122 | 123 | ## License 124 | 125 | The code is available under the [MIT License](LICENSE.md). -------------------------------------------------------------------------------- /src/js/sticky-footer.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if ( typeof define === 'function' && define.amd ) { 3 | define([], factory(root)); 4 | } else if ( typeof exports === 'object' ) { 5 | module.exports = factory(root); 6 | } else { 7 | root.stickyFooter = factory(root); 8 | } 9 | })(typeof global !== 'undefined' ? global : this.window || this.global, function (root) { 10 | 11 | 'use strict'; 12 | 13 | // 14 | // Variables 15 | // 16 | 17 | var stickyFooter = {}; // Object for public APIs 18 | var supports = 'querySelector' in document && 'addEventListener' in root; // Feature test 19 | var settings, wrap, footer, eventTimeout; 20 | 21 | // Default settings 22 | var defaults = { 23 | selectorWrap: '[data-sticky-wrap]', 24 | selectorFooter: '[data-sticky-footer]', 25 | callback: function () {} 26 | }; 27 | 28 | 29 | // 30 | // Methods 31 | // 32 | 33 | /** 34 | * Merge two or more objects. Returns a new object. 35 | * @private 36 | * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] 37 | * @param {Object} objects The objects to merge together 38 | * @returns {Object} Merged values of defaults and options 39 | */ 40 | var extend = function () { 41 | 42 | // Variables 43 | var extended = {}; 44 | var deep = false; 45 | var i = 0; 46 | var length = arguments.length; 47 | 48 | // Check if a deep merge 49 | if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 50 | deep = arguments[0]; 51 | i++; 52 | } 53 | 54 | // Merge the object into the extended object 55 | var merge = function (obj) { 56 | for ( var prop in obj ) { 57 | if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 58 | // If deep merge and property is an object, merge properties 59 | if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 60 | extended[prop] = extend( true, extended[prop], obj[prop] ); 61 | } else { 62 | extended[prop] = obj[prop]; 63 | } 64 | } 65 | } 66 | }; 67 | 68 | // Loop through each object and conduct a merge 69 | for ( ; i < length; i++ ) { 70 | var obj = arguments[i]; 71 | merge(obj); 72 | } 73 | 74 | return extended; 75 | 76 | }; 77 | 78 | /** 79 | * Get height of the viewport 80 | * @private 81 | * @return {Number} Height of the viewport in pixels 82 | */ 83 | var getViewportHeight = function () { 84 | return Math.max( document.documentElement.clientHeight, window.innerHeight || 0 ); 85 | }; 86 | 87 | /** 88 | * Set page wrapper height to fill viewport (minus footer height) 89 | * @private 90 | * @param {Element} wrap Page wrapper 91 | * @param {Element} footer Page footer 92 | * @param {Object} settings 93 | */ 94 | var setWrapHeight = function ( wrap, footer, settings ) { 95 | wrap.style.minHeight = ( getViewportHeight() - footer.offsetHeight ) + 'px'; 96 | settings.callback(); // Run callback 97 | }; 98 | 99 | /** 100 | * Destroy the current initialization. 101 | * @public 102 | */ 103 | stickyFooter.destroy = function () { 104 | 105 | if ( !settings ) return; 106 | 107 | // Unset styles 108 | document.documentElement.style.minHeight = ''; 109 | document.body.style.minHeight = ''; 110 | wrap.style.minHeight = ''; 111 | window.removeEventListener( 'resize', eventThrottler, false ); 112 | 113 | // Reset variables 114 | settings = null; 115 | wrap = null; 116 | footer = null; 117 | eventTimeout = null; 118 | 119 | }; 120 | 121 | /** 122 | * On window scroll and resize, only run events at a rate of 15fps for better performance 123 | * @private 124 | * @param {Function} eventTimeout Timeout function 125 | * @param {NodeList} wrap The content wrapper for the page 126 | * @param {NodeList} footer The footer for the page 127 | * @param {Object} settings 128 | */ 129 | var eventThrottler = function () { 130 | if ( !eventTimeout ) { 131 | eventTimeout = setTimeout(function() { 132 | eventTimeout = null; 133 | setWrapHeight( wrap, footer, settings ); 134 | }, 66); 135 | } 136 | }; 137 | 138 | /** 139 | * Initialize Plugin 140 | * @public 141 | * @param {Object} options User settings 142 | */ 143 | stickyFooter.init = function ( options ) { 144 | 145 | // feature test 146 | if ( !supports ) return; 147 | 148 | // Destroy any existing initializations 149 | stickyFooter.destroy(); 150 | 151 | // Selectors and variables 152 | settings = extend( defaults, options || {} ); // Merge user options with defaults 153 | wrap = document.querySelector( settings.selectorWrap ); 154 | footer = document.querySelector( settings.selectorFooter ); 155 | 156 | // Sanity check 157 | if ( !wrap || !footer ) return; 158 | 159 | // Stick footer 160 | document.documentElement.style.minHeight = '100%'; 161 | document.body.style.minHeight = '100%'; 162 | setWrapHeight( wrap, footer, settings ); 163 | window.addEventListener( 'resize', eventThrottler, false); // Run Sticky Footer on window resize 164 | 165 | }; 166 | 167 | 168 | // 169 | // Public APIs 170 | // 171 | 172 | return stickyFooter; 173 | 174 | }); -------------------------------------------------------------------------------- /dist/js/sticky-footer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * sticky-footer v4.2.0: Responsive sticky footers 3 | * (c) 2016 Chris Ferdinandi 4 | * MIT License 5 | * http://github.com/cferdinandi/sticky-footer 6 | */ 7 | 8 | (function (root, factory) { 9 | if ( typeof define === 'function' && define.amd ) { 10 | define([], factory(root)); 11 | } else if ( typeof exports === 'object' ) { 12 | module.exports = factory(root); 13 | } else { 14 | root.stickyFooter = factory(root); 15 | } 16 | })(typeof global !== 'undefined' ? global : this.window || this.global, (function (root) { 17 | 18 | 'use strict'; 19 | 20 | // 21 | // Variables 22 | // 23 | 24 | var stickyFooter = {}; // Object for public APIs 25 | var supports = 'querySelector' in document && 'addEventListener' in root; // Feature test 26 | var settings, wrap, footer, eventTimeout; 27 | 28 | // Default settings 29 | var defaults = { 30 | selectorWrap: '[data-sticky-wrap]', 31 | selectorFooter: '[data-sticky-footer]', 32 | callback: function () {} 33 | }; 34 | 35 | 36 | // 37 | // Methods 38 | // 39 | 40 | /** 41 | * Merge two or more objects. Returns a new object. 42 | * @private 43 | * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] 44 | * @param {Object} objects The objects to merge together 45 | * @returns {Object} Merged values of defaults and options 46 | */ 47 | var extend = function () { 48 | 49 | // Variables 50 | var extended = {}; 51 | var deep = false; 52 | var i = 0; 53 | var length = arguments.length; 54 | 55 | // Check if a deep merge 56 | if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 57 | deep = arguments[0]; 58 | i++; 59 | } 60 | 61 | // Merge the object into the extended object 62 | var merge = function (obj) { 63 | for ( var prop in obj ) { 64 | if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 65 | // If deep merge and property is an object, merge properties 66 | if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 67 | extended[prop] = extend( true, extended[prop], obj[prop] ); 68 | } else { 69 | extended[prop] = obj[prop]; 70 | } 71 | } 72 | } 73 | }; 74 | 75 | // Loop through each object and conduct a merge 76 | for ( ; i < length; i++ ) { 77 | var obj = arguments[i]; 78 | merge(obj); 79 | } 80 | 81 | return extended; 82 | 83 | }; 84 | 85 | /** 86 | * Get height of the viewport 87 | * @private 88 | * @return {Number} Height of the viewport in pixels 89 | */ 90 | var getViewportHeight = function () { 91 | return Math.max( document.documentElement.clientHeight, window.innerHeight || 0 ); 92 | }; 93 | 94 | /** 95 | * Set page wrapper height to fill viewport (minus footer height) 96 | * @private 97 | * @param {Element} wrap Page wrapper 98 | * @param {Element} footer Page footer 99 | * @param {Object} settings 100 | */ 101 | var setWrapHeight = function ( wrap, footer, settings ) { 102 | wrap.style.minHeight = ( getViewportHeight() - footer.offsetHeight ) + 'px'; 103 | settings.callback(); // Run callback 104 | }; 105 | 106 | /** 107 | * Destroy the current initialization. 108 | * @public 109 | */ 110 | stickyFooter.destroy = function () { 111 | 112 | if ( !settings ) return; 113 | 114 | // Unset styles 115 | document.documentElement.style.minHeight = ''; 116 | document.body.style.minHeight = ''; 117 | wrap.style.minHeight = ''; 118 | window.removeEventListener( 'resize', eventThrottler, false ); 119 | 120 | // Reset variables 121 | settings = null; 122 | wrap = null; 123 | footer = null; 124 | eventTimeout = null; 125 | 126 | }; 127 | 128 | /** 129 | * On window scroll and resize, only run events at a rate of 15fps for better performance 130 | * @private 131 | * @param {Function} eventTimeout Timeout function 132 | * @param {NodeList} wrap The content wrapper for the page 133 | * @param {NodeList} footer The footer for the page 134 | * @param {Object} settings 135 | */ 136 | var eventThrottler = function () { 137 | if ( !eventTimeout ) { 138 | eventTimeout = setTimeout((function() { 139 | eventTimeout = null; 140 | setWrapHeight( wrap, footer, settings ); 141 | }), 66); 142 | } 143 | }; 144 | 145 | /** 146 | * Initialize Plugin 147 | * @public 148 | * @param {Object} options User settings 149 | */ 150 | stickyFooter.init = function ( options ) { 151 | 152 | // feature test 153 | if ( !supports ) return; 154 | 155 | // Destroy any existing initializations 156 | stickyFooter.destroy(); 157 | 158 | // Selectors and variables 159 | settings = extend( defaults, options || {} ); // Merge user options with defaults 160 | wrap = document.querySelector( settings.selectorWrap ); 161 | footer = document.querySelector( settings.selectorFooter ); 162 | 163 | // Sanity check 164 | if ( !wrap || !footer ) return; 165 | 166 | // Stick footer 167 | document.documentElement.style.minHeight = '100%'; 168 | document.body.style.minHeight = '100%'; 169 | setWrapHeight( wrap, footer, settings ); 170 | window.addEventListener( 'resize', eventThrottler, false); // Run Sticky Footer on window resize 171 | 172 | }; 173 | 174 | 175 | // 176 | // Public APIs 177 | // 178 | 179 | return stickyFooter; 180 | 181 | })); -------------------------------------------------------------------------------- /docs/dist/js/sticky-footer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * sticky-footer v4.2.0: Responsive sticky footers 3 | * (c) 2016 Chris Ferdinandi 4 | * MIT License 5 | * http://github.com/cferdinandi/sticky-footer 6 | */ 7 | 8 | (function (root, factory) { 9 | if ( typeof define === 'function' && define.amd ) { 10 | define([], factory(root)); 11 | } else if ( typeof exports === 'object' ) { 12 | module.exports = factory(root); 13 | } else { 14 | root.stickyFooter = factory(root); 15 | } 16 | })(typeof global !== 'undefined' ? global : this.window || this.global, (function (root) { 17 | 18 | 'use strict'; 19 | 20 | // 21 | // Variables 22 | // 23 | 24 | var stickyFooter = {}; // Object for public APIs 25 | var supports = 'querySelector' in document && 'addEventListener' in root; // Feature test 26 | var settings, wrap, footer, eventTimeout; 27 | 28 | // Default settings 29 | var defaults = { 30 | selectorWrap: '[data-sticky-wrap]', 31 | selectorFooter: '[data-sticky-footer]', 32 | callback: function () {} 33 | }; 34 | 35 | 36 | // 37 | // Methods 38 | // 39 | 40 | /** 41 | * Merge two or more objects. Returns a new object. 42 | * @private 43 | * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] 44 | * @param {Object} objects The objects to merge together 45 | * @returns {Object} Merged values of defaults and options 46 | */ 47 | var extend = function () { 48 | 49 | // Variables 50 | var extended = {}; 51 | var deep = false; 52 | var i = 0; 53 | var length = arguments.length; 54 | 55 | // Check if a deep merge 56 | if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 57 | deep = arguments[0]; 58 | i++; 59 | } 60 | 61 | // Merge the object into the extended object 62 | var merge = function (obj) { 63 | for ( var prop in obj ) { 64 | if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 65 | // If deep merge and property is an object, merge properties 66 | if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 67 | extended[prop] = extend( true, extended[prop], obj[prop] ); 68 | } else { 69 | extended[prop] = obj[prop]; 70 | } 71 | } 72 | } 73 | }; 74 | 75 | // Loop through each object and conduct a merge 76 | for ( ; i < length; i++ ) { 77 | var obj = arguments[i]; 78 | merge(obj); 79 | } 80 | 81 | return extended; 82 | 83 | }; 84 | 85 | /** 86 | * Get height of the viewport 87 | * @private 88 | * @return {Number} Height of the viewport in pixels 89 | */ 90 | var getViewportHeight = function () { 91 | return Math.max( document.documentElement.clientHeight, window.innerHeight || 0 ); 92 | }; 93 | 94 | /** 95 | * Set page wrapper height to fill viewport (minus footer height) 96 | * @private 97 | * @param {Element} wrap Page wrapper 98 | * @param {Element} footer Page footer 99 | * @param {Object} settings 100 | */ 101 | var setWrapHeight = function ( wrap, footer, settings ) { 102 | wrap.style.minHeight = ( getViewportHeight() - footer.offsetHeight ) + 'px'; 103 | settings.callback(); // Run callback 104 | }; 105 | 106 | /** 107 | * Destroy the current initialization. 108 | * @public 109 | */ 110 | stickyFooter.destroy = function () { 111 | 112 | if ( !settings ) return; 113 | 114 | // Unset styles 115 | document.documentElement.style.minHeight = ''; 116 | document.body.style.minHeight = ''; 117 | wrap.style.minHeight = ''; 118 | window.removeEventListener( 'resize', eventThrottler, false ); 119 | 120 | // Reset variables 121 | settings = null; 122 | wrap = null; 123 | footer = null; 124 | eventTimeout = null; 125 | 126 | }; 127 | 128 | /** 129 | * On window scroll and resize, only run events at a rate of 15fps for better performance 130 | * @private 131 | * @param {Function} eventTimeout Timeout function 132 | * @param {NodeList} wrap The content wrapper for the page 133 | * @param {NodeList} footer The footer for the page 134 | * @param {Object} settings 135 | */ 136 | var eventThrottler = function () { 137 | if ( !eventTimeout ) { 138 | eventTimeout = setTimeout((function() { 139 | eventTimeout = null; 140 | setWrapHeight( wrap, footer, settings ); 141 | }), 66); 142 | } 143 | }; 144 | 145 | /** 146 | * Initialize Plugin 147 | * @public 148 | * @param {Object} options User settings 149 | */ 150 | stickyFooter.init = function ( options ) { 151 | 152 | // feature test 153 | if ( !supports ) return; 154 | 155 | // Destroy any existing initializations 156 | stickyFooter.destroy(); 157 | 158 | // Selectors and variables 159 | settings = extend( defaults, options || {} ); // Merge user options with defaults 160 | wrap = document.querySelector( settings.selectorWrap ); 161 | footer = document.querySelector( settings.selectorFooter ); 162 | 163 | // Sanity check 164 | if ( !wrap || !footer ) return; 165 | 166 | // Stick footer 167 | document.documentElement.style.minHeight = '100%'; 168 | document.body.style.minHeight = '100%'; 169 | setWrapHeight( wrap, footer, settings ); 170 | window.addEventListener( 'resize', eventThrottler, false); // Run Sticky Footer on window resize 171 | 172 | }; 173 | 174 | 175 | // 176 | // Public APIs 177 | // 178 | 179 | return stickyFooter; 180 | 181 | })); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gulp Packages 3 | */ 4 | 5 | // General 6 | var gulp = require('gulp'); 7 | var fs = require('fs'); 8 | var del = require('del'); 9 | var lazypipe = require('lazypipe'); 10 | var plumber = require('gulp-plumber'); 11 | var flatten = require('gulp-flatten'); 12 | var tap = require('gulp-tap'); 13 | var rename = require('gulp-rename'); 14 | var header = require('gulp-header'); 15 | var footer = require('gulp-footer'); 16 | var watch = require('gulp-watch'); 17 | var livereload = require('gulp-livereload'); 18 | var package = require('./package.json'); 19 | 20 | // Scripts 21 | var jshint = require('gulp-jshint'); 22 | var stylish = require('jshint-stylish'); 23 | var concat = require('gulp-concat'); 24 | var uglify = require('gulp-uglify'); 25 | var optimizejs = require('gulp-optimize-js'); 26 | 27 | // Docs 28 | var markdown = require('gulp-markdown'); 29 | var fileinclude = require('gulp-file-include'); 30 | 31 | 32 | /** 33 | * Paths to project folders 34 | */ 35 | 36 | var paths = { 37 | input: 'src/**/*', 38 | output: 'dist/', 39 | scripts: { 40 | input: 'src/js/*', 41 | output: 'dist/js/' 42 | }, 43 | docs: { 44 | input: 'src/docs/*.{html,md,markdown}', 45 | output: 'docs/', 46 | templates: 'src/docs/_templates/', 47 | assets: 'src/docs/assets/**' 48 | } 49 | }; 50 | 51 | 52 | /** 53 | * Template for banner to add to file headers 54 | */ 55 | 56 | var banner = { 57 | full : 58 | '/*!\n' + 59 | ' * <%= package.name %> v<%= package.version %>: <%= package.description %>\n' + 60 | ' * (c) ' + new Date().getFullYear() + ' <%= package.author.name %>\n' + 61 | ' * MIT License\n' + 62 | ' * <%= package.repository.url %>\n' + 63 | ' */\n\n', 64 | min : 65 | '/*!' + 66 | ' <%= package.name %> v<%= package.version %>' + 67 | ' | (c) ' + new Date().getFullYear() + ' <%= package.author.name %>' + 68 | ' | MIT License' + 69 | ' | <%= package.repository.url %>' + 70 | ' */\n' 71 | }; 72 | 73 | 74 | /** 75 | * Gulp Taks 76 | */ 77 | 78 | // Lint, minify, and concatenate scripts 79 | gulp.task('build:scripts', ['clean:dist'], function() { 80 | var jsTasks = lazypipe() 81 | .pipe(header, banner.full, { package : package }) 82 | .pipe(optimizejs) 83 | .pipe(gulp.dest, paths.scripts.output) 84 | .pipe(rename, { suffix: '.min' }) 85 | .pipe(uglify) 86 | .pipe(optimizejs) 87 | .pipe(header, banner.min, { package : package }) 88 | .pipe(gulp.dest, paths.scripts.output); 89 | 90 | return gulp.src(paths.scripts.input) 91 | .pipe(plumber()) 92 | .pipe(tap(function (file, t) { 93 | if ( file.isDirectory() ) { 94 | var name = file.relative + '.js'; 95 | return gulp.src(file.path + '/*.js') 96 | .pipe(concat(name)) 97 | .pipe(jsTasks()); 98 | } 99 | })) 100 | .pipe(jsTasks()); 101 | }); 102 | 103 | // Lint scripts 104 | gulp.task('lint:scripts', function () { 105 | return gulp.src(paths.scripts.input) 106 | .pipe(plumber()) 107 | .pipe(jshint()) 108 | .pipe(jshint.reporter('jshint-stylish')); 109 | }); 110 | 111 | // Remove pre-existing content from output folder 112 | gulp.task('clean:dist', function () { 113 | del.sync([ 114 | paths.output 115 | ]); 116 | }); 117 | 118 | // Generate documentation 119 | gulp.task('build:docs', ['compile', 'clean:docs'], function() { 120 | return gulp.src(paths.docs.input) 121 | .pipe(plumber()) 122 | .pipe(fileinclude({ 123 | prefix: '@@', 124 | basepath: '@file' 125 | })) 126 | .pipe(tap(function (file, t) { 127 | if ( /\.md|\.markdown/.test(file.path) ) { 128 | return t.through(markdown); 129 | } 130 | })) 131 | .pipe(header(fs.readFileSync(paths.docs.templates + '/_header.html', 'utf8'))) 132 | .pipe(footer(fs.readFileSync(paths.docs.templates + '/_footer.html', 'utf8'))) 133 | .pipe(gulp.dest(paths.docs.output)); 134 | }); 135 | 136 | // Copy distribution files to docs 137 | gulp.task('copy:dist', ['compile', 'clean:docs'], function() { 138 | return gulp.src(paths.output + '/**') 139 | .pipe(plumber()) 140 | .pipe(gulp.dest(paths.docs.output + '/dist')); 141 | }); 142 | 143 | // Copy documentation assets to docs 144 | gulp.task('copy:assets', ['clean:docs'], function() { 145 | return gulp.src(paths.docs.assets) 146 | .pipe(plumber()) 147 | .pipe(gulp.dest(paths.docs.output + '/assets')); 148 | }); 149 | 150 | // Remove prexisting content from docs folder 151 | gulp.task('clean:docs', function () { 152 | return del.sync(paths.docs.output); 153 | }); 154 | 155 | // Spin up livereload server and listen for file changes 156 | gulp.task('listen', function () { 157 | livereload.listen(); 158 | gulp.watch(paths.input).on('change', function(file) { 159 | gulp.start('default'); 160 | gulp.start('refresh'); 161 | }); 162 | }); 163 | 164 | // Run livereload after file change 165 | gulp.task('refresh', ['compile', 'docs'], function () { 166 | livereload.changed(); 167 | }); 168 | 169 | 170 | /** 171 | * Task Runners 172 | */ 173 | 174 | // Compile files 175 | gulp.task('compile', [ 176 | 'lint:scripts', 177 | 'clean:dist', 178 | 'build:scripts' 179 | ]); 180 | 181 | // Generate documentation 182 | gulp.task('docs', [ 183 | 'clean:docs', 184 | 'build:docs', 185 | 'copy:dist', 186 | 'copy:assets' 187 | ]); 188 | 189 | // Compile files and generate docs (default) 190 | gulp.task('default', [ 191 | 'compile', 192 | 'docs' 193 | ]); 194 | 195 | // Compile files and generate docs when something changes 196 | gulp.task('watch', [ 197 | 'listen', 198 | 'default' 199 | ]); --------------------------------------------------------------------------------