Repository: eddiemf/vue-scrollactive Branch: master Commit: 9b5350161ec9 Files: 27 Total size: 70.1 KB Directory structure: gitextract_9omynut5/ ├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .webpack/ │ ├── webpack.config.dev.js │ └── webpack.config.prod.js ├── LICENSE ├── README.md ├── dist/ │ ├── index.html │ └── main.js ├── package.json ├── src/ │ ├── ScrollContainer.js │ ├── index.js │ ├── sandbox/ │ │ ├── index.html │ │ ├── normalize.scss │ │ ├── sandbox.js │ │ └── sandbox.scss │ ├── scrollactive.vue │ └── utils/ │ ├── find.js │ ├── forEach.js │ ├── getIdFromHash.js │ ├── getSectionIdFromElement.js │ ├── getSectionSelector.js │ ├── index.js │ └── pushHashToUrl.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ [ "@babel/env", { "modules": false } ] ], "plugins": [ "@babel/plugin-transform-runtime" ] } ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintrc.json ================================================ { "extends": ["eslint:recommended"], "parserOptions": { "sourceType": "module" }, "env": { "browser": true, "es6": true }, "globals": { "Vue": "readonly" } } ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/node,sass,macos,sublimetext,visualstudiocode ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless ### Sass ### .sass-cache/ *.css.map *.sass.map *.scss.map ### SublimeText ### # Cache files for Sublime Text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # Workspace files are user-specific *.sublime-workspace # Project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using Sublime Text # *.sublime-project # SFTP configuration file sftp-config.json # Package control specific files Package Control.last-run Package Control.ca-list Package Control.ca-bundle Package Control.system-ca-bundle Package Control.cache/ Package Control.ca-certs/ Package Control.merged-ca-bundle Package Control.user-ca-bundle oscrypto-ca-bundle.crt bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github GitHub.sublime-settings ### VisualStudioCode ### .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # End of https://www.gitignore.io/api/node,sass,macos,sublimetext,visualstudiocode ================================================ FILE: .prettierrc ================================================ { "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true, "printWidth": 100 } ================================================ FILE: .webpack/webpack.config.dev.js ================================================ const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', entry: path.resolve(__dirname, '../src/sandbox/sandbox.js'), output: { path: path.resolve(__dirname, '../dist'), }, devtool: 'inline-source-map', module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'], }, { test: /\.vue$/, loader: 'vue-loader', }, { test: /\.css$/, use: ['vue-style-loader', 'css-loader'], }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], }, ], }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: 'src/sandbox/index.html', }), ], devServer: { contentBase: path.join(__dirname, '../dist'), }, }; ================================================ FILE: .webpack/webpack.config.prod.js ================================================ const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); module.exports = { mode: 'production', entry: path.resolve(__dirname, '../src/index.js'), output: { filename: 'vue-scrollactive.min.js', globalObject: "typeof self !== 'undefined' ? self : this", path: path.resolve(__dirname, '../dist'), publicPath: '/dist/', library: { root: 'vueScrollactive', amd: 'vue-scrollactive', commonjs: 'vue-scrollactive', }, libraryTarget: 'umd', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'], }, { test: /\.vue$/, use: ['vue-loader'], }, ], }, plugins: [new VueLoaderPlugin()], }; ================================================ FILE: LICENSE ================================================ Copyright (c) 2017 Mauricio Dziedzinski 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. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # vue-scrollactive This component makes it simple to highlight a menu item with an 'active' class as you scroll. - Highlights items with a class as you scroll - Scrolls to item's section on click - Configurable easing scroll effect - Emits events for full control Make sure to check the [demo](https://eddiemf.github.io/vue-scrollactive/dist) where you can play around with every option. ## Installation Install using `yarn` ```bash yarn add vue-scrollactive ``` or `npm` ```bash npm install --save vue-scrollactive ``` then install the plugin ```js import VueScrollactive from 'vue-scrollactive'; Vue.use(VueScrollactive); ``` Or if you wish to include it in a `script` tag, just download the source code from the latest release [here](https://github.com/eddiemf/vue-scrollactive/releases/latest) and include the `vue-scrollactive.min.js` file located in the `dist` folder in your page as a script: ```html ``` If you're not running any transpiler like babel, you'll most likely need to install a Promise polyfill such as [this](https://github.com/taylorhakes/promise-polyfill) to support older browsers since this library depends on promises to work. ## Usage The primary way to use the plugin is to wrap your menu in a `` tag (which will be your nav) and add a `.scrollactive-item` class in your `` tags as I show in the example below: ```html Home About Us Portfolio Contact ``` You can follow whatever structure you wish, just make sure to set the `.scrollactive-item` class in the items you want to highlight and set its `href` with a valid element ID that you would like to track while scrolling. The secondary way to use it is almost the same as the primary but instead of relying on `href` to find your sections you'll need to set a data attribute `data-section-selector` on your elements with the section selector you wish to have. ```html Home About Us Portfolio Contact ``` As you can see this gives you more freedom to choose different tags and you can use whatever CSS selector you find necessary, but it's important to notice that `data-section-selector` takes precedence over `href`, so if you have a tag `` it will completely ignore the `#section-1` and use `#another-section` instead. ## Events Scrollactive will emit an `itemchanged(event, currentItem, lastActiveItem)` event when an active menu item is changed to another. You can catch that event doing as the example below: ```html Home About Us Portfolio Contact ``` ```javascript // ... methods: { onItemChanged(event, currentItem, lastActiveItem) { // here you have access to everything you need regarding that event }, }, // ... ``` ## Configuration All options should be passed as a prop in the `` component as you can see in the example below: ```html ``` Remember that all options are optional and you can check the default values below: ### Options ```javascript /** * Active class that will be applied to the active item. */ activeClass: { type: String, default: 'is-active', }, /** * Amount of space between top of screen and the section to highlight. (Usually your fixed * header's height). */ offset: { type: Number, default: 20, }, /** * Amount of space between the top of the screen and the section to highlight when clicking a * scrollactive item to scroll. It will use the value of the `offset` prop if none is provided * here. Useful when you want to use the `offset` prop to make an item be active as soon as * it shows on the screen but still scroll to the top of the section when clicking the item. */ scrollOffset: { type: Number, default: null, }, /** * The selector string of the scroll container element you'd like to use. It defaults to the * window object (most common), but you might want to change in case you're using an element * as container with overflow. */ scrollContainerSelector: { type: String, default: '', }, /** * Enables/disables the scrolling when clicking in a menu item. * Disable if you'd like to handle the scrolling by your own. */ clickToScroll: { type: Boolean, default: true, }, /** * The duration of the scroll animation when clicking to scroll is activated. */ duration: { type: Number, default: 600, }, /** * Defines if the plugin should track the section change when clicking an item to scroll to * its section. If set to true, it will always keep track and change the active class to the * current section while scrolling, if false, the active class will be immediately applied to * the clicked menu item, ignoring the passed sections until the scrolling is over. */ alwaysTrack: { type: Boolean, default: false, }, /** * Your custom easing value for the click to scroll functionality. * It must be a string with 4 values separated by commas in a cubic bezier format. */ bezierEasingValue: { type: String, default: '.5,0,.35,1', }, /** * Decides if the URL should be modified with the section id when clicking a scrollactive * item. */ modifyUrl: { type: Boolean, default: true, }, /** * If true the active class will only be applied when a section matches exactly one of the * scrollactive items, meaning it will be highlighted when scrolling exactly inside the * section. If false (default) it will always highlight the last item which was matched * in a section, even if it is already outside that section (and not inside another that's * being tracked). */ exact: { type: Boolean, default: false, }, /** * If true the active class will be applied to the first scrollactive-item before you scroll * past it (even if you didn't reach it yet). */ highlightFirstItem: { type: Boolean, default: false, }, /** * Changes the scrollactive container component html tag. */ tag: { type: String, default: 'nav', }, /** * If true the screen will scroll down to the element in the URL when the component is mounted. */ scrollOnStart: { type: Boolean, default: true, }, ``` ## Contributing Clone the repository and install the dependencies running `yarn`. After the dependencies are installed you should be good to run `yarn start` which will load up a server with the sandbox for you to play around. ================================================ FILE: dist/index.html ================================================ Scrollactive!

