diff --git a/README.md b/README.md index 86270a9..0b1ddbc 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,93 @@ # Simple Scrollspy -[Simple scrollspy](https://huukimit.github.io/simple-scrollspy) is lightweight javascript library without jQuery, no dependencies. Only 1.94Kb. -This is a [demo](https://huukimit.github.io/simple-scrollspy/demo). +[Simple scrollspy](https://huukimit.github.io/simple-scrollspy) is a lightweight javascript library without jQuery, +no dependencies. It is used to make scrollspy effect for your menu, table of contents, etc. +Only 1.94Kb. + +This is a [scrollspy demo](https://huukimit.github.io/simple-scrollspy/demo) for my menu in the navigation bar. ## Install -You can install by `npm` or download inject `simple-scrollspy.js` file into your HTML code: +1. Via NPM: + ```npm npm install simple-scrollspy ``` + +2. The other way, you also can inject `simple-scrollspy.min.js` file into your HTML code: + ```html - + ``` ## Usages + +Easy for using, syntax just like this: + +```html +scrollSpy(menuElement, options) +``` + +This little plugin will add `active` class into your existing menu item when you scroll your page to a matched section by ID. +If you are giving it a try, make sure that you: +1. Added CSS for `active` class in your menu item. Because, this plugin do NOT include CSS. +2. Added ID for your sections. + Example: `
...
` +3. Added an attribute which is an section ID into your menu items. Default is `href`. + Example: `"href"="#first-section"`. +You also replace `href` with the other name by `hrefAttribute` in `options`. + +### Arguments + +1. The `menuElement` is query selector to your menu. It is `String` or `HTMLElement` instance. +2. The `options` is optional. It is type of `Object` which contains properties below: + +| Name | Type | Default | Description | +|--------------------|:---------|:--------------|:-----------------------------------| +| `sectionClass` | String | `.scrollspy` | Query selector to your sections | +| `menuActiveTarget` | String | `li > a` | Element will be added active class | +| `offset` | Number | 0 | Offset number | +| `hrefAttribute` | String | `href` | The menu item's attribute name which contains section ID | +| `activeClass` | String | `active` | Active class name will be added into `menuActiveTarget`| + +### ES6 example + ```js import scrollSpy from 'simple-scrollspy' -scrollSpy('#menu-list', { - offset: 100, - menuActiveTarget: '.menu__item > a', - sectionClass: 'body section.scrollspy', - activeClass: 'active' -}) +const options = { + sectionClass: '.scrollspy', // Query selector to your sections + menuActiveTarget: '.menu-item', // Query selector to your elements that will be added `active` class + offset: 100 // Menu item will active before scroll to a matched section 100px +} + +// init: +scrollSpy(document.getElementById('main-menu'), options) + +// or shorter: +scrollSpy('#main-menu', options) ``` -Or: + +### Inject static file + ```html - + ``` -## Arguments -- The first argument is your menu list selector. Type of `String|HTMLElement`. -- The second argument is optional. Type of `Object`: +## Development -| Name | Type | Default | Description | -| ------------- |:-------------:|:-------------:| :-----------| -| `offset` | Number | 0 | Offset number | -| `menuActiveTarget` | String | `li > a` | Element will be added active class | -| `sectionClass` | String | `.scrollspy` | Query selector to your sections | -| `activeClass` | String | `active` | Active class name | +```bash +$ yarn install +$ yarn dev +``` ## Helpful links - [Documentation](https://huukimit.github.io/simple-scrollspy) diff --git a/demo/demo.css b/demo/demo.css new file mode 100644 index 0000000..e5b57e3 --- /dev/null +++ b/demo/demo.css @@ -0,0 +1,100 @@ +html, body { + padding: 0; + margin: 0; + font-family: Roboto, sans-serif; + font-size: 100%; + color: #333333; +} + +.navbar { + position: fixed; + top: 0; + left: 0; + width: 100%; + min-height: 60px; + color: #333333; + background: #ffffff; + overflow: hidden; + z-index: 1000; + -webkit-box-shadow: 0 4px 4px 0 rgba(0, 0, 0, .1); + -moz-box-shadow: 0 4px 4px 0 rgba(0, 0, 0, .1); + box-shadow: 0 4px 4px 0 rgba(0, 0, 0, .1); +} + +.navbar-brand a { + text-decoration: none; + color: #0097e6; +} + +.navbar .container { + display: flex; + align-items: center; + justify-content: space-between; + max-width: 1200px; + margin-left: auto; + margin-right: auto; + padding-left: 15px; + padding-right: 15px; +} + +.menu-item { + display: inline-block; + font-size: 1.125rem; + text-decoration: none; + padding: 1rem; + color: inherit; +} + +.menu-item:hover { + background: rgba(0, 0, 0, 0.1); +} + +.menu-item.active { + background: #00a8ff; + color: #ffffff; +} + +.section { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100vh; + font-size: 3rem; + font-weight: bold; + color: #ffffff; +} + +.section:nth-child(2) { + background: linear-gradient(#0097e6, #00a8ff); +} + +.section:nth-child(3) { + background: #ffffff; + color: #333333; +} + +.section:nth-child(4) { + background: linear-gradient(#8c7ae6, #9c88ff); +} + +.section:nth-child(5) { + background: linear-gradient(#e1b12c, #fbc531); +} + +.section:nth-child(6) { + background: linear-gradient(#40739e, #487eb0); +} + +.footer { + padding-top: 2rem; + padding-bottom: 3rem; + text-align: center; + background: #353b48; + color: #ffffff; +} +.footer a { + color: #ffffff; + text-decoration: none; + display: inline-block; +} diff --git a/demo/index.html b/demo/index.html index 37666f9..e015b6e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -6,294 +6,54 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> Simple Scrollspy - + + -
- - Simple Scrollspy - -
- -
-

Simple Scrollspy

-

- Simple scrollspy without jQuery, no dependencies and lightweight. Only 1.94Kb.
- This is a demo -

-
-
-
-

Install

-

- You can install by npm or download inject simple-scrollspy.js file into your HTML code: -

-

- - npm install simple-scrollspy - - - <script src="/path/to/dist/simple-scrollspy.js"></script> - -

-
-
-

Usages

-

-

-import scrollSpy from 'simple-scrollspy'
 
-scrollSpy('#menu-list', {
-    offset: 100,
-    menuActiveTarget: '.menu__item > a',
-    sectionClass: 'body section.scrollspy',
-    activeClass: 'active'
-})
-
-

-

-

-<script src="/path/to/dist/simple-scrollspy.js"></script>
-<script>
-    window.onload = function () {
-        scrollSpy('#menu-list', {
-            offset: 100,
-            menuActiveTarget: '.menu__item > a',
-            sectionClass: 'body section.scrollspy',
-            activeClass: 'active'
-        })
-    }
-</script>
-
-

-
-
-

Arguments

-

-

-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDescription
offsetNumber0Offset number
menuActiveTargetStringli > aElement will be added active class
sectionClassString.scrollspyQuery selector to your sections
activeClassStringactiveActive class name
+
-
-

License

-

MIT License


- -

Copyright (c) 2018 Kim Nguyen Huu


-

- Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: -

+ +
+ -

- The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. -

+
Hero
+
Section 1
+
Section 2
+
Section 3
+
Section 4
+ - - Nguyen Huu Kim <kimnguyen.ict@gmail.com> - - - - - + diff --git a/dist/simple-scrollspy.js b/dist/simple-scrollspy.js deleted file mode 100644 index eabce2f..0000000 --- a/dist/simple-scrollspy.js +++ /dev/null @@ -1 +0,0 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.scrollSpy=e():t.scrollSpy=e()}(window,function(){return function(t){var e={};function o(r){if(e[r])return e[r].exports;var n=e[r]={i:r,l:!1,exports:{}};return t[r].call(n.exports,n,n.exports,o),n.l=!0,n.exports}return o.m=t,o.c=e,o.d=function(t,e,r){o.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},o.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=1)}([function(t,e,o){"use strict";o.r(e),o.d(e,"ScrollSpy",function(){return r});class r{constructor(t,e={}){if(!t)throw new Error("First argument is query selector to your navigation.");if("object"!=typeof e)throw new Error("Second argument must be instance of Object.");this.menuList=t instanceof HTMLElement?t:document.querySelector(t),this.options=Object.assign({},{offset:0,menuActiveTarget:"li > a",sectionClass:".scrollspy",activeClass:"active"},e),this.sections=document.querySelectorAll(this.options.sectionClass)}onScroll(){this.sections.forEach(t=>{const e=document.documentElement.scrollTop||document.body.scrollTop;if(t.offsetTop<=e+this.options.offset){const e=t.getAttribute("id"),o=this.menuList.querySelector(`[data-scrollspy="#${e}"]`);if(!o)return;this.removeCurrentActive(),this.setActive(o)}})}setActive(t){t.setAttribute("data-active","true"),t.classList.add(this.options.activeClass)}removeCurrentActive(){this.menuList.querySelectorAll(this.options.menuActiveTarget).forEach(t=>{t.setAttribute("data-active","false"),t.classList.remove(this.options.activeClass)})}}},function(t,e,o){t.exports=((t,e={})=>{const{ScrollSpy:r}=o(0),n=new r(t,e);return window.onload=n.onScroll(),window.addEventListener("scroll",()=>n.onScroll()),n})}])}); \ No newline at end of file diff --git a/dist/simple-scrollspy.min.js b/dist/simple-scrollspy.min.js new file mode 100644 index 0000000..1ee6f92 --- /dev/null +++ b/dist/simple-scrollspy.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.scrollSpy=e():t.scrollSpy=e()}(window,function(){return function(t){var e={};function o(n){if(e[n])return e[n].exports;var s=e[n]={i:n,l:!1,exports:{}};return t[n].call(s.exports,s,s.exports,o),s.l=!0,s.exports}return o.m=t,o.c=e,o.d=function(t,e,n){o.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:n})},o.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=1)}([function(t,e,o){"use strict";o.r(e),o.d(e,"ScrollSpy",function(){return n});class n{constructor(t,e={}){if(!t)throw new Error("First argument is query selector to your navigation.");if("object"!=typeof e)throw new Error("Second argument must be instance of Object.");this.menuList=t instanceof HTMLElement?t:document.querySelector(t),this.options=Object.assign({},{sectionClass:".scrollspy",menuActiveTarget:"li > a",offset:0,hrefAttribute:"href",activeClass:"active"},e),this.sections=document.querySelectorAll(this.options.sectionClass)}onScroll(){const t=this.getSectionInView(),e=this.getMenuItemBySection(t);this.removeCurrentActive({ignore:e}),this.setActive(e)}getMenuItemBySection(t){const e=t.getAttribute("id");return this.menuList.querySelector(`[${this.options.hrefAttribute}="#${e}"]`)}getSectionInView(){for(let t=0;te&&n<=o)return this.sections[t]}}setActive(t){t.classList.contains(this.options.activeClass)||t.classList.add(this.options.activeClass)}removeCurrentActive({ignore:t}){const{hrefAttribute:e,menuActiveTarget:o}=this.options,n=`${o}.active:not([${e}="${t.getAttribute(e)}"])`;this.menuList.querySelectorAll(n).forEach(t=>t.classList.remove(this.options.activeClass))}}},function(t,e,o){t.exports=((t,e={})=>{const{ScrollSpy:n}=o(0),s=new n(t,e);return window.onload=s.onScroll(),window.addEventListener("scroll",()=>s.onScroll()),s})}])}); \ No newline at end of file diff --git a/src/scrollspy.js b/src/scrollspy.js index 1652966..9cf4711 100644 --- a/src/scrollspy.js +++ b/src/scrollspy.js @@ -1,52 +1,59 @@ export class ScrollSpy { - constructor (menu, options = {}) { - if (!menu) { - throw new Error('First argument is query selector to your navigation.') - } - - if (typeof options !== 'object') { - throw new Error('Second argument must be instance of Object.') - } - - let defaultOptions = { - offset: 0, - menuActiveTarget: 'li > a', - sectionClass: '.scrollspy', - activeClass: 'active', - } - - this.menuList = menu instanceof HTMLElement ? menu : document.querySelector(menu) - this.options = Object.assign({}, defaultOptions, options) - this.sections = document.querySelectorAll(this.options.sectionClass) + constructor(menu, options = {}) { + if (!menu) { + throw new Error('First argument is query selector to your navigation.') } - onScroll () { - this.sections.forEach((section) => { - const currentPosition = document.documentElement.scrollTop || document.body.scrollTop - const isInView = section.offsetTop <= currentPosition + this.options.offset - if (isInView) { - const menuItemID = section.getAttribute('id') - const activeItem = this.menuList.querySelector(`[data-scrollspy="#${menuItemID}"]`) - if (!activeItem) { - return - } - - this.removeCurrentActive() - this.setActive(activeItem) - } - }) + if (typeof options !== 'object') { + throw new Error('Second argument must be instance of Object.') } - setActive (activeItem) { - activeItem.setAttribute('data-active', 'true') - activeItem.classList.add(this.options.activeClass) + let defaultOptions = { + sectionClass: '.scrollspy', + menuActiveTarget: 'li > a', + offset: 0, + hrefAttribute: 'href', + activeClass: 'active' } - removeCurrentActive () { - const menuItems = this.menuList.querySelectorAll(this.options.menuActiveTarget) - menuItems.forEach((item) => { - item.setAttribute('data-active', 'false') - item.classList.remove(this.options.activeClass) - }) + this.menuList = menu instanceof HTMLElement ? menu : document.querySelector(menu) + this.options = Object.assign({}, defaultOptions, options) + this.sections = document.querySelectorAll(this.options.sectionClass) + } + + onScroll() { + const section = this.getSectionInView() + const menuItem = this.getMenuItemBySection(section) + + this.removeCurrentActive({ ignore: menuItem }) + this.setActive(menuItem) + } + + getMenuItemBySection(section) { + const sectionId = section.getAttribute('id') + return this.menuList.querySelector(`[${this.options.hrefAttribute}="#${sectionId}"]`) + } + + getSectionInView() { + for (let i = 0; i < this.sections.length; i++) { + const startAt = this.sections[i].offsetTop + const endAt = startAt + this.sections[i].offsetHeight + const currentPosition = (document.documentElement.scrollTop || document.body.scrollTop) + this.options.offset + const isInView = currentPosition > startAt && currentPosition <= endAt + if (isInView) return this.sections[i] } + } + + setActive(activeItem) { + const isActive = activeItem.classList.contains(this.options.activeClass) + if (!isActive) activeItem.classList.add(this.options.activeClass) + } + + removeCurrentActive({ ignore }) { + const { hrefAttribute, menuActiveTarget } = this.options + const items = `${menuActiveTarget}.active:not([${hrefAttribute}="${ignore.getAttribute(hrefAttribute)}"])` + const menuItems = this.menuList.querySelectorAll(items) + + menuItems.forEach((item) => item.classList.remove(this.options.activeClass)) + } } diff --git a/webpack.config.js b/webpack.config.js index 0d2dd4a..334dfc8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,7 +5,7 @@ module.exports = { entry: resolve(__dirname, 'src/index.js'), output: { path: resolve(__dirname, 'dist'), - filename: 'simple-scrollspy.js', + filename: 'simple-scrollspy.min.js', library: 'scrollSpy', libraryTarget: 'umd' },