├── .gitignore ├── bower.json ├── package.json ├── LICENSE ├── README.md └── lrStickyHeader.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lr-sticky-header", 3 | "main": "lrStickyHeader.js", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/lorenzofox3/lrStickyHeader", 6 | "authors": [ 7 | "lorenzofox3 " 8 | ], 9 | "description": "make table header sticky", 10 | "keywords": [ 11 | "table", 12 | "header", 13 | "sticky" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lr-sticky-header", 3 | "version": "1.1.0", 4 | "description": "make table header sticky", 5 | "main": "lrStickyHeader.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/lorenzofox3/lrStickyHeader.git" 12 | }, 13 | "keywords": [ 14 | "table", 15 | "sticky", 16 | "header" 17 | ], 18 | "author": "laurent renard", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/lorenzofox3/lrStickyHeader/issues" 22 | }, 23 | "homepage": "https://github.com/lorenzofox3/lrStickyHeader#readme" 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 RENARD Laurent 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lrStickyHeader 2 | 3 | make table headers sticky, example for [React CRM](http://reactcrm.com/) 4 | 5 | ![stStickeyHeader](http://i.imgur.com/ocN250H.gif) 6 | 7 | [live demo](http://lorenzofox3.github.io/lrStickyHeader/example.html) 8 | 9 | * (almost)no css to add 10 | * no dependency 11 | * does not add any other element to the markup 12 | * ~ 100 loc 13 | 14 | ## install 15 | 16 | ``bower install lr-sticky-header`` 17 | 18 | ``npm install lr-sticky-header`` 19 | 20 | ## dependencies 21 | 22 | None 23 | 24 | ## usage 25 | 26 | ```Javascript 27 | var tableElement = document.getElementById('table'); 28 | 29 | var stickyTable = lrStickyHeader(tableElement); 30 | 31 | var parentElement = document.getElementById('scrollPanel'); 32 | var stickyTable2 = lrStickyHeader(tableElement, {parent: parentElement}); 33 | ``` 34 | 35 | ### style 36 | 37 | You'll need your table element and its children to have the property ``box-sizing`` set to ``border-box`` (it is the default of many css framework such bootstrap 38 | 39 | when the header is sticked the class name ``lr-sticky-header`` is added to the thead element if you want to add some more style 40 | 41 | ### api 42 | 43 | * **setWidth()** : if you want to call manually the resize of the column (within a resize event handler for example) 44 | * **clean()** ~: to detach the scroll event handler from the window 45 | 46 | ### [example of a directive](https://github.com/lorenzofox3/stStickyHeader) with [smart-table](http://lorenzofox3.github.io/smart-table-website/) 47 | -------------------------------------------------------------------------------- /lrStickyHeader.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define([], function () { 5 | return (global['lrStickyHeader'] = factory(global)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(global); 12 | } else { 13 | global['lrStickyHeader'] = factory(global); 14 | } 15 | })(window, function factory (window) { 16 | 'use strict'; 17 | function getOffset (element, property) { 18 | var offset = element[property]; 19 | var parent = element; 20 | while ((parent = parent.offsetParent) !== null) { 21 | offset += parent[property]; 22 | } 23 | return offset; 24 | } 25 | 26 | var sticky = { 27 | //todo some memoize stuff 28 | setWidth: function setWidth () { 29 | var firstRow = this.tbody.getElementsByTagName('TR')[0]; 30 | var trh = this.thead.getElementsByTagName('TR')[0]; 31 | var firstTds; 32 | var firstThs; 33 | 34 | function setCellWidth (cell) { 35 | cell.style.width = cell.offsetWidth + 'px'; 36 | } 37 | 38 | if (firstRow && trh) { 39 | firstTds = firstRow.getElementsByTagName('TD'); 40 | firstThs = trh.getElementsByTagName('TH'); 41 | 42 | [].forEach.call(firstTds, setCellWidth); 43 | [].forEach.call(firstThs, setCellWidth); 44 | } 45 | }, 46 | eventListener: function eventListener () { 47 | var offsetTop = getOffset(this.thead, 'offsetTop') - Number(this.headerHeight); 48 | var offsetLeft = getOffset(this.thead, 'offsetLeft'); 49 | var parentOffsetTop = this.parentIsWindow ? 0 : getOffset(this.parent, 'offsetTop'); 50 | var parentScrollTop = this.parentIsWindow ? parent.scrollY : this.parent.scrollTop; 51 | var classes = this.thead.className.split(' '); 52 | 53 | if (this.stick !== true && (offsetTop - (parentOffsetTop + parentScrollTop) < 0) && 54 | (offsetTop + this.tbody.offsetHeight - (parentOffsetTop + parentScrollTop) > 0)) { 55 | this.stick = true; 56 | this.treshold = offsetTop; 57 | this.windowScrollY = this.parentIsWindow ? 0 : window.scrollY; 58 | this.setWidth(); 59 | this.thead.style.left = offsetLeft + 'px'; 60 | this.thead.style.top = Number(this.headerHeight + parentOffsetTop - this.windowScrollY) + 'px'; 61 | setTimeout(function () { 62 | classes.push('lr-sticky-header'); 63 | this.thead.style.position = 'fixed'; 64 | this.thead.className = classes.join(' '); 65 | this.element.style.marginTop = Number(this.thead.offsetHeight) + 'px'; 66 | }.bind(this), 0); 67 | } 68 | 69 | if (this.stick === true && !this.parentIsWindow && this.windowScrollY !== window.scrollY) { 70 | this.windowScrollY = window.scrollY; 71 | this.thead.style.top = Number(this.headerHeight + parentOffsetTop - this.windowScrollY) + 'px'; 72 | } 73 | 74 | if (this.stick === true && ( 75 | (this.parentIsWindow && (this.treshold - parentScrollTop > 0)) || 76 | (parentScrollTop <= 0))) { 77 | this.stick = false; 78 | this.thead.style.position = 'initial'; 79 | classes.splice(classes.indexOf('lr-sticky-header'), 1); 80 | this.thead.className = (classes).join(' '); 81 | this.element.style.marginTop = '0'; 82 | } 83 | } 84 | }; 85 | 86 | return function lrStickyHeader (tableElement, options) { 87 | var headerHeight = 0; 88 | if (options&&options.headerHeight) 89 | headerHeight=options.headerHeight; 90 | var parent = window; 91 | if (options && options.parent) { 92 | parent = options.parent; 93 | } 94 | 95 | var thead; 96 | var tbody; 97 | 98 | if (tableElement.tagName !== 'TABLE') { 99 | throw new Error('lrStickyHeader only works on table element'); 100 | } 101 | tbody = tableElement.getElementsByTagName('TBODY'); 102 | thead = tableElement.getElementsByTagName('THEAD'); 103 | 104 | if (!thead.length) { 105 | throw new Error('could not find the thead group element'); 106 | } 107 | 108 | if (!tbody.length) { 109 | throw new Error('could not find the tbody group element'); 110 | } 111 | 112 | thead = thead[0]; 113 | tbody = tbody[0]; 114 | 115 | 116 | var stickyTable = Object.create(sticky, { 117 | element: {value: tableElement}, 118 | parent: { 119 | get: function () { 120 | return parent; 121 | } 122 | }, 123 | parentIsWindow: {value: parent === window}, 124 | headerHeight: { 125 | get: function () { 126 | return headerHeight; 127 | } 128 | }, 129 | thead: { 130 | get: function () { 131 | return thead; 132 | } 133 | }, 134 | tbody: { 135 | get: function () { 136 | return tbody; 137 | } 138 | } 139 | }); 140 | 141 | var listener = stickyTable.eventListener.bind(stickyTable); 142 | parent.addEventListener('scroll', listener); 143 | if (parent !== window) { 144 | window.addEventListener('scroll', listener); 145 | } 146 | stickyTable.clean = function clean () { 147 | parent.removeEventListener('scroll', listener); 148 | if (parent !== window) { 149 | window.removeEventListener('scroll', listener); 150 | } 151 | }; 152 | 153 | return stickyTable; 154 | }; 155 | }); 156 | 157 | --------------------------------------------------------------------------------