├── LICENSE ├── README.md ├── package.json ├── simple-scrollbar.css ├── simple-scrollbar.js ├── simple-scrollbar.min.js └── types.d.ts /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Vitor Buzinaro 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 | [![](https://data.jsdelivr.com/v1/package/npm/simple-scrollbar/badge)](https://www.jsdelivr.com/package/npm/simple-scrollbar) 2 | 3 | # SimpleScrollbar 4 | Very simple vanilla javascript library for creating a custom scrollbar cross-browser and cross-devices. 5 | 6 | ## Demo 7 | http://buzinas.github.io/simple-scrollbar 8 | 9 | ## React version 10 | 11 | I also open sourced a newer and more modern React version at: 12 | https://github.com/closeio/react-custom-scroller 13 | 14 | ## Benefits 15 | 16 | - Extremely lightweight (less than 1KB after gzip and minify) 17 | - It uses the native scroll events, so: 18 | - All the events work and are smooth (mouse wheel, space, page down, page up, arrows etc). 19 | - The performance is awesome! 20 | - No dependencies, completely vanilla Javascript! 21 | - RTL support (thanks to [@BabkinAleksandr](https://github.com/BabkinAleksandr)) 22 | 23 | ## Browser Support 24 | 25 | It was developed with evergreen browsers in mind, but it works on IE11. 26 | 27 | ## Usage 28 | 29 | You can use this library as a script tag, or you can import it as a npm module, eg: 30 | 31 | ### Script tag 32 | 33 | Download the script [here](https://github.com/buzinas/simple-scrollbar/blob/master/simple-scrollbar.min.js) and the styles from [here](https://github.com/buzinas/simple-scrollbar/blob/master/simple-scrollbar.css) and include it: 34 | 35 | ```html 36 | 37 | 38 | ``` 39 | 40 | Or include it via [jsDelivr CDN](https://www.jsdelivr.com/package/npm/simple-scrollbar): 41 | 42 | ```html 43 | 44 | 45 | ``` 46 | 47 | ### CommonJS 48 | 49 | ```js 50 | const SimpleScrollbar = require('simple-scrollbar'); 51 | require('simple-scrollbar/simple-scrollbar.css') 52 | ``` 53 | 54 | ### ES2015 modules 55 | 56 | ```js 57 | import SimpleScrollbar from 'simple-scrollbar' 58 | import 'simple-scrollbar/simple-scrollbar.css' 59 | ``` 60 | 61 | ### Auto-binding 62 | Include the attribute `ss-container` in any `
` that you want to make scrollable, and the library will turn it for you 63 | 64 | ```HTML 65 |
One
66 |
67 | Two 68 |
69 | ``` 70 | 71 | ### Manual binding 72 | If you want to manually turn your div in a SimpleScrollbar, you can use the `SimpleScrollbar.initEl` method. 73 | 74 | ```HTML 75 |
76 | 77 | 81 | ``` 82 | 83 | ### Dynamically added content 84 | If you use some client Framework, like Angular, Aurelia, etc - or any library that includes DOMElements dynamically in your app, and you want to use the SimpleScrollbar `ss-container` attribute, you can use the `SimpleScrollbar.initAll` method, and it will turn all the elements with that attribute in a scrollable one for you. 85 | 86 | ```Javascript 87 | var div = document.createElement('div'); 88 | div.insertAdjacentHTML('afterbegin', 'One'); 89 | div.setAttribute('ss-container', true); 90 | 91 | var otherDiv = div.cloneNode(true); 92 | otherDiv.querySelector('span').textContent = 'Two'; 93 | 94 | document.body.appendChild(div); 95 | document.body.appendChild(otherDiv); 96 | 97 | SimpleScrollbar.initAll(); 98 | ``` 99 | 100 | 101 | ### RTL Support 102 | 103 | Add `direction: rtl;` to your `
`'s CSS, and SimpleScrollbar will detect the direction automatically. 104 | 105 | ## Credits 106 | Inspired by yairEO's jQuery plugin ([fakescroll](https://github.com/yairEO/fakescroll)) 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-scrollbar", 3 | "version": "0.4.0", 4 | "description": "Very simple vanilla javascript library for creating a custom scrollbar cross-browser and cross-devices.", 5 | "main": "simple-scrollbar.js", 6 | "types": "./types.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/buzinas/simple-scrollbar.git" 10 | }, 11 | "keywords": [ 12 | "scrollbar", 13 | "custom-scrollbar", 14 | "tiny-scrollbar", 15 | "simple-scrollbar", 16 | "vanilla-js" 17 | ], 18 | "author": "Vitor Buzinaro", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/buzinas/simple-scrollbar/issues" 22 | }, 23 | "homepage": "https://github.com/buzinas/simple-scrollbar#readme" 24 | } 25 | -------------------------------------------------------------------------------- /simple-scrollbar.css: -------------------------------------------------------------------------------- 1 | .ss-wrapper { 2 | overflow: hidden; 3 | width: 100%; 4 | height: 100%; 5 | position: relative; 6 | z-index: 1; 7 | float: left; 8 | } 9 | 10 | .ss-content { 11 | height: 100%; 12 | width: calc(100% + 18px); 13 | padding: 0 0 0 0; 14 | position: relative; 15 | overflow-x: auto; 16 | overflow-y: scroll; 17 | box-sizing: border-box; 18 | } 19 | 20 | .ss-content.rtl { 21 | width: calc(100% + 18px); 22 | right: auto; 23 | } 24 | 25 | .ss-scroll { 26 | position: relative; 27 | background: rgba(0, 0, 0, 0.1); 28 | width: 9px; 29 | border-radius: 4px; 30 | top: 0; 31 | z-index: 2; 32 | cursor: pointer; 33 | opacity: 0; 34 | transition: opacity 0.25s linear; 35 | } 36 | 37 | .ss-hidden { 38 | display: none; 39 | } 40 | 41 | .ss-container:hover .ss-scroll, 42 | .ss-container:active .ss-scroll { 43 | opacity: 1; 44 | } 45 | 46 | .ss-grabbed { 47 | -o-user-select: none; 48 | -ms-user-select: none; 49 | -moz-user-select: none; 50 | -webkit-user-select: none; 51 | user-select: none; 52 | } 53 | -------------------------------------------------------------------------------- /simple-scrollbar.js: -------------------------------------------------------------------------------- 1 | ;(function(root, factory) { 2 | if (typeof exports === 'object') { 3 | module.exports = factory(window, document) 4 | } else { 5 | root.SimpleScrollbar = factory(window, document) 6 | } 7 | })(this, function(w, d) { 8 | var raf = w.requestAnimationFrame || w.setImmediate || function(c) { return setTimeout(c, 0); }; 9 | 10 | function initEl(el) { 11 | Object.defineProperty(el, 'data-simple-scrollbar', { value: new SimpleScrollbar(el), configurable: true }); 12 | } 13 | 14 | function unbindEl(el) { 15 | if (!Object.prototype.hasOwnProperty.call(el, 'data-simple-scrollbar')) return; 16 | el['data-simple-scrollbar'].unBind(); 17 | //Remove the elements property 18 | delete el['data-simple-scrollbar']; 19 | } 20 | 21 | // Mouse drag handler 22 | function dragDealer(el, context) { 23 | var lastPageY; 24 | 25 | el.addEventListener('mousedown', function(e) { 26 | lastPageY = e.pageY; 27 | el.classList.add('ss-grabbed'); 28 | d.body.classList.add('ss-grabbed'); 29 | 30 | d.addEventListener('mousemove', drag); 31 | d.addEventListener('mouseup', stop); 32 | 33 | return false; 34 | }); 35 | 36 | function drag(e) { 37 | var delta = e.pageY - lastPageY; 38 | lastPageY = e.pageY; 39 | 40 | raf(function() { 41 | context.el.scrollTop += delta / context.scrollRatio; 42 | }); 43 | } 44 | 45 | function stop() { 46 | el.classList.remove('ss-grabbed'); 47 | d.body.classList.remove('ss-grabbed'); 48 | d.removeEventListener('mousemove', drag); 49 | d.removeEventListener('mouseup', stop); 50 | } 51 | } 52 | 53 | // Constructor 54 | function ss(el) { 55 | this.target = el; 56 | this.content = el.firstElementChild; 57 | 58 | this.direction = w.getComputedStyle(this.target).direction; 59 | 60 | this.bar = '
'; 61 | //Create a reference to the function binding to remove the event listeners 62 | this.mB = this.moveBar.bind(this); 63 | 64 | this.wrapper = d.createElement('div'); 65 | this.wrapper.setAttribute('class', 'ss-wrapper'); 66 | 67 | this.el = d.createElement('div'); 68 | this.el.setAttribute('class', 'ss-content'); 69 | 70 | if (this.direction === 'rtl') { 71 | this.el.classList.add('rtl'); 72 | } 73 | 74 | this.wrapper.appendChild(this.el); 75 | 76 | while (this.target.firstChild) { 77 | this.el.appendChild(this.target.firstChild); 78 | } 79 | this.target.appendChild(this.wrapper); 80 | 81 | this.target.insertAdjacentHTML('beforeend', this.bar); 82 | this.bar = this.target.lastChild; 83 | 84 | dragDealer(this.bar, this); 85 | this.moveBar(); 86 | 87 | w.addEventListener('resize', this.mB); 88 | this.el.addEventListener('scroll', this.mB); 89 | this.el.addEventListener('mouseenter', this.mB); 90 | 91 | this.target.classList.add('ss-container'); 92 | 93 | var css = w.getComputedStyle(el); 94 | if (css['height'] === '0px' && css['max-height'] !== '0px') { 95 | el.style.height = css['max-height']; 96 | } 97 | 98 | this.unBind = function() { 99 | //Remove event listeners 100 | w.removeEventListener('resize', this.mB); 101 | this.el.removeEventListener('scroll', this.mB); 102 | this.el.removeEventListener('mouseenter', this.mB); 103 | 104 | this.target.classList.remove('ss-container'); 105 | 106 | //Unwrap the initial content and remove remaining wrappers 107 | this.target.insertBefore(this.content, this.wrapper); 108 | this.target.removeChild(this.wrapper); 109 | 110 | //Remove the bar including its drag-dealer event listener 111 | this.target.removeChild(this.bar); 112 | this.bar = null; //make way for the garbage collector 113 | } 114 | } 115 | 116 | ss.prototype = { 117 | moveBar: function(e) { 118 | var totalHeight = this.el.scrollHeight, 119 | ownHeight = this.el.clientHeight, 120 | _this = this; 121 | 122 | this.scrollRatio = ownHeight / totalHeight; 123 | 124 | var isRtl = _this.direction === 'rtl'; 125 | var right = isRtl ? 126 | (_this.target.clientWidth - _this.bar.clientWidth + 18) : 127 | (_this.target.clientWidth - _this.bar.clientWidth) * -1; 128 | 129 | raf(function() { 130 | // Hide scrollbar if no scrolling is possible 131 | if(_this.scrollRatio >= 1) { 132 | _this.bar.classList.add('ss-hidden') 133 | } else { 134 | _this.bar.classList.remove('ss-hidden') 135 | _this.bar.style.cssText = 'height:' + Math.max(_this.scrollRatio * 100, 10) + '%; top:' + (_this.el.scrollTop / totalHeight ) * 100 + '%;right:' + right + 'px;'; 136 | } 137 | }); 138 | } 139 | } 140 | 141 | function initAll() { 142 | var nodes = d.querySelectorAll('*[ss-container]'); 143 | 144 | for (var i = 0; i < nodes.length; i++) { 145 | initEl(nodes[i]); 146 | } 147 | } 148 | 149 | function unbindAll() { 150 | var nodes = d.querySelectorAll('.ss-container'); 151 | 152 | for (var i = 0; i < nodes.length; i++) { 153 | unbindEl(nodes[i]); 154 | } 155 | } 156 | 157 | d.addEventListener('DOMContentLoaded', initAll); 158 | ss.initEl = initEl; 159 | ss.initAll = initAll; 160 | ss.unbindEl = unbindEl; 161 | ss.unbindAll = unbindAll; 162 | 163 | var SimpleScrollbar = ss; 164 | return SimpleScrollbar; 165 | }); 166 | -------------------------------------------------------------------------------- /simple-scrollbar.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports?module.exports=e(window,document):t.SimpleScrollbar=e(window,document)}(this,function(t,e){function s(t){Object.prototype.hasOwnProperty.call(t,"data-simple-scrollbar")||Object.defineProperty(t,"data-simple-scrollbar",{value:new o(t)})}function i(t,s){function i(t){var e=t.pageY-a;a=t.pageY,n(function(){s.el.scrollTop+=e/s.scrollRatio})}function r(){t.classList.remove("ss-grabbed"),e.body.classList.remove("ss-grabbed"),e.removeEventListener("mousemove",i),e.removeEventListener("mouseup",r)}var a;t.addEventListener("mousedown",function(s){return a=s.pageY,t.classList.add("ss-grabbed"),e.body.classList.add("ss-grabbed"),e.addEventListener("mousemove",i),e.addEventListener("mouseup",r),!1})}function r(t){for(this.target=t,this.direction=window.getComputedStyle(this.target).direction,this.bar='
',this.wrapper=e.createElement("div"),this.wrapper.setAttribute("class","ss-wrapper"),this.el=e.createElement("div"),this.el.setAttribute("class","ss-content"),"rtl"===this.direction&&this.el.classList.add("rtl"),this.wrapper.appendChild(this.el);this.target.firstChild;)this.el.appendChild(this.target.firstChild);this.target.appendChild(this.wrapper),this.target.insertAdjacentHTML("beforeend",this.bar),this.bar=this.target.lastChild,i(this.bar,this),this.moveBar(),this.el.addEventListener("scroll",this.moveBar.bind(this)),this.el.addEventListener("mouseenter",this.moveBar.bind(this)),this.target.classList.add("ss-container");var s=window.getComputedStyle(t);"0px"===s.height&&"0px"!==s["max-height"]&&(t.style.height=s["max-height"])}function a(){for(var t=e.querySelectorAll("*[ss-container]"),i=0;i=1?i.bar.classList.add("ss-hidden"):(i.bar.classList.remove("ss-hidden"),i.bar.style.cssText="height:"+Math.max(100*i.scrollRatio,10)+"%; top:"+i.el.scrollTop/e*100+"%;right:"+a+"px;")})}},e.addEventListener("DOMContentLoaded",a),r.initEl=s,r.initAll=a;var o=r;return o}); 2 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | export default interface SimpleScrollBar { 2 | initEl (element: Element): void; 3 | initAll (): void; 4 | moveBar (e: Event): void; 5 | } 6 | --------------------------------------------------------------------------------