├── .gitignore ├── Gruntfile.js ├── README.md ├── bower.json ├── demo └── index.html ├── jquery.fs.scroller.css ├── jquery.fs.scroller.js ├── jquery.fs.scroller.min.css ├── jquery.fs.scroller.min.js ├── package.json ├── scroller.jquery.json └── src ├── jquery.fs.scroller-config.less ├── jquery.fs.scroller-styles.less ├── jquery.fs.scroller.js └── jquery.fs.scroller.less /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | 3 | // Less 4 | 5 | module.exports = function(grunt) { 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | meta: { 10 | banner: '/* \n' + 11 | ' * <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n' + 12 | ' * <%= pkg.description %> \n' + 13 | ' * <%= pkg.homepage %> \n' + 14 | ' * \n' + 15 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>; <%= pkg.license %> Licensed \n' + 16 | ' */\n' 17 | }, 18 | // JS Hint 19 | jshint: { 20 | options: { 21 | globals: { 22 | 'jQuery': true, 23 | '$' : true 24 | }, 25 | browser: true, 26 | curly: true, 27 | eqeqeq: true, 28 | forin: true, 29 | freeze: true, 30 | immed: true, 31 | latedef: true, 32 | newcap: true, 33 | noarg: true, 34 | nonew: true, 35 | smarttabs: true, 36 | sub: true, 37 | undef: true, 38 | validthis: true 39 | }, 40 | files: [ 41 | 'src/<%= pkg.codename %>.js' 42 | ] 43 | }, 44 | // Copy 45 | copy: { 46 | main: { 47 | files: [ 48 | { 49 | src: 'src/<%= pkg.codename %>.js', 50 | dest: '<%= pkg.codename %>.js' 51 | } 52 | ] 53 | } 54 | }, 55 | // Uglify 56 | uglify: { 57 | options: { 58 | report: 'min' 59 | }, 60 | target: { 61 | files: { 62 | '<%= pkg.codename %>.min.js': [ '<%= pkg.codename %>.js' ] 63 | } 64 | } 65 | }, 66 | // jQuery Manifest 67 | jquerymanifest: { 68 | options: { 69 | source: grunt.file.readJSON('package.json'), 70 | overrides: { 71 | name: '<%= pkg.id %>', 72 | keywords: '<%= pkg.keywords %>', 73 | homepage: '<%= pkg.homepage %>', 74 | docs: '<%= pkg.homepage %>', 75 | demo: '<%= pkg.homepage %>', 76 | download: '<%= pkg.repository.url %>', 77 | bugs: '<%= pkg.repository.url %>/issues', 78 | dependencies: { 79 | jquery: '>=1.7' 80 | } 81 | } 82 | } 83 | }, 84 | // LESS 85 | less: { 86 | main: { 87 | files: { 88 | '<%= pkg.codename %>.css': [ 89 | 'src/<%= pkg.codename %>-config.less', 90 | 'src/<%= pkg.codename %>.less' 91 | ] 92 | } 93 | }, 94 | min: { 95 | options: { 96 | report: 'min', 97 | cleancss: true 98 | }, 99 | files: { 100 | '<%= pkg.codename %>.min.css': [ 101 | 'src/<%= pkg.codename %>-config.less', 102 | 'src/<%= pkg.codename %>.less' 103 | ] 104 | } 105 | } 106 | }, 107 | // Auto Prefixer 108 | autoprefixer: { 109 | options: { 110 | browsers: [ '> 1%', 'last 5 versions', 'Firefox ESR', 'Opera 12.1', 'IE 8', 'IE 9' ] 111 | }, 112 | no_dest: { 113 | src: '*.css' 114 | } 115 | }, 116 | // Banner 117 | usebanner: { 118 | options: { 119 | position: 'top', 120 | banner: '<%= meta.banner %>' 121 | }, 122 | files: { 123 | src: [ 124 | '<%= pkg.codename %>.css', 125 | '<%= pkg.codename %>.js', 126 | '<%= pkg.codename %>.min.css', 127 | '<%= pkg.codename %>.min.js' 128 | ] 129 | } 130 | }, 131 | // Bower sync 132 | sync: { 133 | all: { 134 | options: { 135 | sync: [ 'name', 'version', 'description', 'author', 'license', 'homepage' ], 136 | overrides: { 137 | main: [ 138 | '<%= pkg.codename %>.js', 139 | '<%= pkg.codename %>.css' 140 | ], 141 | ignore: [ "*.jquery.json" ] 142 | } 143 | } 144 | } 145 | }, 146 | // Watcher - For dev only!! 147 | watch: { 148 | scripts: { 149 | files: [ 150 | 'src/**.js' 151 | ], 152 | tasks: [ 153 | 'jshint', 154 | 'copy' 155 | ] 156 | }, 157 | styles: { 158 | files: [ 159 | 'src/**.less' 160 | ], 161 | tasks: [ 162 | 'less', 163 | 'autoprefixer' 164 | ] 165 | } 166 | } 167 | }); 168 | 169 | // Load tasks 170 | grunt.loadNpmTasks('grunt-contrib-watch'); 171 | grunt.loadNpmTasks('grunt-contrib-jshint'); 172 | grunt.loadNpmTasks('grunt-contrib-copy'); 173 | grunt.loadNpmTasks('grunt-contrib-uglify'); 174 | grunt.loadNpmTasks('grunt-jquerymanifest'); 175 | grunt.loadNpmTasks('grunt-contrib-less'); 176 | grunt.loadNpmTasks('grunt-autoprefixer'); 177 | grunt.loadNpmTasks('grunt-banner'); 178 | grunt.loadNpmTasks('grunt-npm2bower-sync'); 179 | 180 | // Readme 181 | grunt.registerTask('buildReadme', 'Build Formstone README.md file.', function () { 182 | var pkg = grunt.file.readJSON('package.json'), 183 | extra = grunt.file.exists('src/README.md') ? '\n\n---\n\n' + grunt.file.read('src/README.md') : ''; 184 | destination = "README.md", 185 | markdown = '

Development of this plugin has ended. Please upgrade to the new Formstone.


\n\n' + 186 | 'Built with Grunt \n' + 187 | '# ' + pkg.name + ' \n\n' + 188 | pkg.description + ' \n\n' + 189 | '- [Demo](' + pkg.demo + ') \n' + 190 | '- [Documentation](' + pkg.homepage + ') \n\n' + 191 | '#### Bower Support \n' + 192 | '`bower install ' + pkg.name + '` ' + 193 | extra; 194 | 195 | grunt.file.write(destination, markdown); 196 | grunt.log.writeln('File "' + destination + '" created.'); 197 | }); 198 | 199 | // Default task. 200 | grunt.registerTask('default', [ 'jshint', 'copy', 'uglify', 'jquerymanifest', 'less', 'autoprefixer', 'usebanner', 'sync', 'buildReadme' ]); 201 | 202 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Development of this plugin has ended. Please upgrade to the new Formstone.


2 | 3 | Built with Grunt 4 | # Scroller 5 | 6 | A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library. 7 | 8 | - [Demo](http://classic.formstone.it/components/Scroller/demo/index.html) 9 | - [Documentation](http://classic.formstone.it/scroller/) 10 | 11 | #### Bower Support 12 | `bower install Scroller` -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scroller", 3 | "version": "3.1.2", 4 | "description": "A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library.", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "license": "MIT", 11 | "homepage": "http://classic.formstone.it/scroller/", 12 | "main": [ 13 | "jquery.fs.scroller.js", 14 | "jquery.fs.scroller.css" 15 | ], 16 | "ignore": [ 17 | "*.jquery.json" 18 | ] 19 | } -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Scroller Demo 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 36 | 37 | 90 | 91 | 92 | 93 | 94 | 95 | 102 |
103 |
104 |
105 |

Scroller Demo

106 |
107 | View Documentation 108 |
109 | 110 | 111 | 112 | 113 |

Basic

114 |

The most basic method is simply applying the scroller plugin:

115 | 116 |
$(".scrollbar").scroller();
117 | 118 |
Demo
119 |
120 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla porta aliquet gravida. Aenean pulvinar blandit orci vel fermentum. Phasellus viverra pulvinar viverra. Quisque et sagittis ante. Nulla sem neque, fermentum at laoreet vel, rhoncus sit amet nisl. Donec consectetur urna ac massa placerat ac pharetra sem mollis. Integer nibh urna, placerat vel placerat in, tempor at nisi. Aenean rutrum, enim sit amet rutrum fermentum, magna justo volutpat massa, lacinia adipiscing nisl magna eget nibh. In auctor, nibh eget faucibus tristique, arcu turpis molestie orci, placerat suscipit diam nibh et erat. Curabitur tempus lectus quis odio ornare porta.

121 |

Aenean odio libero, eleifend eu luctus quis, accumsan ac risus. Mauris egestas bibendum tortor vel semper. Ut egestas, erat ut rutrum tempus, massa erat commodo arcu, id congue justo odio ac erat. Quisque non purus et nunc consequat scelerisque. Vestibulum vitae tristique dolor. Suspendisse ac quam interdum libero euismod tempus. Sed a ante justo, varius commodo urna.

122 |

Ut ante felis, malesuada eu eleifend convallis, tempor eget tellus. Vestibulum rhoncus elementum dui a sagittis. Praesent eu nunc vitae massa cursus luctus ac eget massa. Etiam sagittis nibh nisi, vel sagittis felis. Aliquam vulputate mauris id nunc pellentesque at fermentum lacus dapibus. Donec pretium consectetur magna sit amet egestas. Ut dignissim adipiscing purus sit amet lobortis.

123 |

Curabitur ullamcorper, nisi quis convallis luctus, nunc nisi mollis felis, eu mattis nunc sapien sed sapien. Vestibulum in nisi mauris, mattis sagittis lacus. Pellentesque in sem augue, a blandit augue. Aliquam bibendum diam ac erat imperdiet vestibulum. Vivamus dignissim, quam in feugiat scelerisque, enim risus interdum turpis, in vehicula ante lorem eu erat. Mauris ac turpis luctus libero pulvinar ultricies. Praesent porttitor nulla non risus porta consequat. Nunc vulputate porta nulla non ultrices. Donec et magna eu nisl elementum scelerisque. Curabitur in vehicula dui.

124 |

Suspendisse eu nibh in libero euismod condimentum eget nec enim. Suspendisse nec ligula nec augue tristique semper sed suscipit erat. Integer et nunc a augue pellentesque fringilla. Nullam leo ligula, mattis id pretium ac, suscipit sed nunc. Duis ac lorem nec velit malesuada tempus hendrerit ut metus. Quisque a lacus vel lectus rhoncus luctus. Aliquam ornare, nunc sit amet porttitor ornare, libero lectus congue enim, vitae tincidunt sapien justo et ligula. Proin vestibulum blandit fringilla.

125 |
126 | 127 |
128 | 129 | 130 |

Advanced

131 |

Customize a specific Scroller instance:

