ECMAScript2015+ & TypeScript Friendly, dependency-free smooth scroll library.
:lollipop: See Demo
Features
- Dependecy-free!!
- ECMAScript2015+ & TypeScript friendly
- Use
requestAnimationFrame
API - Supports vertical and horizontal scroll
- Supports dynamic trigger (event delegation)
- Supports container for the scroll
- Supports many easing types
- Supports server-side rendering (Can load without putting out errors.)
Table of Contents
Usage
1. Install
via NPM
$ npm install sweet-scroll
use
import SweetScroll from 'sweet-scroll';
via MANUAL
- Download the sweet-scroll.min.js
- Load it in the script tag.
<script src="sweet-scroll.min.js"></script>
via CDN (UNPKG)
<script src="https://unpkg.com/sweet-scroll/sweet-scroll.min.js"></script>
2. Setup of HTML
<a href="#intro" data-scroll>Go to Introduction</a>
...
<div id="intro">Introduction</div>
3. Initialize SweetScroll
You need to initialize an instance after DOMContentLoaded
.
document.addEventListener('DOMContentLoaded', () => {
const scroller = new SweetScroll({});
}, false);
Options
The following options are applied by default. It can be customized as needed.
{
trigger: '[data-scroll]',
header: '[data-scroll-header]',
duration: 1000,
easing: 'easeOutQuint',
offset: 0,
vertical: true,
horizontal: false,
cancellable: true,
updateURL: false,
preventDefault: true,
stopPropagation: true,
quickMode: false,
before: null,
after: null,
cancel: null,
complete: null,
step: null,
}
Easings
Supports the following easing.
Built-in (22)
- Normal
- Quad
easeInQuad
easeOutQuad
easeInOutQuad
- Cubic
easeInCubic
easeOutCubic
easeInOutCubic
- Quart
easeInQuart
easeOutQuart
easeInOutQuart
- Quint
easeInQuint
easeOutQuint
(default)easeInOutQuint
- Sine
easeInSine
easeOutSine
easeInOutSine
- Expo
easeInExpo
easeOutExpo
easeInOutExpo
- Circ
easeInCirc
easeOutCirc
easeInOutCirc
Advanced (9)
Easing functions that are not built in can pass functions directly.
const scroller = new SweetScroll({
easing: advancedEasingFunction,
});
Elastic
easeInElastic
const easeInElastic = (_, t, b, c, d) => {
let s = 1.70158;
let p = 0;
let a = c;
if (t === 0) return b;
if ((t /= d) === 1) return b + c;
if (!p) p = d * 0.3;
if (a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * asin(c / a);
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
};
easeOutElastic
const easeOutElastic = (_, t, b, c, d) => {
let s = 1.70158;
let p = 0;
let a = c;
if (t === 0) return b;
if ((t /= d) === 1) return b + c;
if (!p) p = d * 0.3;
if (a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * asin(c / a);
}
return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
};
easeInOutElastic
const easeInOutElastic = (_, t, b, c, d) => {
let s = 1.70158;
let p = 0;
let a = c;
if (t === 0) return b;
if ((t /= d / 2) === 2) return b + c;
if (!p) p = d * (0.3 * 1.5);
if (a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
if (t < 1) {
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
}
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
};
Back
easeInBack
const easeInBack = (_, t, b, c, d, s = 1.70158) => (
c * (t /= d) * t * ((s + 1) * t - s) + b
);
easeOutBack
const easeOutBack = (_, t, b, c, d, s = 1.70158) => (
c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b
);
easeInOutBack
const easeInOutBack = (_, t, b, c, d, s = 1.70158) => (
(t /= d / 2) < 1
? c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b
: c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b
);
Bounce
easeOutBounce
const easeOutBounce = (_, t, b, c, d) => {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
};
easeInBounce
const easeOutBounce = (_, t, b, c, d) => {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
};
const easeInBounce = (x, t, b, c, d) => (
c - easeOutBounce(x, d - t, 0, c, d) + b
);
easeInOutBounce
const easeOutBounce = (_, t, b, c, d) => {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
};
const easeInBounce = (x, t, b, c, d) => (
c - easeOutBounce(x, d - t, 0, c, d) + b
);
const easeInOutBounce = (x, t, b, c, d) => (
t < d / 2
? easeInBounce(x, t * 2, 0, c, d) * 0.5 + b
: easeOutBounce(x, t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b
);
Live demo
Customizing Tips
Specifying container elements
In the following example we have specified in the container for scrolling the #container
.
<div id="container">
<a href="#heading2" data-scroll>Go to Heading2</a>
...
<h2 id="heading2">Heading2</h2>
</div>
const scroller = new SweetScroll({}, '#container');
const scroller = new SweetScroll({}, document.getElementById('container'));
Add the data-scroll-header
attribute in order to offset the height of the fixed header.
<header data-scroll-header></header>
Specify the CSS Selector in header
option instead of the data-scroll-header
attribute.
const scroller = new SweetScroll({
header: '#header',
});
Override of options for each element
You can override the default options by passing the option in JSON
format to the data-scroll-options
.
<a href="#target" data-scroll data-scroll-options='{"easing": "easeOutExpo"}'>Go to Target</a>
if you want to use in non anchor element
Will use the data-scroll attribute instead of href.
<button type="button" data-scroll="+=500">Scroll under 500px</button>
Do you feel scrolling is slow?
You can solve with quickMode
options!
const scroller = new SweetScroll({
quickMode: true,
easing: 'easeOutExpo',
});
quickMode
finishes scrolling in a moment.
Scrolling animation in another page
The following, Introduce one of the mounting method.
document.addEventListener('DOMContentLoaded', () => {
const scroller = new SweetScroll();
const hash = window.location.hash;
const needsInitialScroll = document.getElementById(hash.substr(1)) != null;
if (needsInitialScroll) {
window.location.hash = '';
}
window.addEventListener('load', () => {
if (needsInitialScroll) {
scroller.to(hash, { updateURL: 'replace' });
}
}, false);
}, false);
Live demo
You can also achieve the same thing in other ways by using the provided API.
API
new SweetScroll(options?: PartialOptions, container?: string | Element)
Will generate a SweetScroll instance.
Example:
const scroller = new SweetScroll({
duration: 1200,
easing: 'easeOutExpo',
}, '#container');
SweetScroll.create(options?: PartialOptions, container?: string | Element)
Will generate a SweetScroll instance. (factory method)
Example:
const scroller = SweetScroll.create({
duration: 1200,
easing: 'easeOutExpo',
}, '#container');
to(distance: any, options?: PartialOptions)
Scroll animation to the specified distance
.
distance
to can specify the CSS Selector or scroll position.
Example:
const scroller = new SweetScroll();
scroller.to('#footer');
scroller.to({ top: 1000, left: 20 });
scroller.to([0, 1000]);
scroller.to(500);
scroller.to('+=500');
scroller.to('-=200');
toTop(distance: any, options?: PartialOptions)
Vertical scroll animation to the specified distance
.
Example:
scroller.toTop(0);
toLeft(distance: any, options?: PartialOptions)
Horizontal scroll animation to the specified distance
.
Example:
scroller.toLeft(1500);
toElement($el: Element, options?: PartialOptions)
Scroll animation to the specified Element
.
Example:
scroller.toElement(document.getElementById('content'));
update(options: PartialOptions)
Will update the SweetScroll instance.
Primarily used in the case of option update.
Example:
scroller.update({
trigger: 'a[href^="#"]',
duration: 3000,
});
stop(gotoEnd: boolean = true)
gotoEnd: {Boolean}
Will stop the current scroll animation.
Example:
scroller.stop(true);
destroy()
Will destroy the SweetScroll instance.
Disable of the method and event handler.
Example:
scroller.destroy();
Callbacks
In before
and after
, you will pass the coordinates and the triggering element in the argument.
In addition, you can stop the scrolling by return a before
in false
.
Example:
const scroller = new SweetScroll({
before: (offset: Offset, $trigger: Element | null, scroller: SweetScroll): boolean | void => {
console.log('Before!!', offset, scroller);
if ($trigger && $trigger.classList.contains('is-disabled')) {
return false;
}
},
cancel: (scroller: SweetScroll): void => {
console.log('Cancel!!', scroller);
},
after: (offset: Offset, $trigger: Element | null, scroller: SweetScroll): void => {
console.log('After!!', offset, $trigger, scroller);
},
complete: (isCancel: boolean, scroller: SweetScroll): void => {
console.log('Complete!!', isCancel, scroller);
},
step: (time: number, scroller: SweetScroll): void => {
console.log('step', time, scroller);
},
});
Extends Class:
The following is a pattern to override a method in the inheritance destination class.
import SweetScroll, { Offset } from 'sweet-scroll';
class MyScroll extends SweetScroll {
protected onBefore(offset: Offset, $trigger: Element | null): boolean | void {
console.log('Before!!', offset);
if ($trigger && $trigger.classList.contains('is-disabled')) {
return false;
}
}
protected onCancel(): void {
console.log('Canell!!');
}
protected onAfter(offset: Offset, $trigger: Element | null): void {
console.log('After!!', offset, $trigger);
}
protected onComplete(isCancel: boolean): void {
console.log('Complete!!', isCancel);
}
protected onStep(time: number): void {
console.log('step', time);
}
}
Browser Support
Works in IE10+
, and all modern browsers.
Scrolling with IE9
It is necessary to use polyfill or ponyfill of requestAnimationFrame
.
Example ponyfill
Using raf module.
import raf from 'raf';
import SweetScroll from 'sweet-scroll';
SweetScroll.raf = raf;
SweetScroll.caf = raf.cancel;
CHANGELOG
See the CHANGELOG.md
Contibute
- Fork it!
- Create your feature branch:
git checkout -b my-new-feature
- Commit your changes:
git commit -am 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request :muscle:
Bugs, feature requests and comments are more than welcome in the issues.
Development
We will develop using the following npm scripts.
npm start
Launch the local server and let the demo run. Opening http://localhost:3000 in your browser.
npm run build
Compile TypeScript and create type definitions.
npm run test
Run unit testing with Jest.
License
MIT © tsuyoshiwada