Skip to content

Commit 25e9b11

Browse files
author
cferdinandi
committed
v10.0.0
Rewrote code for better accessibility and functionality: - Instead of preventing default on click, script now temporarily removes id from the target element to prevent scroll jumping, then adds it back after the hashchange. This provides built in browser history and forward/backward button support. - New approach also allows for animated scrolling on page load - Reversed order of toggle and anchor variables for animateScroll() method - animateScroll() now supports scrolling to a number, not just an element
1 parent 3aae922 commit 25e9b11

File tree

11 files changed

+359
-198
lines changed

11 files changed

+359
-198
lines changed

README.md

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@ Compiled and production-ready code can be found in the `dist` directory. The `sr
1717

1818
### 2. Add the markup to your HTML.
1919

20+
Turn anchor links into Smooth Scroll links by adding the `[data-scroll]` data attribute. Give the anchor location an ID just like you normally would.
21+
2022
```html
2123
<a data-scroll href="#bazinga">Anchor Link</a>
2224
...
2325
<span id="bazinga">Bazinga!</span>
2426
```
2527

26-
Turn anchor links into Smooth Scroll links by adding the `[data-scroll]` data attribute. Give the anchor location an ID just like you normally would.
27-
2828
### 3. Initialize Smooth Scroll.
2929

30+
In the footer of your page, after the content, initialize Smooth Scroll. And that's it, you're done. Nice work!
31+
3032
```html
3133
<script>
3234
smoothScroll.init();
3335
</script>
3436
```
3537

36-
In the footer of your page, after the content, initialize Smooth Scroll. And that's it, you're done. Nice work!
37-
3838

3939

4040
## Installing with Package Managers
@@ -49,7 +49,7 @@ You can install Smooth Scroll with your favorite package manager.
4949

5050
## Working with the Source Files
5151

52-
If you would prefer, you can work with the development code in the `src` directory using the included [Gulp build system](http://gulpjs.com/). This compiles, lints, and minifies code, and runs unit tests. It's the same build system that's used by [Kraken](http://cferdinandi.github.io/kraken/), so it includes some unnecessary tasks and Sass variables but can be dropped right in to the boilerplate without any configuration.
52+
If you would prefer, you can work with the development code in the `src` directory using the included [Gulp build system](http://gulpjs.com/). This compiles, lints, and minifies code, and runs unit tests.
5353

5454
### Dependencies
5555
Make sure these are installed first.
@@ -82,8 +82,8 @@ smoothScroll.init({
8282
selectorHeader: '[data-scroll-header]', // Selector for fixed headers (must be a valid CSS selector)
8383
speed: 500, // Integer. How fast to complete the scroll in milliseconds
8484
easing: 'easeInOutCubic', // Easing pattern to use
85-
updateURL: true, // Boolean. Whether or not to update the URL with the anchor hash on scroll
8685
offset: 0, // Integer. How far to offset the scrolling anchor location in pixels
86+
scrollOnLoad: true, // Boolean. If true, animate to anchor on page load if URL has a hash
8787
callback: function ( toggle, anchor ) {} // Function to run after scrolling
8888
});
8989
```
@@ -127,22 +127,21 @@ Learn more about the different easing patterns and what they do at [easings.net]
127127

128128
### Override settings with data attributes
129129

130-
Smooth Scroll also lets you override global settings on a link-by-link basis using the `[data-options]` data attribute:
130+
Smooth Scroll also lets you override global settings on a link-by-link basis using the `[data-options]` data attribute.
131131

132132
```html
133133
<a data-scroll
134134
data-options='{
135135
"speed": 500,
136136
"easing": "easeInOutCubic",
137-
"offset": 0,
138-
"updateURL": false
137+
"offset": 0
139138
}'
140139
>
141140
Anchor Link
142141
</a>
143142
```
144143