132 | 133 |
$(".scrollbar").scroller({
134 | 	customClass: "advanced",
135 | 	trackMargin: 15,
136 | 	handleSize: 40
137 | });
138 | 139 |
Demo
140 |
141 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla porta aliquet gravida. Aenean pulvinar blandit orci vel fermentum. Phasellus viverra pulvinar viverra. Quisque et sagittis ante. Nulla sem neque, fermentum at laoreet vel, rhoncus sit amet nisl. Donec consectetur urna ac massa placerat ac pharetra sem mollis. Integer nibh urna, placerat vel placerat in, tempor at nisi. Aenean rutrum, enim sit amet rutrum fermentum, magna justo volutpat massa, lacinia adipiscing nisl magna eget nibh. In auctor, nibh eget faucibus tristique, arcu turpis molestie orci, placerat suscipit diam nibh et erat. Curabitur tempus lectus quis odio ornare porta.

142 |

Aenean odio libero, eleifend eu luctus quis, accumsan ac risus. Mauris egestas bibendum tortor vel semper. Ut egestas, erat ut rutrum tempus, massa erat commodo arcu, id congue justo odio ac erat. Quisque non purus et nunc consequat scelerisque. Vestibulum vitae tristique dolor. Suspendisse ac quam interdum libero euismod tempus. Sed a ante justo, varius commodo urna.

143 |

Ut ante felis, malesuada eu eleifend convallis, tempor eget tellus. Vestibulum rhoncus elementum dui a sagittis. Praesent eu nunc vitae massa cursus luctus ac eget massa. Etiam sagittis nibh nisi, vel sagittis felis. Aliquam vulputate mauris id nunc pellentesque at fermentum lacus dapibus. Donec pretium consectetur magna sit amet egestas. Ut dignissim adipiscing purus sit amet lobortis.

144 |

Curabitur ullamcorper, nisi quis convallis luctus, nunc nisi mollis felis, eu mattis nunc sapien sed sapien. Vestibulum in nisi mauris, mattis sagittis lacus. Pellentesque in sem augue, a blandit augue. Aliquam bibendum diam ac erat imperdiet vestibulum. Vivamus dignissim, quam in feugiat scelerisque, enim risus interdum turpis, in vehicula ante lorem eu erat. Mauris ac turpis luctus libero pulvinar ultricies. Praesent porttitor nulla non risus porta consequat. Nunc vulputate porta nulla non ultrices. Donec et magna eu nisl elementum scelerisque. Curabitur in vehicula dui.

145 |

Suspendisse eu nibh in libero euismod condimentum eget nec enim. Suspendisse nec ligula nec augue tristique semper sed suscipit erat. Integer et nunc a augue pellentesque fringilla. Nullam leo ligula, mattis id pretium ac, suscipit sed nunc. Duis ac lorem nec velit malesuada tempus hendrerit ut metus. Quisque a lacus vel lectus rhoncus luctus. Aliquam ornare, nunc sit amet porttitor ornare, libero lectus congue enim, vitae tincidunt sapien justo et ligula. Proin vestibulum blandit fringilla.

146 |
147 | 148 |
149 | 150 | 151 |

Horizontal

152 |

Add a horizontal scrollbar to a content element:

153 | 154 |
$(".scrollbar").scroller({
155 | 	horizontal: true
156 | });
157 | 158 |
Demo
159 |
160 |
161 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla porta aliquet gravida. Aenean pulvinar blandit orci vel fermentum. Phasellus viverra pulvinar viverra. Quisque et sagittis ante. Nulla sem neque, fermentum at laoreet vel, rhoncus sit amet nisl. Donec consectetur urna ac massa placerat ac pharetra sem mollis. Integer nibh urna, placerat vel placerat in, tempor at nisi.

162 |

Aenean odio libero, eleifend eu luctus quis, accumsan ac risus. Mauris egestas bibendum tortor vel semper. Ut egestas, erat ut rutrum tempus, massa erat commodo arcu, id congue justo odio ac erat. Quisque non purus et nunc consequat scelerisque. Vestibulum vitae tristique dolor. Suspendisse ac quam interdum libero euismod tempus. Sed a ante justo, varius commodo urna.

163 |

Ut ante felis, malesuada eu eleifend convallis, tempor eget tellus. Vestibulum rhoncus elementum dui a sagittis. Praesent eu nunc vitae massa cursus luctus ac eget massa. Etiam sagittis nibh nisi, vel sagittis felis. Aliquam vulputate mauris id nunc pellentesque at fermentum lacus dapibus. Donec pretium consectetur magna sit amet egestas. Ut dignissim adipiscing purus sit amet lobortis.

164 |

Curabitur ullamcorper, nisi quis convallis luctus, nunc nisi mollis felis, eu mattis nunc sapien sed sapien. Vestibulum in nisi mauris, mattis sagittis lacus. Pellentesque in sem augue, a blandit augue. Aliquam bibendum diam ac erat imperdiet vestibulum. Vivamus dignissim, quam in feugiat scelerisque, enim risus interdum turpis, in vehicula ante lorem eu erat. Mauris ac turpis luctus libero pulvinar ultricies.

165 |

Suspendisse eu nibh in libero euismod condimentum eget nec enim. Suspendisse nec ligula nec augue tristique semper sed suscipit erat. Integer et nunc a augue pellentesque fringilla. Nullam leo ligula, mattis id pretium ac, suscipit sed nunc. Duis ac lorem nec velit malesuada tempus hendrerit ut metus. Quisque a lacus vel lectus rhoncus luctus.

166 |
167 |
168 | 169 |
170 | 171 | 172 |

Reset

173 |

After appending content, you will need to call the reset method on the target element:

174 | 175 |
$(".scrollbar").scroller("reset");
176 | 177 |
Demo
178 |
179 | Append Content 180 |
181 |
182 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

183 |
184 | 185 |
186 | 187 | 188 |

Scroll

189 |

Manually scroll an instance to a specific pixel value or selector:

190 | 191 |
$(".scrollbar").scroller("scroll", [value | selector]);
192 | 193 |
Demo
194 | 200 |
201 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla porta aliquet gravida. Aenean pulvinar blandit orci vel fermentum. Phasellus viverra pulvinar viverra. Quisque et sagittis ante. Nulla sem neque, fermentum at laoreet vel, rhoncus sit amet nisl. Donec consectetur urna ac massa placerat ac pharetra sem mollis. Integer nibh urna, placerat vel placerat in, tempor at nisi. Aenean rutrum, enim sit amet rutrum fermentum, magna justo volutpat massa, lacinia adipiscing nisl magna eget nibh. In auctor, nibh eget faucibus tristique, arcu turpis molestie orci, placerat suscipit diam nibh et erat. Curabitur tempus lectus quis odio ornare porta.

202 |

Aenean odio libero, eleifend eu luctus quis, accumsan ac risus. Mauris egestas bibendum tortor vel semper. Ut egestas, erat ut rutrum tempus, massa erat commodo arcu, id congue justo odio ac erat. Quisque non purus et nunc consequat scelerisque. Vestibulum vitae tristique dolor. Suspendisse ac quam interdum libero euismod tempus. Sed a ante justo, varius commodo urna.

203 |

Ut ante felis, malesuada eu eleifend convallis, tempor eget tellus. Vestibulum rhoncus elementum dui a sagittis. Praesent eu nunc vitae massa cursus luctus ac eget massa. Etiam sagittis nibh nisi, vel sagittis felis. Aliquam vulputate mauris id nunc pellentesque at fermentum lacus dapibus. Donec pretium consectetur magna sit amet egestas. Ut dignissim adipiscing purus sit amet lobortis.

204 |

Curabitur ullamcorper, nisi quis convallis luctus, nunc nisi mollis felis, eu mattis nunc sapien sed sapien. Vestibulum in nisi mauris, mattis sagittis lacus. Pellentesque in sem augue, a blandit augue. Aliquam bibendum diam ac erat imperdiet vestibulum. Vivamus dignissim, quam in feugiat scelerisque, enim risus interdum turpis, in vehicula ante lorem eu erat. Mauris ac turpis luctus libero pulvinar ultricies. Praesent porttitor nulla non risus porta consequat. Nunc vulputate porta nulla non ultrices. Donec et magna eu nisl elementum scelerisque. Curabitur in vehicula dui.

205 |

Suspendisse eu nibh in libero euismod condimentum eget nec enim. Suspendisse nec ligula nec augue tristique semper sed suscipit erat. Integer et nunc a augue pellentesque fringilla. Nullam leo ligula, mattis id pretium ac, suscipit sed nunc. Duis ac lorem nec velit malesuada tempus hendrerit ut metus. Quisque a lacus vel lectus rhoncus luctus. Aliquam ornare, nunc sit amet porttitor ornare, libero lectus congue enim, vitae tincidunt sapien justo et ligula. Proin vestibulum blandit fringilla.

