├── .npmignore ├── .gitignore ├── .jshintrc ├── test ├── make.js ├── main.js └── index.html ├── bower.json ├── package.json ├── README.md └── hashy.js /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .jshintrc 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | npm-debug.log 3 | node_modules/ 4 | test/bundle.js 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "browserify": true, 4 | "globals": { 5 | "$": true, 6 | "console": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/make.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var browserify = require('browserify'); 3 | browserify('./main.js') 4 | .transform('babelify', {presets: ['es2015']}) 5 | .bundle() 6 | .pipe(fs.createWriteStream('./bundle.js')); 7 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hashy", 3 | "main": "hashy.js", 4 | "version": "1.0.7", 5 | "homepage": "https://github.com/mrchimp/hashy", 6 | "authors": [ 7 | "mrchimp " 8 | ], 9 | "description": "Make hash links nice.", 10 | "license": "MIT", 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests", 17 | "test.html", 18 | ".gitignore", 19 | "README.md" 20 | ], 21 | "dependencies": { 22 | "jquery": "*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hashy-links", 3 | "version": "1.0.7", 4 | "description": "Hash link fix", 5 | "main": "hashy.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mrchimp/hashy.git" 12 | }, 13 | "author": "mrchimp ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/mrchimp/hashy/issues" 17 | }, 18 | "homepage": "https://github.com/mrchimp/hashy#readme", 19 | "devDependencies": { 20 | "browserify": "~13.0.0", 21 | "babel-preset-es2015": "^6.6.0", 22 | "babelify": "^7.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // This file is just for testing. When you run `node make.js` it will create 4 | // bundle.js which will contain this file and hashy.js 5 | 6 | import Hashy from '../hashy.js'; 7 | 8 | // Initialise hashy. 9 | // This enables smooth scrolling on hash links and will scroll to a hash 10 | // on load, if appropriate. 11 | Hashy.init('.smooth-scroll', 'nav'); 12 | 13 | 14 | // Scroll to a given hash and update the address bar 15 | $('.scroll-to-test').on('click', function (e) { 16 | e.preventDefault(); 17 | Hashy.scrollToHash('#top', false, function () { 18 | console.log('Finished scrolling.'); 19 | }); 20 | }); 21 | 22 | 23 | // Scroll to 10px below the top of the page 24 | $('.offset-test').on('click', function (e) { 25 | e.preventDefault(); 26 | Hashy.scrollToHash('#top', false, function () { 27 | console.log('Finished scrolling.'); 28 | }, 10); 29 | }); 30 | 31 | 32 | // Try to scroll above the top of the page - actually just scroll to top 33 | $('.offset-test-too-far').on('click', function (e) { 34 | e.preventDefault(); 35 | Hashy.scrollToHash('#top', false, function () { 36 | console.log('Finished scrolling.'); 37 | }, -100); 38 | }); 39 | 40 | 41 | // Just set the hash in the address bar. 42 | // Doesn't scroll the page. 43 | $('.set-a-hash').on('click', function (e) { 44 | e.preventDefault(); 45 | Hashy.setHash('#some-random-hash'); 46 | }); 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hashy.js # 2 | 3 | Smooth scroll to internal anchors when clicking "hash" links (e.g. href="#foobar"). 4 | 5 | Offset scroll amount by the height of a given element including on page load - useful for sticky headers. 6 | 7 | Relies on jQuery because I haven't made it not. Pull requests are accepted. 8 | 9 | ## Installation ## 10 | 11 | bower install --save hashy 12 | 13 | # or 14 | 15 | npm install --save hashy-links 16 | 17 | 18 | ## Basic Usage ## 19 | 20 | **Notice that things work slightly different with Hashy v1.0.0+. Hashy is now an object, not a function.** 21 | 22 | index.html 23 | ---------- 24 | Click me 25 | 26 | 27 | main.js 28 | ------- 29 | import Hashy from 'hashy-links'; 30 | 31 | Hashy.init('.smooth-scrolling-links', '#fixed-header'); 32 | 33 | Marvelous. 34 | 35 | 36 | ## Parameters ## 37 | 38 | Hashy.init(link_selector, offset_selector, extra_offset); 39 | 40 | **link_selector** 41 | 42 | This is a jQuery selector for links that you want to smooth scroll when clicked. So if you have something like this: 43 | 44 | When something with this selector is clicked hashy will look on the current page for an element with the given ID. If this element is not found, the default link's action will be used. This should allow external (not same page) links to be selected without causing problems except... 45 | 46 | > **Potential Issue** - If you are on `foo.html` and have a link to `bar.html#baz` but `#baz` also exists on the current page then your link will effectively be broken. This is an issue that needs to be fixed. You can avoid this problem by being careful which links to pick with your selector. For example, **you probably don't want to do `hashy('a')`**. 47 | 48 | **offset_selector** 49 | 50 | This optional selector is for use with fixed menus that normally cover content when using hash links. For single menus an id selector is recommended, e.g. `hashy('#main-menu')`. If you have multiple menus you can pass in a selector that matches multiple items. The scroll will then be offset by the *combined height* of these elements. 51 | 52 | **extra_offset** 53 | 54 | An optional number of pixels to offset the scroll by, in addition the distance calculated by `offset_selector`. Can be positive or negative. You could even make it really, *really* negative and it will just stop at the top of the page. I've got you covered, bud. 55 | 56 | 57 | ## Advanced Usage ## 58 | 59 | There are also a couple of helper methods you can use if you like. 60 | 61 | 62 | ### scrollToHash ### 63 | 64 | Hashy.scrollToHash(hash, quick, callback, extra_offset) 65 | 66 | Smooth scroll to the given hash and update the address bar. 67 | 68 | 69 | #### Parameters #### 70 | 71 | **hash** - The selector to scroll to. e.g. `#somewhere` 72 | 73 | **quick** - (optional. Default: false) Don't transition - just go there! 74 | 75 | **callback** - (optional) Function to call after scrolling has finished. 76 | 77 | **extra_offset** - (optional) An additional pixel value to offset the scroll by 78 | 79 | 80 | ### setHash ### 81 | 82 | Hashy.setHash(#whatever); 83 | 84 | Update the address bar - *don't* scroll. 85 | 86 | 87 | ## Dynamically Sized Content ## 88 | 89 | If your content resizes when the page loads, you're going to want to call hashy after that is done, otherwise the offset calculations may be incorrect. Something like this might help: 90 | 91 | $(window).load(function() { 92 | Hashy.init('.smooth-scrolling-links', #fixed-header'); 93 | }); 94 | -------------------------------------------------------------------------------- /hashy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smooth scroll to hash links. 3 | * Scroll to hash links on load. 4 | * Offset by height of fixed menu. 5 | */ 6 | (function (root, Hashy) { 7 | if ( typeof define === 'function' && define.amd ) { 8 | define(Hashy); 9 | } else if ( typeof exports === 'object' ) { 10 | module.exports = Hashy; 11 | } else { 12 | root.Hashy = Hashy; 13 | } 14 | }(this, { 15 | offset_elem: null, 16 | scroll_time: 400, 17 | 18 | /** 19 | * Scroll the window to a given hash 20 | * @param {string} hash [The hash to scroll to, including # symbol] 21 | * @param {boolean} quick [If true, moves instantly - no smooth scroll] 22 | * @param {function} callback [Callback function] 23 | */ 24 | scrollToHash: function (hash, quick, callback, extra_offset) { 25 | var offset_height = 0, 26 | callback_ran = false; 27 | 28 | extra_offset = typeof extra_offset !== 'undefined' ? extra_offset : 0; 29 | 30 | if (this.offset_elem.length) { 31 | this.offset_elem.each(function () { 32 | offset_height += (jQuery(this).outerHeight() - extra_offset); 33 | }); 34 | } 35 | 36 | var $target = jQuery(hash); 37 | 38 | if ($target.length) { 39 | var scroll_dist = Math.max(0, $target.offset().top - offset_height); 40 | 41 | if (quick) { 42 | window.scroll(0, scroll_dist); 43 | this.setHash(hash); 44 | 45 | if (callback) { 46 | callback(); 47 | } 48 | } else { 49 | // Animating html and body causes the animate() callback 50 | // to be called twice. We'll use callback_ran to prevent 51 | // calling our own callback twice. 52 | jQuery('html, body').stop().animate({ 53 | 'scrollTop': scroll_dist 54 | }, this.scroll_time, 'swing', function () { 55 | this.setHash(hash); 56 | 57 | if (callback) { 58 | if (!callback_ran) { 59 | callback(); 60 | callback_ran = true; 61 | } 62 | } 63 | }.bind(this)); 64 | } 65 | } 66 | }, 67 | 68 | /** 69 | * Set the hash part of the URL without scrolling the window 70 | * @param {string} hash [New hash to set, including # symbol] 71 | */ 72 | setHash: function (hash) { 73 | if (history.pushState) { 74 | history.pushState(null, null, hash); 75 | } else { 76 | location.hash = hash; 77 | } 78 | }, 79 | 80 | /** 81 | * Initialise Hashy 82 | * @param {String} link_sel [Selector for hash link elements] 83 | * @param {String} offset_sel [Selector for offset element] 84 | * @param {Integer} extra_offset [Value for additional offset] 85 | */ 86 | init: function (link_sel, offset_sel, extra_offset) { 87 | this.offset_elem = jQuery(offset_sel); 88 | 89 | // Smooth scroll hash links when clicked 90 | jQuery(link_sel).on('click', function (e) { 91 | var hash = e.currentTarget.hash; 92 | if (jQuery(hash).length) { 93 | e.preventDefault(); 94 | this.scrollToHash(hash, false, false, extra_offset); 95 | } 96 | }.bind(this)); 97 | 98 | // Scroll to hash on page load. The browser does this by 99 | // default but this will compensate for sticky headers 100 | 101 | if (window.location.hash) { 102 | jQuery(window).on('load', function () { 103 | window.setTimeout(function() { 104 | this.scrollToHash(window.location.hash, true, false, extra_offset); 105 | }.bind(this), 0); 106 | }.bind(this)); 107 | } 108 | } 109 | })); 110 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hashy.js 6 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |

Link 1 (smooth)

32 |

Link 2 (smooth)

33 |

Link 3 (smooth)

34 |

Link 4 (smooth)

35 |

Link 5 (smooth)

36 |

Link 6 (smooth)

37 |

Link to somewhere else

38 |

Test 1

39 |

Lebowski ipsum hello, Pilar? My name is Walter Sobchak, we spoke on the phone, this is my associate Jeffrey Lebowski. Dolor sit amet, consectetur adipiscing elit praesent ac magna justo pellentesque ac lectus quis. That's fuckin' combat. The man in the black pyjamas, Dude. Worthy fuckin' adversary. Elit blandit fringilla a ut turpis. Jesus, man, can you change the station? Praesent felis ligula, malesuada suscipit malesuada non, ultrices non urna sed orci.

40 |

Link 1 (not smooth)

41 |

Link 2 (not smooth)

42 |

Link 3 (not smooth)

43 |

Link 4 (not smooth)

44 |

Link 5 (not smooth)

45 |

Link 6 (not smooth)

46 |

Test 2

47 |

You got the wrong guy. I'm the Dude, man. Ac maecenas vitae eros velit, eu suscipit erat integer. Finishing my coffee. Purus lacus, pretium vel venenatis eu, volutpat. You think veer kidding und making mit de funny stuff? Non erat donec a metus ac eros dictum aliquet nulla consectetur egestas placerat. I have no choice but to tell these bums that they should do whatever is necessary to recover their money from you, Jeffrey Lebowski. Maecenas pulvinar nisl et nisl.

48 |

Link 1 (smooth)

49 |

Link 2 (smooth)

50 |

Link 3 (smooth)

51 |

Link 4 (smooth)

52 |

Link 5 (smooth)

53 |

Link 6 (smooth)

54 |

Test 3

55 |

Life does not stop and start at your convenience, you miserable piece of shit. Rhoncus at volutpat felis blandit in libero turpis, laoreet et. I mean I had an M16, Jacko, not an Abrams fucking tank. Molestie sed, volutpat et erat nulla ut orci quis neque consectetur tincidunt. HERE'S WHAT HAPPENS, LARRY! Aliquam erat volutpat donec aliquam orci eget mi lobortis sed tincidunt diam mattis. …and even if he's a lazy man, and the Dude was certainly that—quite possibly the laziest in Los Angeles County. Fusce sem quam, ultricies sed convallis ac, hendrerit eu urna curabitur.

56 |

Link 1 (not smooth)

57 |

Link 2 (not smooth)

58 |

Link 3 (not smooth)

59 |

Link 4 (not smooth)

60 |

Link 5 (not smooth)

61 |

Link 6 (not smooth)

62 |

Test 4

63 |

VEE FUCK YOU UP, MAN! VEE TAKE YOUR MONEY! Ipsum, placerat id condimentum rutrum, rhoncus ac lorem aliquam placerat posuere. D'ya have a good sarsaparilla? Neque, at dignissim magna ullamcorper in aliquam. Zere ARE no ROOLZ! Sagittis massa ac tortor ultrices faucibus curabitur eu mi sapien, ut ultricies. This guy fucking walks. I've never been more certain of anything in my life. Ipsum morbi eget risus nulla nullam vel nisi enim, vel auctor.

64 |

Link 1 (smooth)

65 |

Link 2 (smooth)

66 |

Link 3 (smooth)

67 |

Link 4 (smooth)

68 |

Link 5 (smooth)

69 |

Link 6 (smooth)

70 |

Test 5

71 |

Huh? Oh. Yeah. Tape deck. Couple of Creedence tapes. And there was a, uh… my briefcase. Ante morbi id urna vel felis lacinia placerat vestibulum turpis nulla, viverra. All right, Plan B. You might want to watch out the front window there, Larry. Nec volutpat ac, ornare id. Where's my goddamn money, you bum?! Lectus cras pharetra faucibus tristique nullam non. I know my rights. Accumsan justo nulla facilisi integer interdum elementum nulla, nec eleifend nisl euismod.

72 |

Link 1 (not smooth)

73 |

Link 2 (not smooth)

74 |

Link 3 (not smooth)

75 |

Link 4 (not smooth)

76 |

Link 5 (not smooth)

77 |

Link 6 (not smooth)

78 |

Test 6

79 |

Life does not stop and start at your convenience, you miserable piece of shit. Rhoncus at volutpat felis blandit in libero turpis, laoreet et. I mean I had an M16, Jacko, not an Abrams fucking tank. Molestie sed, volutpat et erat nulla ut orci quis neque consectetur tincidunt. HERE'S WHAT HAPPENS, LARRY! Aliquam erat volutpat donec aliquam orci eget mi lobortis sed tincidunt diam mattis. …and even if he's a lazy man, and the Dude was certainly that—quite possibly the laziest in Los Angeles County. Fusce sem quam, ultricies sed convallis ac, hendrerit eu urna curabitur.

80 |

81 | Scroll to top 82 |

83 |

84 | Scroll to top + 10px 85 |

86 |

87 | Scroll toe far up -100px 88 |

89 |

90 | Set a hash 91 |

92 | 93 | 94 | 95 | 96 | --------------------------------------------------------------------------------