145-
**Note:** You must use [valid JSON](http://jsonlint.com/) in order for the `data-options` feature to work.
144+
***Note:*** *You must use [valid JSON](http://jsonlint.com/) in order for the `data-options` feature to work. Does not support the `callback` method.*
146145

147146
### Use Smooth Scroll events in your own scripts
148147

@@ -153,24 +152,37 @@ Animate scrolling to an anchor.
153152

154153
```javascript
155154
smoothScroll.animateScroll(
156-
toggle, // Node that toggles the animation. ex. document.querySelector('#toggle')
157155
anchor, // ID of the anchor to scroll to. ex. '#bazinga'
156+
toggle, // Node that toggles the animation, OR an integer. ex. document.querySelector('#toggle')
158157
options // Classes and callbacks. Same options as those passed into the init() function.
159158
);
160159
```
161160

162161
**Example 1**
163162

164163
```javascript
165-
smoothScroll.animateScroll( null, '#bazinga' );
164+
smoothScroll.animateScroll( '#bazinga' );
166165
```
167166

168167
**Example 2**
169168

170169
```javascript
171170
var toggle = document.querySelector('#toggle');
172171
var options = { speed: 1000, easing: 'easeOutCubic' };
173-
smoothScroll.animateScroll( toggle, '#bazinga', options );
172+
smoothScroll.animateScroll( '#bazinga', toggle, options );
173+
```
174+
175+
**Example 3**
176+
177+
```javascript
178+
smoothScroll.animateScroll( 750 );
179+
```
180+
181+
#### escapeCharacters()
182+
Escape special characters for use with `animateScroll()`.
183+
184+
```javascript
185+
var toggle = smoothScroll.escapeCharacters('#1@#%^-');
174186
```
175187

176188
#### destroy()
@@ -191,25 +203,9 @@ Add a `[data-scroll-header]` data attribute to fixed headers. Smooth Scroll will
191203
</nav>
192204
```
193205

194-
### Animating links to other pages
195-
196-
Smooth Scroll does not include an option to animate scrolling to links on other pages, but you can easily add this functionality using the API.
197-
198-
1. Do *not* add the `data-scroll` attribute to links to other pages. Treat them like normal links, and include your anchor link hash as normal.
199-
200-
```html
201-
<a href="some-page.html#example">
202-
```
203-
2. Add the following script to the footer of your page, after the `smoothScroll.init()` function.
206+
### Animating links to other pages [NEW in v10]
204207

205-
```html
206-
<script>
207-
if ( window.location.hash ) {
208-
var options = {}; // Any custom options you want to use would go here
209-
smoothScroll.animateScroll( null, window.location.hash, options );
210-
}
211-
</script>
212-
```
208+
Smooth Scroll now supports animating anchor links to other pages. Simply make sure that `scrollOnLoad` is set to `true` (the default) and point your link to the anchor on the page as normal.
213209

214210

215211
## Browser Compatibility
@@ -222,7 +218,9 @@ Smooth Scroll is built with modern JavaScript APIs, and uses progressive enhance
222218

223219
## Known Issues
224220

225-
If the `<body>` element has been assigned a height of `100%`, Smooth Scroll is unable to properly calculate page distances and will not scroll to the right location. The `<body>` element can have a fixed, non-percentage based height (ex. `500px`), or a height of `auto`.
221+
- If the `<body>` element has been assigned a height of `100%`, Smooth Scroll is unable to properly calculate page distances and will not scroll to the right location. The `<body>` element can have a fixed, non-percentage based height (ex. `500px`), or a height of `auto`.
222+
- SmoothScroll looks for a `hash` on the `href` of the link. As a result passing in an empty hash (`#`) will not work and will jump to the top of the page.
223+
- Many browsers will treat `#top` the same as an empty hash (`#`), resulting in a jump to the top of the page. To animate scrolling to the top of the page, please use a different ID.
226224

227225

228226

dist/js/smooth-scroll.js

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*!
2-
* smooth-scroll v7.1.1: Animate scrolling to anchor links
3-
* (c) 2015 Chris Ferdinandi
2+
* smooth-scroll v8.0.0: Animate scrolling to anchor links
3+
* (c) 2016 Chris Ferdinandi
44
* MIT License
55
* http://github.com/cferdinandi/smooth-scroll
66
*/
@@ -22,8 +22,8 @@
2222
//
2323

2424
var smoothScroll = {}; // Object for public APIs
25-
var supports = 'querySelector' in document && 'addEventListener' in root; // Feature test
26-
var settings, eventTimeout, fixedHeader, headerHeight;
25+
var supports = 'querySelector' in document && 'addEventListener' in root && 'onhashchange' in root; // Feature test
26+
var settings, eventTimeout, fixedHeader, headerHeight, anchor;
2727

2828
// Default settings
2929
var defaults = {
@@ -32,7 +32,7 @@
3232
speed: 500,
3333
easing: 'easeInOutCubic',
3434
offset: 0,
35-
updateURL: true,
35+
scrollOnLoad: true,
3636
callback: function () {}
3737
};
3838

@@ -170,12 +170,18 @@
170170

171171
/**
172172
* Escape special characters for use with querySelector
173-
* @private
173+
* @public
174174
* @param {String} id The anchor ID to escape
175175
* @author Mathias Bynens
176176
* @link https://github.com/mathiasbynens/CSS.escape
177177
*/
178-
var escapeCharacters = function ( id ) {
178+
smoothScroll.escapeCharacters = function ( id ) {
179+
180+
// Remove leading hash
181+
if ( id.charAt(0) === '#' ) {
182+
id = id.substr(1);
183+
}
184+
179185
var string = String(id);
180186
var length = string.length;
181187
var index = -1;
@@ -237,7 +243,9 @@
237243
result += '\\' + string.charAt(index);
238244

239245
}
240-
return result;
246+
247+
return '#' + result;
248+
241249
};
242250

243251
/**
@@ -309,17 +317,10 @@
309317
};
310318

311319
/**
312-
* Update the URL
313-
* @private
314-
* @param {Element} anchor The element to scroll to
315-
* @param {Boolean} url Whether or not to update the URL history
320+
* Get the height of a fixed header in pixels
321+
* @param {Node} header The header
322+
* @return {Integer} The header height in pixels
316323
*/
317-
var updateUrl = function ( anchor, url ) {
318-
if ( root.history.pushState && (url || url === 'true') && root.location.protocol !== 'file:' ) {
319-
root.history.pushState( null, null, [root.location.protocol, '//', root.location.host, root.location.pathname, root.location.search, anchor].join('') );
320-
}
321-
};
322-
323324
var getHeaderHeight = function ( header ) {
324325
return header === null ? 0 : ( getHeight( header ) + header.offsetTop );
325326
};
@@ -331,28 +332,26 @@
331332
* @param {Element} anchor The element to scroll to
332333
* @param {Object} options
333334
*/
334-
smoothScroll.animateScroll = function ( toggle, anchor, options ) {
335+
smoothScroll.animateScroll = function ( anchor, toggle, options ) {
335336

336337
// Options and overrides
337338
var overrides = getDataOptions( toggle ? toggle.getAttribute('data-options') : null );
338339
var settings = extend( settings || defaults, options || {}, overrides ); // Merge user options with defaults
339-
anchor = '#' + escapeCharacters(anchor.substr(1)); // Escape special characters and leading numbers
340340

341341
// Selectors and variables
342-
var anchorElem = anchor === '#' ? root.document.documentElement : root.document.querySelector(anchor);
342+
var isNum = Object.prototype.toString.call( anchor ) === '[object Number]' ? true : false;
343+
var anchorElem = isNum ? null : root.document.querySelector(anchor);
344+
if ( !isNum && !anchorElem ) return;
343345
var startLocation = root.pageYOffset; // Current location on the page
344346
if ( !fixedHeader ) { fixedHeader = root.document.querySelector( settings.selectorHeader ); } // Get the fixed header if not already set
345347
if ( !headerHeight ) { headerHeight = getHeaderHeight( fixedHeader ); } // Get the height of a fixed header if one exists and not already set
346-
var endLocation = getEndLocation( anchorElem, headerHeight, parseInt(settings.offset, 10) ); // Scroll to location
348+
var endLocation = isNum ? anchor : getEndLocation( anchorElem, headerHeight, parseInt(settings.offset, 10) ); // Location to scroll to
347349
var animationInterval; // interval timer
348350
var distance = endLocation - startLocation; // distance to travel
349351
var documentHeight = getDocumentHeight();
350352
var timeLapsed = 0;
351353
var percentage, position;
352354

353-
// Update URL
354-
updateUrl(anchor, settings.updateURL);
355-
356355
/**
357356
* Stop the scroll animation when it reaches its target (or the bottom/top of page)
358357
* @private
@@ -364,8 +363,10 @@
364363
var currentLocation = root.pageYOffset;
365364
if ( position == endLocation || currentLocation == endLocation || ( (root.innerHeight + currentLocation) >= documentHeight ) ) {
366365
clearInterval(animationInterval);
367-
anchorElem.focus();
368-
settings.callback( toggle, anchor ); // Run callbacks after animation complete
366+
if ( anchorElem ) {
367+
anchorElem.focus();
368+
}
369+
settings.callback( anchor, toggle ); // Run callbacks after animation complete
369370
}
370371
};
371372

@@ -404,24 +405,65 @@
404405
};
405406

406407
/**
407-
* If smooth scroll element clicked, animate scroll
408+
* Handle has change event
409+
* @private
410+
*/
411+
var hashChangeHandler = function () {
412+
413+
// Get hash from URL
414+
var hash = root.location.hash;
415+
416+
// If anchor target is cached, reset it's ID
417+
if ( anchor ) {
418+
anchor.id = anchor.getAttribute( 'data-scroll-id' );
419+
anchor = null;
420+
}
421+
422+
// If there's a URL hash, animate scrolling to the matching ID
423+
if ( !hash ) return;
424+
hash = smoothScroll.escapeCharacters( hash ); // Escape special characters and leading numbers
425+
var toggle = document.querySelector( settings.select + '[href*="' + hash + '"]');
426+
smoothScroll.animateScroll( hash, toggle, settings); // Animate scroll
427+
428+
};
429+
430+
/**
431+
* Handle toggle click events
408432
* @private
433+
* @param {Event} event
409434
*/
410-
var eventHandler = function (event) {
435+
var clickHandler = function (event) {
436+
437+
// Check if event target is a smooth scroll link and has a hash
411438
var toggle = getClosest( event.target, settings.selector );
412-
if ( toggle && toggle.tagName.toLowerCase() === 'a' ) {
413-
event.preventDefault(); // Prevent default click event
414-
smoothScroll.animateScroll( toggle, toggle.hash, settings); // Animate scroll
439+
if ( !toggle || !toggle.hash ) return;
440+
441+
// Escape the hash characters
442+
var hash = smoothScroll.escapeCharacters( toggle.hash ); // Escape special characters and leading numbers
443+
444+
// If hash is already active, animate immediately (no hash change will fire)
445+
if ( hash === ( root.location.hash || '#top' ) ) {
446+
smoothScroll.animateScroll( hash, toggle, settings);
447+
return;
448+
}
449+
450+
// Get the anchor target
451+
anchor = document.querySelector( hash );
452+
453+
// If anchor exists, save the ID as a data attribute and remove it (prevents scroll jump)
454+
if ( anchor ) {
455+
anchor.setAttribute( 'data-scroll-id', anchor.id );
456+
anchor.id = '';
415457
}
458+
416459
};
417460

418461
/**
419-
* On window scroll and resize, only run events at a rate of 15fps for better performance
462+
* On window resize, only run events at a rate of 15fps for better performance
420463
* @private
421-
* @param {Function} eventTimeout Timeout function
422-
* @param {Object} settings
464+
* @param {Event} event
423465
*/
424-
var eventThrottler = function (event) {
466+
var resizeThrottler = function (event) {
425467
if ( !eventTimeout ) {
426468
eventTimeout = setTimeout(function() {
427469
eventTimeout = null; // Reset timeout
@@ -440,14 +482,16 @@
440482
if ( !settings ) return;
441483

442484
// Remove event listeners
443-
root.document.removeEventListener( 'click', eventHandler, false );
444-
root.removeEventListener( 'resize', eventThrottler, false );
485+
document.removeEventListener( 'click', clickHandler, false );
486+
root.removeEventListener( 'hashchange', hashChangeHandler, false );
487+
root.removeEventListener( 'resize', resizeThrottler, false );
445488

446-
// Reset varaibles
489+
// Reset variables
447490
settings = null;
448491
eventTimeout = null;
449492
fixedHeader = null;
450493
headerHeight = null;
494+
anchor = null;
451495
};
452496

453497
/**
@@ -468,9 +512,18 @@
468512
fixedHeader = root.document.querySelector( settings.selectorHeader ); // Get the fixed header
469513
headerHeight = getHeaderHeight( fixedHeader );
470514

471-
// When a toggle is clicked, run the click handler
472-
root.document.addEventListener('click', eventHandler, false );
473-
if ( fixedHeader ) { root.addEventListener( 'resize', eventThrottler, false ); }
515+
// Event listeners
516+
document.addEventListener('click', clickHandler, false);
517+
root.addEventListener('hashchange', hashChangeHandler, false);
518+
if ( fixedHeader ) { root.addEventListener( 'resize', resizeThrottler, false ); }
519+
520+
// Scroll on load
521+
if ( settings.scrollOnLoad ) {
522+
setTimeout(function() {
523+
window.scrollTo(0, 0);
524+
}, 1);
525+
hashChangeHandler();
526+
}
474527

475528
};
476529

0 commit comments

Comments
 (0)