206 |
207 | 208 | 209 | 210 |
211 |
212 | 217 | 218 | -------------------------------------------------------------------------------- /jquery.fs.scroller.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Scroller v3.1.2 - 2015-04-04 3 | * A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library. 4 | * http://classic.formstone.it/scroller/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | .fs-scrollbar { 10 | overflow: hidden; 11 | overflow-x: hidden; 12 | overflow-y: hidden; 13 | position: relative; 14 | } 15 | .fs-scrollbar, 16 | .fs-scrollbar * { 17 | -webkit-user-select: none !important; 18 | -moz-user-select: none !important; 19 | -ms-user-select: none !important; 20 | user-select: none !important; 21 | } 22 | .fs-scrollbar, 23 | .fs-scrollbar-content, 24 | .fs-scrollbar-bar, 25 | .fs-scrollbar-track, 26 | .fs-scrollbar-handle { 27 | box-sizing: border-box; 28 | } 29 | .fs-scrollbar-content { 30 | position: relative; 31 | z-index: 1; 32 | height: 100%; 33 | overflow: auto; 34 | overflow-x: hidden; 35 | overflow-y: auto; 36 | -webkit-overflow-scrolling: touch; 37 | } 38 | .fs-scrollbar-content::-webkit-scrollbar, 39 | .fs-scrollbar-content::-webkit-scrollbar-button, 40 | .fs-scrollbar-content::-webkit-scrollbar-track, 41 | .fs-scrollbar-content::-webkit-scrollbar-track-piece, 42 | .fs-scrollbar-content::-webkit-scrollbar-thumb, 43 | .fs-scrollbar-content::-webkit-scrollbar-corner, 44 | .fs-scrollbar-content::-webkit-resizer { 45 | background: transparent; 46 | opacity: 0; 47 | } 48 | .fs-scrollbar-bar { 49 | width: 16px; 50 | height: 100%; 51 | position: absolute; 52 | right: 0; 53 | top: 0; 54 | z-index: 2; 55 | background: #ffffff; 56 | border: 1px solid #eeeeee; 57 | border-width: 0 0 0 1px; 58 | display: none; 59 | } 60 | .fs-scrollbar-track { 61 | width: 100%; 62 | height: 100%; 63 | position: relative; 64 | background: #ffffff; 65 | overflow: hidden; 66 | } 67 | .fs-scrollbar-handle { 68 | width: 10px; 69 | height: 20px; 70 | position: absolute; 71 | top: 0; 72 | right: 3px; 73 | z-index: 2; 74 | background: #cccccc; 75 | border: 1px solid #ffffff; 76 | border-radius: 5px; 77 | cursor: pointer; 78 | } 79 | .fs-scrollbar-horizontal .fs-scrollbar-content { 80 | overflow: auto; 81 | overflow-x: auto; 82 | overflow-y: hidden; 83 | padding: 0 0 16px 0; 84 | } 85 | .fs-scrollbar-horizontal .fs-scrollbar-bar { 86 | width: 100%; 87 | height: 16px; 88 | top: auto; 89 | bottom: 0; 90 | border-width: 1px 0 0 0; 91 | } 92 | .fs-scrollbar-horizontal .fs-scrollbar-handle { 93 | width: 20px; 94 | height: 10px; 95 | top: auto; 96 | right: auto; 97 | bottom: 3px; 98 | } 99 | .fs-scrollbar-setup .fs-scrollbar-content, 100 | .fs-scrollbar-active .fs-scrollbar-content { 101 | padding: 20px; 102 | } 103 | .fs-scrollbar-setup .fs-scrollbar-bar, 104 | .fs-scrollbar-active .fs-scrollbar-bar { 105 | display: block; 106 | } 107 | 108 | .fs-scrollbar { 109 | overflow: hidden; 110 | overflow-x: hidden; 111 | overflow-y: hidden; 112 | position: relative; 113 | } 114 | .fs-scrollbar, 115 | .fs-scrollbar * { 116 | -webkit-user-select: none !important; 117 | -moz-user-select: none !important; 118 | -ms-user-select: none !important; 119 | user-select: none !important; 120 | } 121 | .fs-scrollbar, 122 | .fs-scrollbar-content, 123 | .fs-scrollbar-bar, 124 | .fs-scrollbar-track, 125 | .fs-scrollbar-handle { 126 | box-sizing: border-box; 127 | } 128 | .fs-scrollbar-content { 129 | position: relative; 130 | z-index: 1; 131 | height: 100%; 132 | overflow: auto; 133 | overflow-x: hidden; 134 | overflow-y: auto; 135 | -webkit-overflow-scrolling: touch; 136 | } 137 | .fs-scrollbar-content::-webkit-scrollbar, 138 | .fs-scrollbar-content::-webkit-scrollbar-button, 139 | .fs-scrollbar-content::-webkit-scrollbar-track, 140 | .fs-scrollbar-content::-webkit-scrollbar-track-piece, 141 | .fs-scrollbar-content::-webkit-scrollbar-thumb, 142 | .fs-scrollbar-content::-webkit-scrollbar-corner, 143 | .fs-scrollbar-content::-webkit-resizer { 144 | background: transparent; 145 | opacity: 0; 146 | } 147 | .fs-scrollbar-bar { 148 | width: 16px; 149 | height: 100%; 150 | position: absolute; 151 | right: 0; 152 | top: 0; 153 | z-index: 2; 154 | background: #ffffff; 155 | border: 1px solid #eeeeee; 156 | border-width: 0 0 0 1px; 157 | display: none; 158 | } 159 | .fs-scrollbar-track { 160 | width: 100%; 161 | height: 100%; 162 | position: relative; 163 | background: #ffffff; 164 | overflow: hidden; 165 | } 166 | .fs-scrollbar-handle { 167 | width: 10px; 168 | height: 20px; 169 | position: absolute; 170 | top: 0; 171 | right: 3px; 172 | z-index: 2; 173 | background: #cccccc; 174 | border: 1px solid #ffffff; 175 | border-radius: 5px; 176 | cursor: pointer; 177 | } 178 | .fs-scrollbar-horizontal .fs-scrollbar-content { 179 | overflow: auto; 180 | overflow-x: auto; 181 | overflow-y: hidden; 182 | padding: 0 0 16px 0; 183 | } 184 | .fs-scrollbar-horizontal .fs-scrollbar-bar { 185 | width: 100%; 186 | height: 16px; 187 | top: auto; 188 | bottom: 0; 189 | border-width: 1px 0 0 0; 190 | } 191 | .fs-scrollbar-horizontal .fs-scrollbar-handle { 192 | width: 20px; 193 | height: 10px; 194 | top: auto; 195 | right: auto; 196 | bottom: 3px; 197 | } 198 | .fs-scrollbar-setup .fs-scrollbar-content, 199 | .fs-scrollbar-active .fs-scrollbar-content { 200 | padding: 20px; 201 | } 202 | .fs-scrollbar-setup .fs-scrollbar-bar, 203 | .fs-scrollbar-active .fs-scrollbar-bar { 204 | display: block; 205 | } 206 | .scroller { 207 | overflow: hidden; 208 | overflow-x: hidden; 209 | overflow-y: hidden; 210 | position: relative; 211 | } 212 | .scroller, 213 | .scroller * { 214 | -webkit-user-select: none !important; 215 | -moz-user-select: none !important; 216 | -ms-user-select: none !important; 217 | user-select: none !important; 218 | } 219 | .scroller, 220 | .scroller-content, 221 | .scroller-bar, 222 | .scroller-track, 223 | .scroller-handle { 224 | box-sizing: border-box; 225 | } 226 | .scroller-content { 227 | position: relative; 228 | z-index: 1; 229 | height: 100%; 230 | overflow: auto; 231 | overflow-x: hidden; 232 | overflow-y: auto; 233 | -webkit-overflow-scrolling: touch; 234 | } 235 | .scroller-content::-webkit-scrollbar, 236 | .scroller-content::-webkit-scrollbar-button, 237 | .scroller-content::-webkit-scrollbar-track, 238 | .scroller-content::-webkit-scrollbar-track-piece, 239 | .scroller-content::-webkit-scrollbar-thumb, 240 | .scroller-content::-webkit-scrollbar-corner, 241 | .scroller-content::-webkit-resizer { 242 | background: transparent; 243 | opacity: 0; 244 | } 245 | .scroller-bar { 246 | width: 16px; 247 | height: 100%; 248 | position: absolute; 249 | right: 0; 250 | top: 0; 251 | z-index: 2; 252 | background: #ffffff; 253 | border: 1px solid #eeeeee; 254 | border-width: 0 0 0 1px; 255 | display: none; 256 | } 257 | .scroller-track { 258 | width: 100%; 259 | height: 100%; 260 | position: relative; 261 | background: #ffffff; 262 | overflow: hidden; 263 | } 264 | .scroller-handle { 265 | width: 10px; 266 | height: 20px; 267 | position: absolute; 268 | top: 0; 269 | right: 3px; 270 | z-index: 2; 271 | background: #cccccc; 272 | border: 1px solid #ffffff; 273 | border-radius: 5px; 274 | cursor: pointer; 275 | } 276 | .scroller-horizontal .scroller-content { 277 | overflow: auto; 278 | overflow-x: auto; 279 | overflow-y: hidden; 280 | padding: 0 0 16px 0; 281 | } 282 | .scroller-horizontal .scroller-bar { 283 | width: 100%; 284 | height: 16px; 285 | top: auto; 286 | bottom: 0; 287 | border-width: 1px 0 0 0; 288 | } 289 | .scroller-horizontal .scroller-handle { 290 | width: 20px; 291 | height: 10px; 292 | top: auto; 293 | right: auto; 294 | bottom: 3px; 295 | } 296 | .scroller-setup .scroller-content, 297 | .scroller-active .scroller-content { 298 | padding: 20px; 299 | } 300 | .scroller-setup .scroller-bar, 301 | .scroller-active .scroller-bar { 302 | display: block; 303 | } 304 | -------------------------------------------------------------------------------- /jquery.fs.scroller.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Scroller v3.1.2 - 2015-04-04 3 | * A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library. 4 | * http://classic.formstone.it/scroller/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | ;(function ($, window) { 10 | "use strict"; 11 | 12 | var namespace = "scroller", 13 | $body = null, 14 | classes = { 15 | base: "scroller", 16 | content: "scroller-content", 17 | bar: "scroller-bar", 18 | track: "scroller-track", 19 | handle: "scroller-handle", 20 | isHorizontal: "scroller-horizontal", 21 | isSetup: "scroller-setup", 22 | isActive: "scroller-active" 23 | }, 24 | events = { 25 | start: "touchstart." + namespace + " mousedown." + namespace, 26 | move: "touchmove." + namespace + " mousemove." + namespace, 27 | end: "touchend." + namespace + " mouseup." + namespace 28 | }; 29 | 30 | /** 31 | * @options 32 | * @param customClass [string] <''> "Class applied to instance" 33 | * @param duration [int] <0> "Scroll animation length" 34 | * @param handleSize [int] <0> "Handle size; 0 to auto size" 35 | * @param horizontal [boolean] "Scroll horizontally" 36 | * @param trackMargin [int] <0> "Margin between track and handle edge” 37 | */ 38 | var options = { 39 | customClass: "", 40 | duration: 0, 41 | handleSize: 0, 42 | horizontal: false, 43 | trackMargin: 0 44 | }; 45 | 46 | var pub = { 47 | 48 | /** 49 | * @method 50 | * @name defaults 51 | * @description Sets default plugin options 52 | * @param opts [object] <{}> "Options object" 53 | * @example $.scroller("defaults", opts); 54 | */ 55 | defaults: function(opts) { 56 | options = $.extend(options, opts || {}); 57 | return (typeof this === 'object') ? $(this) : true; 58 | }, 59 | 60 | /** 61 | * @method 62 | * @name destroy 63 | * @description Removes instance of plugin 64 | * @example $(".target").scroller("destroy"); 65 | */ 66 | destroy: function() { 67 | return $(this).each(function(i, el) { 68 | var data = $(el).data(namespace); 69 | 70 | if (data) { 71 | data.$scroller.removeClass( [data.customClass, classes.base, classes.isActive].join(" ") ); 72 | 73 | data.$bar.remove(); 74 | data.$content.contents().unwrap(); 75 | 76 | data.$content.off( classify(namespace) ); 77 | data.$scroller.off( classify(namespace) ) 78 | .removeData(namespace); 79 | } 80 | }); 81 | }, 82 | 83 | /** 84 | * @method 85 | * @name scroll 86 | * @description Scrolls instance of plugin to element or position 87 | * @param pos [string || int] "Target element selector or static position" 88 | * @param duration [int] "Optional scroll duration" 89 | * @example $.scroller("scroll", pos, duration); 90 | */ 91 | scroll: function(pos, dur) { 92 | return $(this).each(function(i) { 93 | var data = $(this).data(namespace), 94 | duration = dur || options.duration; 95 | 96 | if (typeof pos !== "number") { 97 | var $el = $(pos); 98 | if ($el.length > 0) { 99 | var offset = $el.position(); 100 | if (data.horizontal) { 101 | pos = offset.left + data.$content.scrollLeft(); 102 | } else { 103 | pos = offset.top + data.$content.scrollTop(); 104 | } 105 | } else { 106 | pos = data.$content.scrollTop(); 107 | } 108 | } 109 | 110 | var styles = data.horizontal ? { scrollLeft: pos } : { scrollTop: pos }; 111 | 112 | data.$content.stop().animate(styles, duration); 113 | }); 114 | }, 115 | 116 | /** 117 | * @method 118 | * @name reset 119 | * @description Resets layout on instance of plugin 120 | * @example $.scroller("reset"); 121 | */ 122 | reset: function() { 123 | return $(this).each(function(i) { 124 | var data = $(this).data(namespace); 125 | 126 | if (data) { 127 | data.$scroller.addClass(classes.isSetup); 128 | 129 | var barStyles = {}, 130 | trackStyles = {}, 131 | handleStyles = {}, 132 | handlePosition = 0, 133 | isActive = true; 134 | 135 | if (data.horizontal) { 136 | // Horizontal 137 | data.barHeight = data.$content[0].offsetHeight - data.$content[0].clientHeight; 138 | data.frameWidth = data.$content.outerWidth(); 139 | data.trackWidth = data.frameWidth - (data.trackMargin * 2); 140 | data.scrollWidth = data.$content[0].scrollWidth; 141 | data.ratio = data.trackWidth / data.scrollWidth; 142 | data.trackRatio = data.trackWidth / data.scrollWidth; 143 | data.handleWidth = (data.handleSize > 0) ? data.handleSize : data.trackWidth * data.trackRatio; 144 | data.scrollRatio = (data.scrollWidth - data.frameWidth) / (data.trackWidth - data.handleWidth); 145 | data.handleBounds = { 146 | left: 0, 147 | right: data.trackWidth - data.handleWidth 148 | }; 149 | 150 | data.$content.css({ 151 | paddingBottom: data.barHeight + data.paddingBottom 152 | }); 153 | 154 | var scrollLeft = data.$content.scrollLeft(); 155 | 156 | handlePosition = scrollLeft * data.ratio; 157 | isActive = (data.scrollWidth <= data.frameWidth); 158 | 159 | barStyles = { 160 | width: data.frameWidth 161 | }; 162 | 163 | trackStyles = { 164 | width: data.trackWidth, 165 | marginLeft: data.trackMargin, 166 | marginRight: data.trackMargin 167 | }; 168 | 169 | handleStyles = { 170 | width: data.handleWidth 171 | }; 172 | } else { 173 | // Vertical 174 | data.barWidth = data.$content[0].offsetWidth - data.$content[0].clientWidth; 175 | data.frameHeight = data.$content.outerHeight(); 176 | data.trackHeight = data.frameHeight - (data.trackMargin * 2); 177 | data.scrollHeight = data.$content[0].scrollHeight; 178 | data.ratio = data.trackHeight / data.scrollHeight; 179 | data.trackRatio = data.trackHeight / data.scrollHeight; 180 | data.handleHeight = (data.handleSize > 0) ? data.handleSize : data.trackHeight * data.trackRatio; 181 | data.scrollRatio = (data.scrollHeight - data.frameHeight) / (data.trackHeight - data.handleHeight); 182 | data.handleBounds = { 183 | top: 0, 184 | bottom: data.trackHeight - data.handleHeight 185 | }; 186 | 187 | var scrollTop = data.$content.scrollTop(); 188 | 189 | handlePosition = scrollTop * data.ratio; 190 | isActive = (data.scrollHeight <= data.frameHeight); 191 | 192 | barStyles = { 193 | height: data.frameHeight 194 | }; 195 | 196 | trackStyles = { 197 | height: data.trackHeight, 198 | marginBottom: data.trackMargin, 199 | marginTop: data.trackMargin 200 | }; 201 | 202 | handleStyles = { 203 | height: data.handleHeight 204 | }; 205 | } 206 | 207 | // Updates 208 | 209 | if (isActive) { 210 | data.$scroller.removeClass(classes.isActive); 211 | } else { 212 | data.$scroller.addClass(classes.isActive); 213 | } 214 | 215 | data.$bar.css(barStyles); 216 | data.$track.css(trackStyles); 217 | data.$handle.css(handleStyles); 218 | 219 | position(data, handlePosition); 220 | 221 | data.$scroller.removeClass(classes.isSetup); 222 | } 223 | }); 224 | } 225 | }; 226 | 227 | /** 228 | * @method private 229 | * @name init 230 | * @description Initializes plugin 231 | * @param opts [object] "Initialization options" 232 | */ 233 | function init(opts) { 234 | // Local options 235 | opts = $.extend({}, options, opts || {}); 236 | 237 | // Check for Body 238 | if ($body === null) { 239 | $body = $("body"); 240 | } 241 | 242 | // Apply to each element 243 | var $items = $(this); 244 | for (var i = 0, count = $items.length; i < count; i++) { 245 | build($items.eq(i), opts); 246 | } 247 | return $items; 248 | } 249 | 250 | /** 251 | * @method private 252 | * @name build 253 | * @description Builds each instance 254 | * @param $scroller [jQuery object] "Target jQuery object" 255 | * @param opts [object] <{}> "Options object" 256 | */ 257 | function build($scroller, opts) { 258 | if (!$scroller.hasClass(classes.base)) { 259 | // EXTEND OPTIONS 260 | opts = $.extend({}, opts, $scroller.data(namespace + "-options")); 261 | 262 | var html = ''; 263 | 264 | html += '
'; 265 | html += '
'; 266 | html += '
'; 267 | html += '
'; 268 | 269 | opts.paddingRight = parseInt($scroller.css("padding-right"), 10); 270 | opts.paddingBottom = parseInt($scroller.css("padding-bottom"), 10); 271 | 272 | $scroller.addClass( [classes.base, opts.customClass].join(" ") ) 273 | .wrapInner('
') 274 | .prepend(html); 275 | 276 | if (opts.horizontal) { 277 | $scroller.addClass(classes.isHorizontal); 278 | } 279 | 280 | var data = $.extend({ 281 | $scroller: $scroller, 282 | $content: $scroller.find( classify(classes.content) ), 283 | $bar: $scroller.find( classify(classes.bar) ), 284 | $track: $scroller.find( classify(classes.track) ), 285 | $handle: $scroller.find( classify(classes.handle) ) 286 | }, opts); 287 | 288 | data.trackMargin = parseInt(data.trackMargin, 10); 289 | 290 | data.$content.on("scroll." + namespace, data, onScroll); 291 | data.$scroller.on(events.start, classify(classes.track), data, onTrackDown) 292 | .on(events.start, classify(classes.handle), data, onHandleDown) 293 | .data(namespace, data); 294 | 295 | pub.reset.apply($scroller); 296 | 297 | $(window).one("load", function() { 298 | pub.reset.apply($scroller); 299 | }); 300 | } 301 | } 302 | 303 | /** 304 | * @method private 305 | * @name onScroll 306 | * @description Handles scroll event 307 | * @param e [object] "Event data" 308 | */ 309 | function onScroll(e) { 310 | e.preventDefault(); 311 | e.stopPropagation(); 312 | 313 | var data = e.data, 314 | handleStyles = {}; 315 | 316 | if (data.horizontal) { 317 | // Horizontal 318 | var scrollLeft = data.$content.scrollLeft(); 319 | 320 | if (scrollLeft < 0) { 321 | scrollLeft = 0; 322 | } 323 | 324 | var handleLeft = scrollLeft / data.scrollRatio; 325 | 326 | if (handleLeft > data.handleBounds.right) { 327 | handleLeft = data.handleBounds.right; 328 | } 329 | 330 | handleStyles = { 331 | left: handleLeft 332 | }; 333 | } else { 334 | // Vertical 335 | var scrollTop = data.$content.scrollTop(); 336 | 337 | if (scrollTop < 0) { 338 | scrollTop = 0; 339 | } 340 | 341 | var handleTop = scrollTop / data.scrollRatio; 342 | 343 | if (handleTop > data.handleBounds.bottom) { 344 | handleTop = data.handleBounds.bottom; 345 | } 346 | 347 | handleStyles = { 348 | top: handleTop 349 | }; 350 | } 351 | 352 | data.$handle.css(handleStyles); 353 | } 354 | 355 | /** 356 | * @method private 357 | * @name onTrackDown 358 | * @description Handles mousedown event on track 359 | * @param e [object] "Event data" 360 | */ 361 | function onTrackDown(e) { 362 | e.preventDefault(); 363 | e.stopPropagation(); 364 | 365 | var data = e.data, 366 | oe = e.originalEvent, 367 | offset = data.$track.offset(), 368 | touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null, 369 | pageX = (touch) ? touch.pageX : e.clientX, 370 | pageY = (touch) ? touch.pageY : e.clientY; 371 | 372 | if (data.horizontal) { 373 | // Horizontal 374 | data.mouseStart = pageX; 375 | data.handleLeft = pageX - offset.left - (data.handleWidth / 2); 376 | 377 | position(data, data.handleLeft); 378 | } else { 379 | // Vertical 380 | data.mouseStart = pageY; 381 | data.handleTop = pageY - offset.top - (data.handleHeight / 2); 382 | 383 | position(data, data.handleTop); 384 | } 385 | 386 | onStart(data); 387 | } 388 | 389 | /** 390 | * @method private 391 | * @name onHandleDown 392 | * @description Handles mousedown event on handle 393 | * @param e [object] "Event data" 394 | */ 395 | function onHandleDown(e) { 396 | e.preventDefault(); 397 | e.stopPropagation(); 398 | 399 | var data = e.data, 400 | oe = e.originalEvent, 401 | touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null, 402 | pageX = (touch) ? touch.pageX : e.clientX, 403 | pageY = (touch) ? touch.pageY : e.clientY; 404 | 405 | if (data.horizontal) { 406 | // Horizontal 407 | data.mouseStart = pageX; 408 | data.handleLeft = parseInt(data.$handle.css("left"), 10); 409 | } else { 410 | // Vertical 411 | data.mouseStart = pageY; 412 | data.handleTop = parseInt(data.$handle.css("top"), 10); 413 | } 414 | 415 | onStart(data); 416 | } 417 | 418 | /** 419 | * @method private 420 | * @name onStart 421 | * @description Handles touch.mouse start 422 | * @param data [object] "Instance data" 423 | */ 424 | function onStart(data) { 425 | data.$content.off( classify(namespace) ); 426 | 427 | $body.on(events.move, data, onMouseMove) 428 | .on(events.end, data, onMouseUp); 429 | } 430 | 431 | /** 432 | * @method private 433 | * @name onMouseMove 434 | * @description Handles mousemove event 435 | * @param e [object] "Event data" 436 | */ 437 | function onMouseMove(e) { 438 | e.preventDefault(); 439 | e.stopPropagation(); 440 | 441 | var data = e.data, 442 | oe = e.originalEvent, 443 | pos = 0, 444 | delta = 0, 445 | touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null, 446 | pageX = (touch) ? touch.pageX : e.clientX, 447 | pageY = (touch) ? touch.pageY : e.clientY; 448 | 449 | if (data.horizontal) { 450 | // Horizontal 451 | delta = data.mouseStart - pageX; 452 | pos = data.handleLeft - delta; 453 | } else { 454 | // Vertical 455 | delta = data.mouseStart - pageY; 456 | pos = data.handleTop - delta; 457 | } 458 | 459 | position(data, pos); 460 | } 461 | 462 | /** 463 | * @method private 464 | * @name onMouseUp 465 | * @description Handles mouseup event 466 | * @param e [object] "Event data" 467 | */ 468 | function onMouseUp(e) { 469 | e.preventDefault(); 470 | e.stopPropagation(); 471 | 472 | var data = e.data; 473 | 474 | data.$content.on("scroll.scroller", data, onScroll); 475 | $body.off(".scroller"); 476 | } 477 | 478 | /** 479 | * @method private 480 | * @name onTouchEnd 481 | * @description Handles mouseup event 482 | * @param e [object] "Event data" 483 | */ 484 | function onTouchEnd(e) { 485 | e.preventDefault(); 486 | e.stopPropagation(); 487 | 488 | var data = e.data; 489 | 490 | data.$content.on("scroll.scroller", data, onScroll); 491 | $body.off(".scroller"); 492 | } 493 | 494 | /** 495 | * @method private 496 | * @name position 497 | * @description Position handle based on scroll 498 | * @param data [object] "Instance data" 499 | * @param pos [int] "Scroll position" 500 | */ 501 | function position(data, pos) { 502 | var handleStyles = {}; 503 | 504 | if (data.horizontal) { 505 | // Horizontal 506 | if (pos < data.handleBounds.left) { 507 | pos = data.handleBounds.left; 508 | } 509 | 510 | if (pos > data.handleBounds.right) { 511 | pos = data.handleBounds.right; 512 | } 513 | 514 | var scrollLeft = Math.round(pos * data.scrollRatio); 515 | 516 | handleStyles = { 517 | left: pos 518 | }; 519 | 520 | data.$content.scrollLeft( scrollLeft ); 521 | } else { 522 | // Vertical 523 | if (pos < data.handleBounds.top) { 524 | pos = data.handleBounds.top; 525 | } 526 | 527 | if (pos > data.handleBounds.bottom) { 528 | pos = data.handleBounds.bottom; 529 | } 530 | 531 | var scrollTop = Math.round(pos * data.scrollRatio); 532 | 533 | handleStyles = { 534 | top: pos 535 | }; 536 | 537 | data.$content.scrollTop( scrollTop ); 538 | } 539 | 540 | data.$handle.css(handleStyles); 541 | } 542 | 543 | /** 544 | * @method private 545 | * @name classify 546 | * @description Create class selector from text 547 | * @param text [string] "Text to convert" 548 | * @return [string] "New class name" 549 | */ 550 | function classify(text) { 551 | return "." + text; 552 | } 553 | 554 | $.fn[namespace] = function(method) { 555 | if (pub[method]) { 556 | return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); 557 | } else if (typeof method === 'object' || !method) { 558 | return init.apply(this, arguments); 559 | } 560 | return this; 561 | }; 562 | 563 | $[namespace] = function(method) { 564 | if (method === "defaults") { 565 | pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); 566 | } 567 | }; 568 | })(jQuery); 569 | -------------------------------------------------------------------------------- /jquery.fs.scroller.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Scroller v3.1.2 - 2015-04-04 3 | * A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library. 4 | * http://classic.formstone.it/scroller/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | .fs-scrollbar{overflow:hidden;overflow-x:hidden;overflow-y:hidden;position:relative}.fs-scrollbar,.fs-scrollbar *{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.fs-scrollbar,.fs-scrollbar-content,.fs-scrollbar-bar,.fs-scrollbar-track,.fs-scrollbar-handle{box-sizing:border-box}.fs-scrollbar-content{position:relative;z-index:1;height:100%;overflow:auto;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.fs-scrollbar-content::-webkit-scrollbar,.fs-scrollbar-content::-webkit-scrollbar-button,.fs-scrollbar-content::-webkit-scrollbar-track,.fs-scrollbar-content::-webkit-scrollbar-track-piece,.fs-scrollbar-content::-webkit-scrollbar-thumb,.fs-scrollbar-content::-webkit-scrollbar-corner,.fs-scrollbar-content::-webkit-resizer{background:0 0;opacity:0}.fs-scrollbar-bar{width:16px;height:100%;position:absolute;right:0;top:0;z-index:2;background:#fff;border:1px solid #eee;border-width:0 0 0 1px;display:none}.fs-scrollbar-track{width:100%;height:100%;position:relative;background:#fff;overflow:hidden}.fs-scrollbar-handle{width:10px;height:20px;position:absolute;top:0;right:3px;z-index:2;background:#ccc;border:1px solid #fff;border-radius:5px;cursor:pointer}.fs-scrollbar-horizontal .fs-scrollbar-content{overflow:auto;overflow-x:auto;overflow-y:hidden;padding:0 0 16px 0}.fs-scrollbar-horizontal .fs-scrollbar-bar{width:100%;height:16px;top:auto;bottom:0;border-width:1px 0 0 0}.fs-scrollbar-horizontal .fs-scrollbar-handle{width:20px;height:10px;top:auto;right:auto;bottom:3px}.fs-scrollbar-setup .fs-scrollbar-content,.fs-scrollbar-active .fs-scrollbar-content{padding:20px}.fs-scrollbar-setup .fs-scrollbar-bar,.fs-scrollbar-active .fs-scrollbar-bar{display:block}.fs-scrollbar{overflow:hidden;overflow-x:hidden;overflow-y:hidden;position:relative}.fs-scrollbar,.fs-scrollbar *{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.fs-scrollbar,.fs-scrollbar-content,.fs-scrollbar-bar,.fs-scrollbar-track,.fs-scrollbar-handle{box-sizing:border-box}.fs-scrollbar-content{position:relative;z-index:1;height:100%;overflow:auto;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.fs-scrollbar-content::-webkit-scrollbar,.fs-scrollbar-content::-webkit-scrollbar-button,.fs-scrollbar-content::-webkit-scrollbar-track,.fs-scrollbar-content::-webkit-scrollbar-track-piece,.fs-scrollbar-content::-webkit-scrollbar-thumb,.fs-scrollbar-content::-webkit-scrollbar-corner,.fs-scrollbar-content::-webkit-resizer{background:0 0;opacity:0}.fs-scrollbar-bar{width:16px;height:100%;position:absolute;right:0;top:0;z-index:2;background:#fff;border:1px solid #eee;border-width:0 0 0 1px;display:none}.fs-scrollbar-track{width:100%;height:100%;position:relative;background:#fff;overflow:hidden}.fs-scrollbar-handle{width:10px;height:20px;position:absolute;top:0;right:3px;z-index:2;background:#ccc;border:1px solid #fff;border-radius:5px;cursor:pointer}.fs-scrollbar-horizontal .fs-scrollbar-content{overflow:auto;overflow-x:auto;overflow-y:hidden;padding:0 0 16px 0}.fs-scrollbar-horizontal .fs-scrollbar-bar{width:100%;height:16px;top:auto;bottom:0;border-width:1px 0 0 0}.fs-scrollbar-horizontal .fs-scrollbar-handle{width:20px;height:10px;top:auto;right:auto;bottom:3px}.fs-scrollbar-setup .fs-scrollbar-content,.fs-scrollbar-active .fs-scrollbar-content{padding:20px}.fs-scrollbar-setup .fs-scrollbar-bar,.fs-scrollbar-active .fs-scrollbar-bar{display:block}.scroller{overflow:hidden;overflow-x:hidden;overflow-y:hidden;position:relative}.scroller,.scroller *{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.scroller,.scroller-content,.scroller-bar,.scroller-track,.scroller-handle{box-sizing:border-box}.scroller-content{position:relative;z-index:1;height:100%;overflow:auto;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}.scroller-content::-webkit-scrollbar,.scroller-content::-webkit-scrollbar-button,.scroller-content::-webkit-scrollbar-track,.scroller-content::-webkit-scrollbar-track-piece,.scroller-content::-webkit-scrollbar-thumb,.scroller-content::-webkit-scrollbar-corner,.scroller-content::-webkit-resizer{background:0 0;opacity:0}.scroller-bar{width:16px;height:100%;position:absolute;right:0;top:0;z-index:2;background:#fff;border:1px solid #eee;border-width:0 0 0 1px;display:none}.scroller-track{width:100%;height:100%;position:relative;background:#fff;overflow:hidden}.scroller-handle{width:10px;height:20px;position:absolute;top:0;right:3px;z-index:2;background:#ccc;border:1px solid #fff;border-radius:5px;cursor:pointer}.scroller-horizontal .scroller-content{overflow:auto;overflow-x:auto;overflow-y:hidden;padding:0 0 16px 0}.scroller-horizontal .scroller-bar{width:100%;height:16px;top:auto;bottom:0;border-width:1px 0 0 0}.scroller-horizontal .scroller-handle{width:20px;height:10px;top:auto;right:auto;bottom:3px}.scroller-setup .scroller-content,.scroller-active .scroller-content{padding:20px}.scroller-setup .scroller-bar,.scroller-active .scroller-bar{display:block} -------------------------------------------------------------------------------- /jquery.fs.scroller.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Scroller v3.1.2 - 2015-04-04 3 | * A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library. 4 | * http://classic.formstone.it/scroller/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | !function(a,b){"use strict";function c(b){b=a.extend({},q,b||{}),null===n&&(n=a("body"));for(var c=a(this),e=0,f=c.length;f>e;e++)d(c.eq(e),b);return c}function d(c,d){if(!c.hasClass(o.base)){d=a.extend({},d,c.data(m+"-options"));var h="";h+='
',h+='
',h+='
',h+="
",d.paddingRight=parseInt(c.css("padding-right"),10),d.paddingBottom=parseInt(c.css("padding-bottom"),10),c.addClass([o.base,d.customClass].join(" ")).wrapInner('
').prepend(h),d.horizontal&&c.addClass(o.isHorizontal);var i=a.extend({$scroller:c,$content:c.find(l(o.content)),$bar:c.find(l(o.bar)),$track:c.find(l(o.track)),$handle:c.find(l(o.handle))},d);i.trackMargin=parseInt(i.trackMargin,10),i.$content.on("scroll."+m,i,e),i.$scroller.on(p.start,l(o.track),i,f).on(p.start,l(o.handle),i,g).data(m,i),r.reset.apply(c),a(b).one("load",function(){r.reset.apply(c)})}}function e(a){a.preventDefault(),a.stopPropagation();var b=a.data,c={};if(b.horizontal){var d=b.$content.scrollLeft();0>d&&(d=0);var e=d/b.scrollRatio;e>b.handleBounds.right&&(e=b.handleBounds.right),c={left:e}}else{var f=b.$content.scrollTop();0>f&&(f=0);var g=f/b.scrollRatio;g>b.handleBounds.bottom&&(g=b.handleBounds.bottom),c={top:g}}b.$handle.css(c)}function f(a){a.preventDefault(),a.stopPropagation();var b=a.data,c=a.originalEvent,d=b.$track.offset(),e="undefined"!=typeof c.targetTouches?c.targetTouches[0]:null,f=e?e.pageX:a.clientX,g=e?e.pageY:a.clientY;b.horizontal?(b.mouseStart=f,b.handleLeft=f-d.left-b.handleWidth/2,k(b,b.handleLeft)):(b.mouseStart=g,b.handleTop=g-d.top-b.handleHeight/2,k(b,b.handleTop)),h(b)}function g(a){a.preventDefault(),a.stopPropagation();var b=a.data,c=a.originalEvent,d="undefined"!=typeof c.targetTouches?c.targetTouches[0]:null,e=d?d.pageX:a.clientX,f=d?d.pageY:a.clientY;b.horizontal?(b.mouseStart=e,b.handleLeft=parseInt(b.$handle.css("left"),10)):(b.mouseStart=f,b.handleTop=parseInt(b.$handle.css("top"),10)),h(b)}function h(a){a.$content.off(l(m)),n.on(p.move,a,i).on(p.end,a,j)}function i(a){a.preventDefault(),a.stopPropagation();var b=a.data,c=a.originalEvent,d=0,e=0,f="undefined"!=typeof c.targetTouches?c.targetTouches[0]:null,g=f?f.pageX:a.clientX,h=f?f.pageY:a.clientY;b.horizontal?(e=b.mouseStart-g,d=b.handleLeft-e):(e=b.mouseStart-h,d=b.handleTop-e),k(b,d)}function j(a){a.preventDefault(),a.stopPropagation();var b=a.data;b.$content.on("scroll.scroller",b,e),n.off(".scroller")}function k(a,b){var c={};if(a.horizontal){ba.handleBounds.right&&(b=a.handleBounds.right);var d=Math.round(b*a.scrollRatio);c={left:b},a.$content.scrollLeft(d)}else{ba.handleBounds.bottom&&(b=a.handleBounds.bottom);var e=Math.round(b*a.scrollRatio);c={top:b},a.$content.scrollTop(e)}a.$handle.css(c)}function l(a){return"."+a}var m="scroller",n=null,o={base:"scroller",content:"scroller-content",bar:"scroller-bar",track:"scroller-track",handle:"scroller-handle",isHorizontal:"scroller-horizontal",isSetup:"scroller-setup",isActive:"scroller-active"},p={start:"touchstart."+m+" mousedown."+m,move:"touchmove."+m+" mousemove."+m,end:"touchend."+m+" mouseup."+m},q={customClass:"",duration:0,handleSize:0,horizontal:!1,trackMargin:0},r={defaults:function(b){return q=a.extend(q,b||{}),"object"==typeof this?a(this):!0},destroy:function(){return a(this).each(function(b,c){var d=a(c).data(m);d&&(d.$scroller.removeClass([d.customClass,o.base,o.isActive].join(" ")),d.$bar.remove(),d.$content.contents().unwrap(),d.$content.off(l(m)),d.$scroller.off(l(m)).removeData(m))})},scroll:function(b,c){return a(this).each(function(){var d=a(this).data(m),e=c||q.duration;if("number"!=typeof b){var f=a(b);if(f.length>0){var g=f.position();b=d.horizontal?g.left+d.$content.scrollLeft():g.top+d.$content.scrollTop()}else b=d.$content.scrollTop()}var h=d.horizontal?{scrollLeft:b}:{scrollTop:b};d.$content.stop().animate(h,e)})},reset:function(){return a(this).each(function(){var b=a(this).data(m);if(b){b.$scroller.addClass(o.isSetup);var c={},d={},e={},f=0,g=!0;if(b.horizontal){b.barHeight=b.$content[0].offsetHeight-b.$content[0].clientHeight,b.frameWidth=b.$content.outerWidth(),b.trackWidth=b.frameWidth-2*b.trackMargin,b.scrollWidth=b.$content[0].scrollWidth,b.ratio=b.trackWidth/b.scrollWidth,b.trackRatio=b.trackWidth/b.scrollWidth,b.handleWidth=b.handleSize>0?b.handleSize:b.trackWidth*b.trackRatio,b.scrollRatio=(b.scrollWidth-b.frameWidth)/(b.trackWidth-b.handleWidth),b.handleBounds={left:0,right:b.trackWidth-b.handleWidth},b.$content.css({paddingBottom:b.barHeight+b.paddingBottom});var h=b.$content.scrollLeft();f=h*b.ratio,g=b.scrollWidth<=b.frameWidth,c={width:b.frameWidth},d={width:b.trackWidth,marginLeft:b.trackMargin,marginRight:b.trackMargin},e={width:b.handleWidth}}else{b.barWidth=b.$content[0].offsetWidth-b.$content[0].clientWidth,b.frameHeight=b.$content.outerHeight(),b.trackHeight=b.frameHeight-2*b.trackMargin,b.scrollHeight=b.$content[0].scrollHeight,b.ratio=b.trackHeight/b.scrollHeight,b.trackRatio=b.trackHeight/b.scrollHeight,b.handleHeight=b.handleSize>0?b.handleSize:b.trackHeight*b.trackRatio,b.scrollRatio=(b.scrollHeight-b.frameHeight)/(b.trackHeight-b.handleHeight),b.handleBounds={top:0,bottom:b.trackHeight-b.handleHeight};var i=b.$content.scrollTop();f=i*b.ratio,g=b.scrollHeight<=b.frameHeight,c={height:b.frameHeight},d={height:b.trackHeight,marginBottom:b.trackMargin,marginTop:b.trackMargin},e={height:b.handleHeight}}g?b.$scroller.removeClass(o.isActive):b.$scroller.addClass(o.isActive),b.$bar.css(c),b.$track.css(d),b.$handle.css(e),k(b,f),b.$scroller.removeClass(o.isSetup)}})}};a.fn[m]=function(a){return r[a]?r[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?this:c.apply(this,arguments)},a[m]=function(a){"defaults"===a&&r.defaults.apply(this,Array.prototype.slice.call(arguments,1))}}(jQuery); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scroller", 3 | "id": "scroller", 4 | "codename": "jquery.fs.scroller", 5 | "version": "3.1.2", 6 | "description": "A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library.", 7 | "keywords": [ 8 | "scroll", 9 | "scrollbar", 10 | "ui", 11 | "javascript", 12 | "jquery", 13 | "formstone" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/FormstoneClassic/Scroller" 18 | }, 19 | "homepage": "http://classic.formstone.it/scroller/", 20 | "demo": "http://classic.formstone.it/components/Scroller/demo/index.html", 21 | "license": "MIT", 22 | "author": { 23 | "name": "Ben Plum", 24 | "email": "mr@benplum.com", 25 | "url": "http://www.benplum.com" 26 | }, 27 | "devDependencies": { 28 | "grunt": "~0.4.2", 29 | "grunt-autoprefixer": "^1.0.1", 30 | "grunt-banner": "^0.2.3", 31 | "grunt-contrib-copy": "^0.6.0", 32 | "grunt-contrib-jshint": "~0.7.2", 33 | "grunt-contrib-less": "^0.11.4", 34 | "grunt-contrib-uglify": "~0.2.7", 35 | "grunt-contrib-watch": "^0.6.1", 36 | "grunt-jquerymanifest": "~0.1.3", 37 | "grunt-npm2bower-sync": "~0.3.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scroller.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scroller", 3 | "version": "3.1.2", 4 | "title": "Scroller", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "http://opensource.org/licenses/MIT" 14 | } 15 | ], 16 | "dependencies": { 17 | "jquery": ">=1.7" 18 | }, 19 | "description": "A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library.", 20 | "keywords": [ 21 | "scroll", 22 | "scrollbar", 23 | "ui", 24 | "javascript", 25 | "jquery", 26 | "formstone" 27 | ], 28 | "docs": "http://classic.formstone.it/scroller/", 29 | "demo": "http://classic.formstone.it/scroller/", 30 | "download": "https://github.com/FormstoneClassic/Scroller", 31 | "bugs": "https://github.com/FormstoneClassic/Scroller/issues", 32 | "homepage": "http://classic.formstone.it/scroller/" 33 | } -------------------------------------------------------------------------------- /src/jquery.fs.scroller-config.less: -------------------------------------------------------------------------------- 1 | 2 | // Bar 3 | 4 | @scroller-bar-size: 16px; // 16px 5 | @scroller-bar-background: #fff; // #fff 6 | @scroller-bar-border-color: #eee; // #eee 7 | @scroller-bar-border-size: 1px; // 1px 8 | 9 | // Track 10 | 11 | @scroller-track-background: #fff; // #fff 12 | 13 | // Handle 14 | 15 | @scroller-handle-size: 10px; // 10px 16 | @scroller-handle-background: #ccc; // #ccc 17 | @scroller-handle-border-color: #fff; // #fff 18 | @scroller-handle-border-radius: 5px; // 5px 19 | @scroller-handle-border-size: 1px; // 1px 20 | 21 | // Content 22 | 23 | @scroller-content-padding: 20px; // 20px 24 | 25 | 26 | .fs-scrollbar { 27 | overflow: hidden; 28 | overflow-x: hidden; 29 | overflow-y: hidden; 30 | position: relative; 31 | 32 | &, 33 | & * { 34 | user-select: none !important; 35 | } 36 | 37 | &, 38 | &-content, 39 | &-bar, 40 | &-track, 41 | &-handle { 42 | box-sizing: border-box; 43 | } 44 | 45 | // .scroller-content 46 | 47 | &-content { 48 | position: relative; 49 | z-index: 1; 50 | 51 | height: 100%; 52 | overflow: auto; 53 | overflow-x: hidden; 54 | overflow-y: auto; 55 | 56 | -webkit-overflow-scrolling: touch; 57 | 58 | // Webkit fix 59 | 60 | &::-webkit-scrollbar, 61 | &::-webkit-scrollbar-button, 62 | &::-webkit-scrollbar-track, 63 | &::-webkit-scrollbar-track-piece, 64 | &::-webkit-scrollbar-thumb, 65 | &::-webkit-scrollbar-corner, 66 | &::-webkit-resizer { 67 | background: transparent; 68 | opacity: 0; 69 | } 70 | } 71 | 72 | // .scroller-bar 73 | 74 | &-bar { 75 | width: @scroller-bar-size; 76 | height: 100%; 77 | 78 | position: absolute; 79 | right: 0; 80 | top: 0; 81 | z-index: 2; 82 | 83 | background: @scroller-bar-background; 84 | border: @scroller-bar-border-size solid @scroller-bar-border-color; 85 | border-width: 0 0 0 @scroller-bar-border-size; 86 | display: none; 87 | } 88 | 89 | // .scroller-track 90 | 91 | &-track { 92 | width: 100%; 93 | height: 100%; 94 | 95 | position: relative; 96 | 97 | background: @scroller-track-background; 98 | overflow: hidden; 99 | } 100 | 101 | // .scroller-handle 102 | 103 | @scroller-handle-position: ((@scroller-bar-size - @scroller-handle-size) / 2); 104 | 105 | &-handle { 106 | width: @scroller-handle-size; 107 | height: (@scroller-handle-size * 2); 108 | 109 | position: absolute; 110 | top: 0; 111 | right: @scroller-handle-position; 112 | z-index: 2; 113 | 114 | background: @scroller-handle-background; 115 | border: @scroller-handle-border-size solid @scroller-handle-border-color; 116 | border-radius: @scroller-handle-border-radius; 117 | cursor: pointer; 118 | } 119 | 120 | // Horizontal 121 | 122 | &-horizontal &-content { 123 | overflow: auto; 124 | overflow-x: auto; 125 | overflow-y: hidden; 126 | padding: 0 0 @scroller-bar-size 0; 127 | } 128 | 129 | &-horizontal &-bar { 130 | width: 100%; 131 | height: @scroller-bar-size; 132 | 133 | top: auto; 134 | bottom: 0; 135 | 136 | border-width: @scroller-bar-border-size 0 0 0; 137 | } 138 | 139 | &-horizontal &-handle { 140 | width: (@scroller-handle-size * 2); 141 | height: @scroller-handle-size; 142 | 143 | top: auto; 144 | right: auto; 145 | bottom: @scroller-handle-position; 146 | } 147 | 148 | // Active / Setup 149 | 150 | &-setup &-content, 151 | &-active &-content { 152 | padding: @scroller-content-padding; 153 | } 154 | 155 | &-setup &-bar, 156 | &-active &-bar { 157 | display: block; 158 | } 159 | } -------------------------------------------------------------------------------- /src/jquery.fs.scroller-styles.less: -------------------------------------------------------------------------------- 1 | 2 | .scroller { 3 | overflow: hidden; 4 | overflow-x: hidden; 5 | overflow-y: hidden; 6 | position: relative; 7 | 8 | &, 9 | & * { 10 | user-select: none !important; 11 | } 12 | 13 | &, 14 | &-content, 15 | &-bar, 16 | &-track, 17 | &-handle { 18 | box-sizing: border-box; 19 | } 20 | 21 | // .scroller-content 22 | 23 | &-content { 24 | position: relative; 25 | z-index: 1; 26 | 27 | height: 100%; 28 | overflow: auto; 29 | overflow-x: hidden; 30 | overflow-y: auto; 31 | 32 | -webkit-overflow-scrolling: touch; 33 | 34 | // Webkit fix 35 | 36 | &::-webkit-scrollbar, 37 | &::-webkit-scrollbar-button, 38 | &::-webkit-scrollbar-track, 39 | &::-webkit-scrollbar-track-piece, 40 | &::-webkit-scrollbar-thumb, 41 | &::-webkit-scrollbar-corner, 42 | &::-webkit-resizer { 43 | background: transparent; 44 | opacity: 0; 45 | } 46 | } 47 | 48 | // .scroller-bar 49 | 50 | &-bar { 51 | width: @scroller-bar-size; 52 | height: 100%; 53 | 54 | position: absolute; 55 | right: 0; 56 | top: 0; 57 | z-index: 2; 58 | 59 | background: @scroller-bar-background; 60 | border: @scroller-bar-border-size solid @scroller-bar-border-color; 61 | border-width: 0 0 0 @scroller-bar-border-size; 62 | display: none; 63 | } 64 | 65 | // .scroller-track 66 | 67 | &-track { 68 | width: 100%; 69 | height: 100%; 70 | 71 | position: relative; 72 | 73 | background: @scroller-track-background; 74 | overflow: hidden; 75 | } 76 | 77 | // .scroller-handle 78 | 79 | @scroller-handle-position: ((@scroller-bar-size - @scroller-handle-size) / 2); 80 | 81 | &-handle { 82 | width: @scroller-handle-size; 83 | height: (@scroller-handle-size * 2); 84 | 85 | position: absolute; 86 | top: 0; 87 | right: @scroller-handle-position; 88 | z-index: 2; 89 | 90 | background: @scroller-handle-background; 91 | border: @scroller-handle-border-size solid @scroller-handle-border-color; 92 | border-radius: @scroller-handle-border-radius; 93 | cursor: pointer; 94 | } 95 | 96 | // Horizontal 97 | 98 | &-horizontal &-content { 99 | overflow: auto; 100 | overflow-x: auto; 101 | overflow-y: hidden; 102 | padding: 0 0 @scroller-bar-size 0; 103 | } 104 | 105 | &-horizontal &-bar { 106 | width: 100%; 107 | height: @scroller-bar-size; 108 | 109 | top: auto; 110 | bottom: 0; 111 | 112 | border-width: @scroller-bar-border-size 0 0 0; 113 | } 114 | 115 | &-horizontal &-handle { 116 | width: (@scroller-handle-size * 2); 117 | height: @scroller-handle-size; 118 | 119 | top: auto; 120 | right: auto; 121 | bottom: @scroller-handle-position; 122 | } 123 | 124 | // Active / Setup 125 | 126 | &-setup &-content, 127 | &-active &-content { 128 | padding: @scroller-content-padding; 129 | } 130 | 131 | &-setup &-bar, 132 | &-active &-bar { 133 | display: block; 134 | } 135 | } -------------------------------------------------------------------------------- /src/jquery.fs.scroller.js: -------------------------------------------------------------------------------- 1 | ;(function ($, window) { 2 | "use strict"; 3 | 4 | var namespace = "scroller", 5 | $body = null, 6 | classes = { 7 | base: "scroller", 8 | content: "scroller-content", 9 | bar: "scroller-bar", 10 | track: "scroller-track", 11 | handle: "scroller-handle", 12 | isHorizontal: "scroller-horizontal", 13 | isSetup: "scroller-setup", 14 | isActive: "scroller-active" 15 | }, 16 | events = { 17 | start: "touchstart." + namespace + " mousedown." + namespace, 18 | move: "touchmove." + namespace + " mousemove." + namespace, 19 | end: "touchend." + namespace + " mouseup." + namespace 20 | }; 21 | 22 | /** 23 | * @options 24 | * @param customClass [string] <''> "Class applied to instance" 25 | * @param duration [int] <0> "Scroll animation length" 26 | * @param handleSize [int] <0> "Handle size; 0 to auto size" 27 | * @param horizontal [boolean] "Scroll horizontally" 28 | * @param trackMargin [int] <0> "Margin between track and handle edge” 29 | */ 30 | var options = { 31 | customClass: "", 32 | duration: 0, 33 | handleSize: 0, 34 | horizontal: false, 35 | trackMargin: 0 36 | }; 37 | 38 | var pub = { 39 | 40 | /** 41 | * @method 42 | * @name defaults 43 | * @description Sets default plugin options 44 | * @param opts [object] <{}> "Options object" 45 | * @example $.scroller("defaults", opts); 46 | */ 47 | defaults: function(opts) { 48 | options = $.extend(options, opts || {}); 49 | return (typeof this === 'object') ? $(this) : true; 50 | }, 51 | 52 | /** 53 | * @method 54 | * @name destroy 55 | * @description Removes instance of plugin 56 | * @example $(".target").scroller("destroy"); 57 | */ 58 | destroy: function() { 59 | return $(this).each(function(i, el) { 60 | var data = $(el).data(namespace); 61 | 62 | if (data) { 63 | data.$scroller.removeClass( [data.customClass, classes.base, classes.isActive].join(" ") ); 64 | 65 | data.$bar.remove(); 66 | data.$content.contents().unwrap(); 67 | 68 | data.$content.off( classify(namespace) ); 69 | data.$scroller.off( classify(namespace) ) 70 | .removeData(namespace); 71 | } 72 | }); 73 | }, 74 | 75 | /** 76 | * @method 77 | * @name scroll 78 | * @description Scrolls instance of plugin to element or position 79 | * @param pos [string || int] "Target element selector or static position" 80 | * @param duration [int] "Optional scroll duration" 81 | * @example $.scroller("scroll", pos, duration); 82 | */ 83 | scroll: function(pos, dur) { 84 | return $(this).each(function(i) { 85 | var data = $(this).data(namespace), 86 | duration = dur || options.duration; 87 | 88 | if (typeof pos !== "number") { 89 | var $el = $(pos); 90 | if ($el.length > 0) { 91 | var offset = $el.position(); 92 | if (data.horizontal) { 93 | pos = offset.left + data.$content.scrollLeft(); 94 | } else { 95 | pos = offset.top + data.$content.scrollTop(); 96 | } 97 | } else { 98 | pos = data.$content.scrollTop(); 99 | } 100 | } 101 | 102 | var styles = data.horizontal ? { scrollLeft: pos } : { scrollTop: pos }; 103 | 104 | data.$content.stop().animate(styles, duration); 105 | }); 106 | }, 107 | 108 | /** 109 | * @method 110 | * @name reset 111 | * @description Resets layout on instance of plugin 112 | * @example $.scroller("reset"); 113 | */ 114 | reset: function() { 115 | return $(this).each(function(i) { 116 | var data = $(this).data(namespace); 117 | 118 | if (data) { 119 | data.$scroller.addClass(classes.isSetup); 120 | 121 | var barStyles = {}, 122 | trackStyles = {}, 123 | handleStyles = {}, 124 | handlePosition = 0, 125 | isActive = true; 126 | 127 | if (data.horizontal) { 128 | // Horizontal 129 | data.barHeight = data.$content[0].offsetHeight - data.$content[0].clientHeight; 130 | data.frameWidth = data.$content.outerWidth(); 131 | data.trackWidth = data.frameWidth - (data.trackMargin * 2); 132 | data.scrollWidth = data.$content[0].scrollWidth; 133 | data.ratio = data.trackWidth / data.scrollWidth; 134 | data.trackRatio = data.trackWidth / data.scrollWidth; 135 | data.handleWidth = (data.handleSize > 0) ? data.handleSize : data.trackWidth * data.trackRatio; 136 | data.scrollRatio = (data.scrollWidth - data.frameWidth) / (data.trackWidth - data.handleWidth); 137 | data.handleBounds = { 138 | left: 0, 139 | right: data.trackWidth - data.handleWidth 140 | }; 141 | 142 | data.$content.css({ 143 | paddingBottom: data.barHeight + data.paddingBottom 144 | }); 145 | 146 | var scrollLeft = data.$content.scrollLeft(); 147 | 148 | handlePosition = scrollLeft * data.ratio; 149 | isActive = (data.scrollWidth <= data.frameWidth); 150 | 151 | barStyles = { 152 | width: data.frameWidth 153 | }; 154 | 155 | trackStyles = { 156 | width: data.trackWidth, 157 | marginLeft: data.trackMargin, 158 | marginRight: data.trackMargin 159 | }; 160 | 161 | handleStyles = { 162 | width: data.handleWidth 163 | }; 164 | } else { 165 | // Vertical 166 | data.barWidth = data.$content[0].offsetWidth - data.$content[0].clientWidth; 167 | data.frameHeight = data.$content.outerHeight(); 168 | data.trackHeight = data.frameHeight - (data.trackMargin * 2); 169 | data.scrollHeight = data.$content[0].scrollHeight; 170 | data.ratio = data.trackHeight / data.scrollHeight; 171 | data.trackRatio = data.trackHeight / data.scrollHeight; 172 | data.handleHeight = (data.handleSize > 0) ? data.handleSize : data.trackHeight * data.trackRatio; 173 | data.scrollRatio = (data.scrollHeight - data.frameHeight) / (data.trackHeight - data.handleHeight); 174 | data.handleBounds = { 175 | top: 0, 176 | bottom: data.trackHeight - data.handleHeight 177 | }; 178 | 179 | var scrollTop = data.$content.scrollTop(); 180 | 181 | handlePosition = scrollTop * data.ratio; 182 | isActive = (data.scrollHeight <= data.frameHeight); 183 | 184 | barStyles = { 185 | height: data.frameHeight 186 | }; 187 | 188 | trackStyles = { 189 | height: data.trackHeight, 190 | marginBottom: data.trackMargin, 191 | marginTop: data.trackMargin 192 | }; 193 | 194 | handleStyles = { 195 | height: data.handleHeight 196 | }; 197 | } 198 | 199 | // Updates 200 | 201 | if (isActive) { 202 | data.$scroller.removeClass(classes.isActive); 203 | } else { 204 | data.$scroller.addClass(classes.isActive); 205 | } 206 | 207 | data.$bar.css(barStyles); 208 | data.$track.css(trackStyles); 209 | data.$handle.css(handleStyles); 210 | 211 | position(data, handlePosition); 212 | 213 | data.$scroller.removeClass(classes.isSetup); 214 | } 215 | }); 216 | } 217 | }; 218 | 219 | /** 220 | * @method private 221 | * @name init 222 | * @description Initializes plugin 223 | * @param opts [object] "Initialization options" 224 | */ 225 | function init(opts) { 226 | // Local options 227 | opts = $.extend({}, options, opts || {}); 228 | 229 | // Check for Body 230 | if ($body === null) { 231 | $body = $("body"); 232 | } 233 | 234 | // Apply to each element 235 | var $items = $(this); 236 | for (var i = 0, count = $items.length; i < count; i++) { 237 | build($items.eq(i), opts); 238 | } 239 | return $items; 240 | } 241 | 242 | /** 243 | * @method private 244 | * @name build 245 | * @description Builds each instance 246 | * @param $scroller [jQuery object] "Target jQuery object" 247 | * @param opts [object] <{}> "Options object" 248 | */ 249 | function build($scroller, opts) { 250 | if (!$scroller.hasClass(classes.base)) { 251 | // EXTEND OPTIONS 252 | opts = $.extend({}, opts, $scroller.data(namespace + "-options")); 253 | 254 | var html = ''; 255 | 256 | html += '
'; 257 | html += '
'; 258 | html += '
'; 259 | html += '
'; 260 | 261 | opts.paddingRight = parseInt($scroller.css("padding-right"), 10); 262 | opts.paddingBottom = parseInt($scroller.css("padding-bottom"), 10); 263 | 264 | $scroller.addClass( [classes.base, opts.customClass].join(" ") ) 265 | .wrapInner('
') 266 | .prepend(html); 267 | 268 | if (opts.horizontal) { 269 | $scroller.addClass(classes.isHorizontal); 270 | } 271 | 272 | var data = $.extend({ 273 | $scroller: $scroller, 274 | $content: $scroller.find( classify(classes.content) ), 275 | $bar: $scroller.find( classify(classes.bar) ), 276 | $track: $scroller.find( classify(classes.track) ), 277 | $handle: $scroller.find( classify(classes.handle) ) 278 | }, opts); 279 | 280 | data.trackMargin = parseInt(data.trackMargin, 10); 281 | 282 | data.$content.on("scroll." + namespace, data, onScroll); 283 | data.$scroller.on(events.start, classify(classes.track), data, onTrackDown) 284 | .on(events.start, classify(classes.handle), data, onHandleDown) 285 | .data(namespace, data); 286 | 287 | pub.reset.apply($scroller); 288 | 289 | $(window).one("load", function() { 290 | pub.reset.apply($scroller); 291 | }); 292 | } 293 | } 294 | 295 | /** 296 | * @method private 297 | * @name onScroll 298 | * @description Handles scroll event 299 | * @param e [object] "Event data" 300 | */ 301 | function onScroll(e) { 302 | e.preventDefault(); 303 | e.stopPropagation(); 304 | 305 | var data = e.data, 306 | handleStyles = {}; 307 | 308 | if (data.horizontal) { 309 | // Horizontal 310 | var scrollLeft = data.$content.scrollLeft(); 311 | 312 | if (scrollLeft < 0) { 313 | scrollLeft = 0; 314 | } 315 | 316 | var handleLeft = scrollLeft / data.scrollRatio; 317 | 318 | if (handleLeft > data.handleBounds.right) { 319 | handleLeft = data.handleBounds.right; 320 | } 321 | 322 | handleStyles = { 323 | left: handleLeft 324 | }; 325 | } else { 326 | // Vertical 327 | var scrollTop = data.$content.scrollTop(); 328 | 329 | if (scrollTop < 0) { 330 | scrollTop = 0; 331 | } 332 | 333 | var handleTop = scrollTop / data.scrollRatio; 334 | 335 | if (handleTop > data.handleBounds.bottom) { 336 | handleTop = data.handleBounds.bottom; 337 | } 338 | 339 | handleStyles = { 340 | top: handleTop 341 | }; 342 | } 343 | 344 | data.$handle.css(handleStyles); 345 | } 346 | 347 | /** 348 | * @method private 349 | * @name onTrackDown 350 | * @description Handles mousedown event on track 351 | * @param e [object] "Event data" 352 | */ 353 | function onTrackDown(e) { 354 | e.preventDefault(); 355 | e.stopPropagation(); 356 | 357 | var data = e.data, 358 | oe = e.originalEvent, 359 | offset = data.$track.offset(), 360 | touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null, 361 | pageX = (touch) ? touch.pageX : e.clientX, 362 | pageY = (touch) ? touch.pageY : e.clientY; 363 | 364 | if (data.horizontal) { 365 | // Horizontal 366 | data.mouseStart = pageX; 367 | data.handleLeft = pageX - offset.left - (data.handleWidth / 2); 368 | 369 | position(data, data.handleLeft); 370 | } else { 371 | // Vertical 372 | data.mouseStart = pageY; 373 | data.handleTop = pageY - offset.top - (data.handleHeight / 2); 374 | 375 | position(data, data.handleTop); 376 | } 377 | 378 | onStart(data); 379 | } 380 | 381 | /** 382 | * @method private 383 | * @name onHandleDown 384 | * @description Handles mousedown event on handle 385 | * @param e [object] "Event data" 386 | */ 387 | function onHandleDown(e) { 388 | e.preventDefault(); 389 | e.stopPropagation(); 390 | 391 | var data = e.data, 392 | oe = e.originalEvent, 393 | touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null, 394 | pageX = (touch) ? touch.pageX : e.clientX, 395 | pageY = (touch) ? touch.pageY : e.clientY; 396 | 397 | if (data.horizontal) { 398 | // Horizontal 399 | data.mouseStart = pageX; 400 | data.handleLeft = parseInt(data.$handle.css("left"), 10); 401 | } else { 402 | // Vertical 403 | data.mouseStart = pageY; 404 | data.handleTop = parseInt(data.$handle.css("top"), 10); 405 | } 406 | 407 | onStart(data); 408 | } 409 | 410 | /** 411 | * @method private 412 | * @name onStart 413 | * @description Handles touch.mouse start 414 | * @param data [object] "Instance data" 415 | */ 416 | function onStart(data) { 417 | data.$content.off( classify(namespace) ); 418 | 419 | $body.on(events.move, data, onMouseMove) 420 | .on(events.end, data, onMouseUp); 421 | } 422 | 423 | /** 424 | * @method private 425 | * @name onMouseMove 426 | * @description Handles mousemove event 427 | * @param e [object] "Event data" 428 | */ 429 | function onMouseMove(e) { 430 | e.preventDefault(); 431 | e.stopPropagation(); 432 | 433 | var data = e.data, 434 | oe = e.originalEvent, 435 | pos = 0, 436 | delta = 0, 437 | touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null, 438 | pageX = (touch) ? touch.pageX : e.clientX, 439 | pageY = (touch) ? touch.pageY : e.clientY; 440 | 441 | if (data.horizontal) { 442 | // Horizontal 443 | delta = data.mouseStart - pageX; 444 | pos = data.handleLeft - delta; 445 | } else { 446 | // Vertical 447 | delta = data.mouseStart - pageY; 448 | pos = data.handleTop - delta; 449 | } 450 | 451 | position(data, pos); 452 | } 453 | 454 | /** 455 | * @method private 456 | * @name onMouseUp 457 | * @description Handles mouseup event 458 | * @param e [object] "Event data" 459 | */ 460 | function onMouseUp(e) { 461 | e.preventDefault(); 462 | e.stopPropagation(); 463 | 464 | var data = e.data; 465 | 466 | data.$content.on("scroll.scroller", data, onScroll); 467 | $body.off(".scroller"); 468 | } 469 | 470 | /** 471 | * @method private 472 | * @name onTouchEnd 473 | * @description Handles mouseup event 474 | * @param e [object] "Event data" 475 | */ 476 | function onTouchEnd(e) { 477 | e.preventDefault(); 478 | e.stopPropagation(); 479 | 480 | var data = e.data; 481 | 482 | data.$content.on("scroll.scroller", data, onScroll); 483 | $body.off(".scroller"); 484 | } 485 | 486 | /** 487 | * @method private 488 | * @name position 489 | * @description Position handle based on scroll 490 | * @param data [object] "Instance data" 491 | * @param pos [int] "Scroll position" 492 | */ 493 | function position(data, pos) { 494 | var handleStyles = {}; 495 | 496 | if (data.horizontal) { 497 | // Horizontal 498 | if (pos < data.handleBounds.left) { 499 | pos = data.handleBounds.left; 500 | } 501 | 502 | if (pos > data.handleBounds.right) { 503 | pos = data.handleBounds.right; 504 | } 505 | 506 | var scrollLeft = Math.round(pos * data.scrollRatio); 507 | 508 | handleStyles = { 509 | left: pos 510 | }; 511 | 512 | data.$content.scrollLeft( scrollLeft ); 513 | } else { 514 | // Vertical 515 | if (pos < data.handleBounds.top) { 516 | pos = data.handleBounds.top; 517 | } 518 | 519 | if (pos > data.handleBounds.bottom) { 520 | pos = data.handleBounds.bottom; 521 | } 522 | 523 | var scrollTop = Math.round(pos * data.scrollRatio); 524 | 525 | handleStyles = { 526 | top: pos 527 | }; 528 | 529 | data.$content.scrollTop( scrollTop ); 530 | } 531 | 532 | data.$handle.css(handleStyles); 533 | } 534 | 535 | /** 536 | * @method private 537 | * @name classify 538 | * @description Create class selector from text 539 | * @param text [string] "Text to convert" 540 | * @return [string] "New class name" 541 | */ 542 | function classify(text) { 543 | return "." + text; 544 | } 545 | 546 | $.fn[namespace] = function(method) { 547 | if (pub[method]) { 548 | return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); 549 | } else if (typeof method === 'object' || !method) { 550 | return init.apply(this, arguments); 551 | } 552 | return this; 553 | }; 554 | 555 | $[namespace] = function(method) { 556 | if (method === "defaults") { 557 | pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1)); 558 | } 559 | }; 560 | })(jQuery); 561 | -------------------------------------------------------------------------------- /src/jquery.fs.scroller.less: -------------------------------------------------------------------------------- 1 | 2 | @import "jquery.fs.scroller-config.less"; 3 | @import "jquery.fs.scroller-styles.less"; --------------------------------------------------------------------------------