Section 1

Section 2

Section 3

Section 4

================================================ FILE: dist/main.js ================================================ !(function (e) { var t = {}; function n(o) { if (t[o]) return t[o].exports; var r = (t[o] = { i: o, l: !1, exports: {} }); return e[o].call(r.exports, r, r.exports, n), (r.l = !0), r.exports; } (n.m = e), (n.c = t), (n.d = function (e, t, o) { n.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: o }); }), (n.r = function (e) { 'undefined' != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: 'Module' }), Object.defineProperty(e, '__esModule', { value: !0 }); }), (n.t = function (e, t) { if ((1 & t && (e = n(e)), 8 & t)) return e; if (4 & t && 'object' == typeof e && e && e.__esModule) return e; var o = Object.create(null); if ( (n.r(o), Object.defineProperty(o, 'default', { enumerable: !0, value: e }), 2 & t && 'string' != typeof e) ) for (var r in e) n.d( o, r, function (t) { return e[t]; }.bind(null, r) ); return o; }), (n.n = function (e) { var t = e && e.__esModule ? function () { return e.default; } : function () { return e; }; return n.d(t, 'a', t), t; }), (n.o = function (e, t) { return Object.prototype.hasOwnProperty.call(e, t); }), (n.p = ''), n((n.s = 7)); })([ function (e, t, n) { 'use strict'; var o, r = function () { return void 0 === o && (o = Boolean(window && document && document.all && !window.atob)), o; }, i = (function () { var e = {}; return function (t) { if (void 0 === e[t]) { var n = document.querySelector(t); if (window.HTMLIFrameElement && n instanceof window.HTMLIFrameElement) try { n = n.contentDocument.head; } catch (e) { n = null; } e[t] = n; } return e[t]; }; })(), a = []; function s(e) { for (var t = -1, n = 0; n < a.length; n++) if (a[n].identifier === e) { t = n; break; } return t; } function l(e, t) { for (var n = {}, o = [], r = 0; r < e.length; r++) { var i = e[r], l = t.base ? i[0] + t.base : i[0], c = n[l] || 0, u = ''.concat(l, ' ').concat(c); n[l] = c + 1; var f = s(u), d = { css: i[1], media: i[2], sourceMap: i[3] }; -1 !== f ? (a[f].references++, a[f].updater(d)) : a.push({ identifier: u, updater: v(d, t), references: 1 }), o.push(u); } return o; } function c(e) { var t = document.createElement('style'), o = e.attributes || {}; if (void 0 === o.nonce) { var r = n.nc; r && (o.nonce = r); } if ( (Object.keys(o).forEach(function (e) { t.setAttribute(e, o[e]); }), 'function' == typeof e.insert) ) e.insert(t); else { var a = i(e.insert || 'head'); if (!a) throw new Error( "Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid." ); a.appendChild(t); } return t; } var u, f = ((u = []), function (e, t) { return (u[e] = t), u.filter(Boolean).join('\n'); }); function d(e, t, n, o) { var r = n ? '' : o.media ? '@media '.concat(o.media, ' {').concat(o.css, '}') : o.css; if (e.styleSheet) e.styleSheet.cssText = f(t, r); else { var i = document.createTextNode(r), a = e.childNodes; a[t] && e.removeChild(a[t]), a.length ? e.insertBefore(i, a[t]) : e.appendChild(i); } } function m(e, t, n) { var o = n.css, r = n.media, i = n.sourceMap; if ( (r ? e.setAttribute('media', r) : e.removeAttribute('media'), i && btoa && (o += '\n/*# sourceMappingURL=data:application/json;base64,'.concat( btoa(unescape(encodeURIComponent(JSON.stringify(i)))), ' */' )), e.styleSheet) ) e.styleSheet.cssText = o; else { for (; e.firstChild; ) e.removeChild(e.firstChild); e.appendChild(document.createTextNode(o)); } } var p = null, h = 0; function v(e, t) { var n, o, r; if (t.singleton) { var i = h++; (n = p || (p = c(t))), (o = d.bind(null, n, i, !1)), (r = d.bind(null, n, i, !0)); } else (n = c(t)), (o = m.bind(null, n, t)), (r = function () { !(function (e) { if (null === e.parentNode) return !1; e.parentNode.removeChild(e); })(n); }); return ( o(e), function (t) { if (t) { if (t.css === e.css && t.media === e.media && t.sourceMap === e.sourceMap) return; o((e = t)); } else r(); } ); } e.exports = function (e, t) { (t = t || {}).singleton || 'boolean' == typeof t.singleton || (t.singleton = r()); var n = l((e = e || []), t); return function (e) { if (((e = e || []), '[object Array]' === Object.prototype.toString.call(e))) { for (var o = 0; o < n.length; o++) { var r = s(n[o]); a[r].references--; } for (var i = l(e, t), c = 0; c < n.length; c++) { var u = s(n[c]); 0 === a[u].references && (a[u].updater(), a.splice(u, 1)); } n = i; } }; }; }, function (e, t, n) { 'use strict'; e.exports = function (e) { var t = []; return ( (t.toString = function () { return this.map(function (t) { var n = (function (e, t) { var n = e[1] || '', o = e[3]; if (!o) return n; if (t && 'function' == typeof btoa) { var r = ((a = o), (s = btoa(unescape(encodeURIComponent(JSON.stringify(a))))), (l = 'sourceMappingURL=data:application/json;charset=utf-8;base64,'.concat(s)), '/*# '.concat(l, ' */')), i = o.sources.map(function (e) { return '/*# sourceURL='.concat(o.sourceRoot || '').concat(e, ' */'); }); return [n].concat(i).concat([r]).join('\n'); } var a, s, l; return [n].join('\n'); })(t, e); return t[2] ? '@media '.concat(t[2], ' {').concat(n, '}') : n; }).join(''); }), (t.i = function (e, n, o) { 'string' == typeof e && (e = [[null, e, '']]); var r = {}; if (o) for (var i = 0; i < this.length; i++) { var a = this[i][0]; null != a && (r[a] = !0); } for (var s = 0; s < e.length; s++) { var l = [].concat(e[s]); (o && r[l[0]]) || (n && (l[2] ? (l[2] = ''.concat(n, ' and ').concat(l[2])) : (l[2] = n)), t.push(l)); } }), t ); }; }, function (e, t) { var n = 'function' == typeof Float32Array; function o(e, t) { return 1 - 3 * t + 3 * e; } function r(e, t) { return 3 * t - 6 * e; } function i(e) { return 3 * e; } function a(e, t, n) { return ((o(t, n) * e + r(t, n)) * e + i(t)) * e; } function s(e, t, n) { return 3 * o(t, n) * e * e + 2 * r(t, n) * e + i(t); } function l(e) { return e; } e.exports = function (e, t, o, r) { if (!(0 <= e && e <= 1 && 0 <= o && o <= 1)) throw new Error('bezier x values must be in [0, 1] range'); if (e === t && o === r) return l; for (var i = n ? new Float32Array(11) : new Array(11), c = 0; c < 11; ++c) i[c] = a(0.1 * c, e, o); function u(t) { for (var n = 0, r = 1; 10 !== r && i[r] <= t; ++r) n += 0.1; --r; var l = n + 0.1 * ((t - i[r]) / (i[r + 1] - i[r])), c = s(l, e, o); return c >= 0.001 ? (function (e, t, n, o) { for (var r = 0; r < 4; ++r) { var i = s(t, n, o); if (0 === i) return t; t -= (a(t, n, o) - e) / i; } return t; })(t, l, e, o) : 0 === c ? l : (function (e, t, n, o, r) { var i, s, l = 0; do { (i = a((s = t + (n - t) / 2), o, r) - e) > 0 ? (n = s) : (t = s); } while (Math.abs(i) > 1e-7 && ++l < 10); return s; })(t, n, n + 0.1, e, o); } return function (e) { return 0 === e ? 0 : 1 === e ? 1 : a(u(e), t, r); }; }; }, function (e, t, n) { var o = n(0), r = n(4); 'string' == typeof (r = r.__esModule ? r.default : r) && (r = [[e.i, r, '']]); var i = { insert: 'head', singleton: !1 }; o(r, i); e.exports = r.locals || {}; }, function (e, t, n) { (t = n(1)(!1)).push([ e.i, '/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}\n', '', ]), (e.exports = t); }, function (e, t, n) { var o = n(0), r = n(6); 'string' == typeof (r = r.__esModule ? r.default : r) && (r = [[e.i, r, '']]); var i = { insert: 'head', singleton: !1 }; o(r, i); e.exports = r.locals || {}; }, function (e, t, n) { (t = n(1)(!1)).push([ e.i, '.nav.is-fixed{position:fixed;left:0;right:0}.nav.is-fixed .is-active{color:#00d1b2}.section{padding-top:100px}.buttons{position:fixed;z-index:10;top:100px;right:100px;padding:15px 30px;border-radius:10px;background-color:#7a7a7a;background-color:#fff}.buttons label{display:block}.buttons input,.buttons button{display:block;width:100%;height:42px;padding:10px 20px;margin-top:15px;margin-bottom:15px;border:0;border:2px solid #00d1b2;border-radius:10px;font-weight:600;outline:0;transition:all 0.1s}.buttons button{background-color:#00d1b2;cursor:pointer;color:#fff}.buttons button:hover{background-color:#fff;color:#00d1b2}\n', '', ]), (e.exports = t); }, function (e, t, n) { 'use strict'; n.r(t); var o = function () { var e = this.$createElement; return (this._self._c || e)( this.tag, { ref: 'scrollactive-nav-wrapper', tag: 'component', staticClass: 'scrollactive-nav' }, [this._t('default')], 2 ); }; o._withStripped = !0; var r = n(2), i = n.n(r); function a(e) { return ( (function (e) { if (Array.isArray(e)) return s(e); })(e) || (function (e) { if ('undefined' != typeof Symbol && Symbol.iterator in Object(e)) return Array.from(e); })(e) || (function (e, t) { if (!e) return; if ('string' == typeof e) return s(e, t); var n = Object.prototype.toString.call(e).slice(8, -1); 'Object' === n && e.constructor && (n = e.constructor.name); if ('Map' === n || 'Set' === n) return Array.from(e); if ('Arguments' === n || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return s(e, t); })(e) || (function () { throw new TypeError( 'Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' ); })() ); } function s(e, t) { (null == t || t > e.length) && (t = e.length); for (var n = 0, o = new Array(t); n < t; n++) o[n] = e[n]; return o; } var l = (function (e, t, n, o, r, i, a, s) { var l, c = 'function' == typeof e ? e.options : e; if ( (t && ((c.render = t), (c.staticRenderFns = n), (c._compiled = !0)), o && (c.functional = !0), i && (c._scopeId = 'data-v-' + i), a ? ((l = function (e) { (e = e || (this.$vnode && this.$vnode.ssrContext) || (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext)) || 'undefined' == typeof __VUE_SSR_CONTEXT__ || (e = __VUE_SSR_CONTEXT__), r && r.call(this, e), e && e._registeredComponents && e._registeredComponents.add(a); }), (c._ssrRegister = l)) : r && (l = s ? function () { r.call(this, (c.functional ? this.parent : this).$root.$options.shadowRoot); } : r), l) ) if (c.functional) { c._injectStyles = l; var u = c.render; c.render = function (e, t) { return l.call(t), u(e, t); }; } else { var f = c.beforeCreate; c.beforeCreate = f ? [].concat(f, l) : [l]; } return { exports: e, options: c }; })( { props: { activeClass: { type: String, default: 'is-active' }, offset: { type: Number, default: 20 }, scrollOffset: { type: Number, default: null }, scrollContainerSelector: { type: String, default: '' }, clickToScroll: { type: Boolean, default: !0 }, duration: { type: Number, default: 600 }, alwaysTrack: { type: Boolean, default: !1 }, bezierEasingValue: { type: String, default: '.5,0,.35,1' }, modifyUrl: { type: Boolean, default: !0 }, exact: { type: Boolean, default: !1 }, highlightFirstItem: { type: Boolean, default: !1 }, tag: { type: String, default: 'nav' }, scrollOnStart: { type: Boolean, default: !0 }, }, data: function () { return { observer: null, items: [], currentItem: null, lastActiveItem: null, scrollAnimationFrame: null, bezierEasing: i.a, }; }, computed: { cubicBezierArray: function () { return this.bezierEasingValue.split(','); }, scrollContainer: function () { var e = window; return ( this.scrollContainerSelector && (e = document.querySelector(this.scrollContainerSelector) || window), e ); }, }, mounted: function () { var e = window.MutationObserver || window.WebKitMutationObserver; this.observer || ((this.observer = new e(this.initScrollactiveItems)), this.observer.observe(this.$refs['scrollactive-nav-wrapper'], { childList: !0, subtree: !0, })), this.initScrollactiveItems(), this.removeActiveClass(), (this.currentItem = this.getItemInsideWindow()), this.currentItem && this.currentItem.classList.add(this.activeClass), this.scrollOnStart && this.scrollToHashElement(), this.scrollContainer.addEventListener('scroll', this.onScroll); }, updated: function () { this.initScrollactiveItems(); }, beforeDestroy: function () { this.scrollContainer.removeEventListener('scroll', this.onScroll), window.cancelAnimationFrame(this.scrollAnimationFrame); }, methods: { onScroll: function (e) { (this.currentItem = this.getItemInsideWindow()), this.currentItem !== this.lastActiveItem && (this.removeActiveClass(), this.$emit('itemchanged', e, this.currentItem, this.lastActiveItem), (this.lastActiveItem = this.currentItem)), this.currentItem && this.currentItem.classList.add(this.activeClass); }, getItemInsideWindow: function () { var e, t = this; return ( [].forEach.call(this.items, function (n) { var o = n === t.items[0], r = document.getElementById(decodeURI(n.hash.substr(1))); if (r) { var i = t.scrollContainer.scrollTop || window.pageYOffset, a = i >= t.getOffsetTop(r) - t.offset, s = i < t.getOffsetTop(r) - t.offset + r.offsetHeight; o && t.highlightFirstItem && s && (e = n), t.exact && a && s && (e = n), !t.exact && a && (e = n); } }), e ); }, initScrollactiveItems: function () { var e = this; (this.items = this.$el.querySelectorAll('.scrollactive-item')), this.clickToScroll ? [].forEach.call(this.items, function (t) { t.addEventListener('click', e.handleClick); }) : [].forEach.call(this.items, function (t) { t.removeEventListener('click', e.handleClick); }); }, setScrollactiveItems: function () { this.initScrollactiveItems(); }, handleClick: function (e) { var t = this; e.preventDefault(); var n = e.currentTarget.hash, o = document.getElementById(decodeURI(n.substr(1))); o ? (this.alwaysTrack || (this.scrollContainer.removeEventListener('scroll', this.onScroll), window.cancelAnimationFrame(this.scrollAnimationFrame), this.removeActiveClass(), e.currentTarget.classList.add(this.activeClass)), this.scrollTo(o).then(function () { if (!t.alwaysTrack) { t.scrollContainer.addEventListener('scroll', t.onScroll); (t.currentItem = [].find.call(t.items, function (e) { return decodeURI(e.hash.substr(1)) === o.id; })), t.currentItem !== t.lastActiveItem && (t.$emit('itemchanged', null, t.currentItem, t.lastActiveItem), (t.lastActiveItem = t.currentItem)); } t.modifyUrl && t.pushHashToUrl(n); })) : console.warn( "[vue-scrollactive] Element '".concat( n, "' was not found. Make sure it is set in the DOM." ) ); }, scrollTo: function (e) { var t = this; return new Promise(function (n) { var o = t.getOffsetTop(e), r = t.scrollContainer.scrollTop || window.pageYOffset, i = o - r, s = t.bezierEasing.apply(t, a(t.cubicBezierArray)), l = null; window.requestAnimationFrame(function e(o) { l || (l = o); var a = o - l, c = a / t.duration; a >= t.duration && (a = t.duration), c >= 1 && (c = 1); var u = t.scrollOffset || t.offset, f = r + s(c) * (i - u); t.scrollContainer.scrollTo(0, f), a < t.duration ? (t.scrollAnimationFrame = window.requestAnimationFrame(e)) : n(); }); }); }, getOffsetTop: function (e) { for (var t = 0, n = e; n; ) (t += n.offsetTop), (n = n.offsetParent); return this.scrollContainer.offsetTop && (t -= this.scrollContainer.offsetTop), t; }, removeActiveClass: function () { var e = this; [].forEach.call(this.items, function (t) { t.classList.remove(e.activeClass); }); }, scrollToHashElement: function () { var e = this, t = window.location.hash; if (t) { var n = document.querySelector(decodeURI(t)); n && ((window.location.hash = ''), setTimeout(function () { var o = n.offsetTop - e.offset; e.scrollContainer.scrollTo(0, o), e.pushHashToUrl(t); }, 0)); } }, pushHashToUrl: function (e) { window.history.pushState ? window.history.pushState(null, null, e) : (window.location.hash = e); }, }, }, o, [], !1, null, null, null ); l.options.__file = 'src/scrollactive.vue'; var c = l.exports, u = { install: function (e) { u.install.installed || e.component('scrollactive', c); }, }; 'undefined' != typeof window && window.Vue && u.install(window.Vue); var f = u; n(3), n(5); Vue.use(f); var d = new Vue({ el: '#app', data: { elements: [], alwaysTrack: !1, duration: 600, clickToScroll: !0, offset: 52, easing: '.5,0,.35,1', }, computed: { numberOfElements: function () { return this.elements.length; }, }, mounted: function () { this.elements = this.$el.querySelectorAll('.scrollactive-item'); }, methods: { addNewElement: function () { var e = this.numberOfElements + 1, t = this.numberOfElements % 2 == 0 ? 'is-primary' : 'is-danger', n = document.createElement('div'); (n.innerHTML = 'Section ') .concat(e, '')), document.querySelector('.nav-center').appendChild(n.firstChild); var o = document.createElement('div'); (o.innerHTML = '
\n
\n

Section ' ) .concat(e, '

\n
\n
\n ')), document.querySelector('main').appendChild(o.firstChild), (this.elements = this.$el.querySelectorAll('.scrollactive-item')); }, removeElement: function () { if (this.numberOfElements >= 1) { var e = [].map .call(this.elements, function (e) { return e.hash; }) .slice(-1); document.querySelector('.nav-center a[href="'.concat(e, '"]')).remove(), document.querySelector('main').removeChild(document.querySelector(e)), (this.elements = this.$el.querySelectorAll('.scrollactive-item')); } }, }, }); t.default = d; }, ]); ================================================ FILE: package.json ================================================ { "name": "vue-scrollactive", "version": "0.9.3", "title": "Vue Scrollactive", "description": "Lightweight and simple to use vue component that highlights menu items as you scroll the page, also scrolling to target section when clicked.", "main": "dist/vue-scrollactive.min.js", "scripts": { "start": "webpack-dev-server", "sandbox": "webpack", "prod": "TARGET=production webpack" }, "repository": { "type": "git", "url": "https://github.com/eddiemf/vue-scrollactive.git" }, "keywords": [ "vue", "vue-plugin", "vue-component", "scrollspy", "scroll", "active", "active-class", "menu-item", "highlight-menu-item" ], "author": { "name": "Mauricio Farias Dziedzinski", "email": "d.mauriciofarias@gmail.com" }, "license": "MIT", "bugs": { "url": "https://github.com/eddiemf/vue-scrollactive/issues" }, "homepage": "https://github.com/eddiemf/vue-scrollactive#readme", "dependencies": { "bezier-easing": "^2.0.3" }, "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.9.6", "@babel/plugin-transform-runtime": "^7.9.6", "@babel/preset-env": "^7.9.6", "babel-loader": "^8.1.0", "css-loader": "^3.5.3", "eslint": "^7.0.0", "html-webpack-plugin": "^4.3.0", "husky": "^4.2.5", "node-sass": "^4.14.1", "prettier": "^2.0.5", "pretty-quick": "^2.0.1", "sass-loader": "^8.0.2", "style-loader": "^1.2.1", "vue-loader": "^15.9.2", "vue-style-loader": "^4.1.2", "vue-template-compiler": "^2.6.11", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0" }, "husky": { "hooks": { "pre-commit": "pretty-quick --staged" } } } ================================================ FILE: src/ScrollContainer.js ================================================ export class ScrollContainer { constructor(containerSelector) { let container = window; if (containerSelector) { container = document.querySelector(containerSelector) || window; } this.container = container; } addScrollListener(callback) { this.scrollListenerCallback = callback; this.container.addEventListener('scroll', callback); } removeScrollListener() { this.container.removeEventListener('scroll', this.scrollListenerCallback); } getDistanceFromTop() { return this.container.scrollTop || this.container.pageYOffset; } scrollTo(x, y) { return this.container.scrollTo(x, y); } getOffsetTop() { return this.container.offsetTop; } } ================================================ FILE: src/index.js ================================================ import Scrollactive from './scrollactive.vue'; const Plugin = {}; Plugin.install = (Vue) => { if (Plugin.install.installed) return; Vue.component('scrollactive', Scrollactive); }; if (typeof window !== 'undefined' && window.Vue) { Plugin.install(window.Vue); } export default Plugin; ================================================ FILE: src/sandbox/index.html ================================================ Scrollactive!

Section 1

Section 2

Section 3

Section 4

================================================ FILE: src/sandbox/normalize.scss ================================================ /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ /* Document ========================================================================== */ /** * 1. Correct the line height in all browsers. * 2. Prevent adjustments of font size after orientation changes in * IE on Windows Phone and in iOS. */ html { line-height: 1.15; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /* Sections ========================================================================== */ /** * Remove the margin in all browsers (opinionated). */ body { margin: 0; } /** * Add the correct display in IE 9-. */ article, aside, footer, header, nav, section { display: block; } /** * Correct the font size and margin on `h1` elements within `section` and * `article` contexts in Chrome, Firefox, and Safari. */ h1 { font-size: 2em; margin: 0.67em 0; } /* Grouping content ========================================================================== */ /** * Add the correct display in IE 9-. * 1. Add the correct display in IE. */ figcaption, figure, main { /* 1 */ display: block; } /** * Add the correct margin in IE 8. */ figure { margin: 1em 40px; } /** * 1. Add the correct box sizing in Firefox. * 2. Show the overflow in Edge and IE. */ hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ pre { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /* Text-level semantics ========================================================================== */ /** * 1. Remove the gray background on active links in IE 10. * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. */ a { background-color: transparent; /* 1 */ -webkit-text-decoration-skip: objects; /* 2 */ } /** * 1. Remove the bottom border in Chrome 57- and Firefox 39-. * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ text-decoration: underline dotted; /* 2 */ } /** * Prevent the duplicate application of `bolder` by the next rule in Safari 6. */ b, strong { font-weight: inherit; } /** * Add the correct font weight in Chrome, Edge, and Safari. */ b, strong { font-weight: bolder; } /** * 1. Correct the inheritance and scaling of font size in all browsers. * 2. Correct the odd `em` font sizing in all browsers. */ code, kbd, samp { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } /** * Add the correct font style in Android 4.3-. */ dfn { font-style: italic; } /** * Add the correct background and color in IE 9-. */ mark { background-color: #ff0; color: #000; } /** * Add the correct font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` elements from affecting the line height in * all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sub { bottom: -0.25em; } sup { top: -0.5em; } /* Embedded content ========================================================================== */ /** * Add the correct display in IE 9-. */ audio, video { display: inline-block; } /** * Add the correct display in iOS 4-7. */ audio:not([controls]) { display: none; height: 0; } /** * Remove the border on images inside links in IE 10-. */ img { border-style: none; } /** * Hide the overflow in IE. */ svg:not(:root) { overflow: hidden; } /* Forms ========================================================================== */ /** * 1. Change the font styles in all browsers (opinionated). * 2. Remove the margin in Firefox and Safari. */ button, input, optgroup, select, textarea { font-family: sans-serif; /* 1 */ font-size: 100%; /* 1 */ line-height: 1.15; /* 1 */ margin: 0; /* 2 */ } /** * Show the overflow in IE. * 1. Show the overflow in Edge. */ button, input { /* 1 */ overflow: visible; } /** * Remove the inheritance of text transform in Edge, Firefox, and IE. * 1. Remove the inheritance of text transform in Firefox. */ button, select { /* 1 */ text-transform: none; } /** * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` * controls in Android 4. * 2. Correct the inability to style clickable types in iOS and Safari. */ button, html [type="button"], /* 1 */ [type="reset"], [type="submit"] { -webkit-appearance: button; /* 2 */ } /** * Remove the inner border and padding in Firefox. */ button::-moz-focus-inner, [type='button']::-moz-focus-inner, [type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner { border-style: none; padding: 0; } /** * Restore the focus styles unset by the previous rule. */ button:-moz-focusring, [type='button']:-moz-focusring, [type='reset']:-moz-focusring, [type='submit']:-moz-focusring { outline: 1px dotted ButtonText; } /** * Correct the padding in Firefox. */ fieldset { padding: 0.35em 0.75em 0.625em; } /** * 1. Correct the text wrapping in Edge and IE. * 2. Correct the color inheritance from `fieldset` elements in IE. * 3. Remove the padding so developers are not caught out when they zero out * `fieldset` elements in all browsers. */ legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } /** * 1. Add the correct display in IE 9-. * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. */ progress { display: inline-block; /* 1 */ vertical-align: baseline; /* 2 */ } /** * Remove the default vertical scrollbar in IE. */ textarea { overflow: auto; } /** * 1. Add the correct box sizing in IE 10-. * 2. Remove the padding in IE 10-. */ [type='checkbox'], [type='radio'] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /** * Correct the cursor style of increment and decrement buttons in Chrome. */ [type='number']::-webkit-inner-spin-button, [type='number']::-webkit-outer-spin-button { height: auto; } /** * 1. Correct the odd appearance in Chrome and Safari. * 2. Correct the outline style in Safari. */ [type='search'] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } /** * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. */ [type='search']::-webkit-search-cancel-button, [type='search']::-webkit-search-decoration { -webkit-appearance: none; } /** * 1. Correct the inability to style clickable types in iOS and Safari. * 2. Change font properties to `inherit` in Safari. */ ::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } /* Interactive ========================================================================== */ /* * Add the correct display in IE 9-. * 1. Add the correct display in Edge, IE, and Firefox. */ details, /* 1 */ menu { display: block; } /* * Add the correct display in all browsers. */ summary { display: list-item; } /* Scripting ========================================================================== */ /** * Add the correct display in IE 9-. */ canvas { display: inline-block; } /** * Add the correct display in IE. */ template { display: none; } /* Hidden ========================================================================== */ /** * Add the correct display in IE 10-. */ [hidden] { display: none; } ================================================ FILE: src/sandbox/sandbox.js ================================================ import Scrollactive from '../index'; import './normalize.scss'; import './sandbox.scss'; Vue.use(Scrollactive); const app = new Vue({ el: '#app', data: { elements: [], alwaysTrack: false, duration: 600, clickToScroll: true, offset: 52, easing: '.5,0,.35,1', }, computed: { numberOfElements() { return this.elements.length; }, }, mounted() { this.elements = this.$el.querySelectorAll('.scrollactive-item'); }, methods: { addNewElement() { const sectionNumber = this.numberOfElements + 1; const colorClass = this.numberOfElements % 2 === 0 ? 'is-primary' : 'is-danger'; const menuItem = document.createElement('div'); menuItem.innerHTML = `Section ${sectionNumber}`; document.querySelector('.nav-center').appendChild(menuItem.firstChild); const section = document.createElement('div'); section.innerHTML = `

Section ${sectionNumber}

`; document.querySelector('main').appendChild(section.firstChild); this.elements = this.$el.querySelectorAll('.scrollactive-item'); }, removeElement() { if (this.numberOfElements >= 1) { const elementsIds = [].map.call(this.elements, (el) => el.hash); const lastElementId = elementsIds.slice(-1); document.querySelector(`.nav-center a[href="${lastElementId}"]`).remove(); document.querySelector('main').removeChild(document.querySelector(lastElementId)); this.elements = this.$el.querySelectorAll('.scrollactive-item'); } }, }, }); export default app; ================================================ FILE: src/sandbox/sandbox.scss ================================================ .nav.is-fixed { position: fixed; left: 0; right: 0; .is-active { color: #00d1b2; } } .section { padding-top: 100px; } .buttons { position: fixed; z-index: 10; top: 100px; right: 100px; padding: 15px 30px; border-radius: 10px; background-color: #7a7a7a; background-color: #fff; label { display: block; } input, button { display: block; width: 100%; height: 42px; padding: 10px 20px; margin-top: 15px; margin-bottom: 15px; border: 0; border: 2px solid #00d1b2; border-radius: 10px; font-weight: 600; outline: 0; transition: all 0.1s; } button { background-color: #00d1b2; cursor: pointer; color: #fff; &:hover { background-color: #fff; color: #00d1b2; } } } ================================================ FILE: src/scrollactive.vue ================================================ ================================================ FILE: src/utils/find.js ================================================ // Must be called with 'call' to prevent bugs on some devices export const find = (list, callback) => [].find.call(list, callback); ================================================ FILE: src/utils/forEach.js ================================================ // Must be called with 'call' to prevent bugs on some devices export const forEach = (list, callback) => [].forEach.call(list, callback); ================================================ FILE: src/utils/getIdFromHash.js ================================================ // Decoded in case there are special characters export const getIdFromHash = (hash) => decodeURI(hash.substr(1)); ================================================ FILE: src/utils/getSectionIdFromElement.js ================================================ export const getSectionIdFromElement = (element) => { if (element.dataset.sectionSelector && element.dataset.sectionSelector.substr(0, 1) === '#') { return element.dataset.sectionSelector; } return element.hash; }; ================================================ FILE: src/utils/getSectionSelector.js ================================================ import { getIdFromHash } from './getIdFromHash'; export const getSectionSelector = (element) => { if (element.dataset.sectionSelector) return element.dataset.sectionSelector; if (element.hash) return `#${getIdFromHash(element.hash)}`; return ''; }; ================================================ FILE: src/utils/index.js ================================================ export { forEach } from './forEach'; export { find } from './find'; export { getIdFromHash } from './getIdFromHash'; export { pushHashToUrl } from './pushHashToUrl'; export { getSectionSelector } from './getSectionSelector'; export { getSectionIdFromElement } from './getSectionIdFromElement'; ================================================ FILE: src/utils/pushHashToUrl.js ================================================ /** * Pushes the given hash to the URL using primarily pushState if available to prevent the * scroll from jumping to the hash element. Uses window.location.hash as a fallback. * * @param {String} hash The hash value to be pushed */ export const pushHashToUrl = (hash) => { if (window.history.pushState) { window.history.pushState(null, null, hash); return; } window.location.hash = hash; }; ================================================ FILE: webpack.config.js ================================================ const TARGET = process.env.TARGET; if (TARGET === 'production') { module.exports = require('./.webpack/webpack.config.prod'); } else { module.exports = require('./.webpack/webpack.config.dev'); }