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
<script src="dist/vue-scrollactive.min.js"></script>
```
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 `<scrollactive>` tag (which will be your nav) and add a `.scrollactive-item` class in your `<a>` tags as I show in the example below:
```html
<scrollactive class="my-nav">
<a href="#home" class="scrollactive-item">Home</a>
<a href="#about-us" class="scrollactive-item">About Us</a>
<a href="#portfolio" class="scrollactive-item">Portfolio</a>
<a href="#contact" class="scrollactive-item">Contact</a>
</scrollactive>
```
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
<scrollactive class="my-nav">
<span data-section-selector="#home" class="scrollactive-item">Home</span>
<span data-section-selector=".about-us" class="scrollactive-item">About Us</span>
<span data-section-selector=".portfolio div span" class="scrollactive-item">Portfolio</span>
<span data-section-selector="#contact" class="scrollactive-item">Contact</span>
</scrollactive>
```
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 `<a href="#section-1" data-section-selector="#another-section">` 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
<scrollactive class="my-nav" v-on:itemchanged="onItemChanged">
<a href="#home" class="scrollactive-item">Home</a>
<a href="#about-us" class="scrollactive-item">About Us</a>
<a href="#portfolio" class="scrollactive-item">Portfolio</a>
<a href="#contact" class="scrollactive-item">Contact</a>
</scrollactive>
```
```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 `<scrollactive>` component as you can see in the example below:
```html
<scrollactive active-class="active" :offset="80" :duration="800" bezier-easing-value=".5,0,.35,1">
</scrollactive>
```
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Scrollactive!</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.4.2/css/bulma.min.css"
/>
</head>
<body>
<div id="app">
<header id="site-header" class="nav is-fixed">
<div class="container">
<scrollactive
ref="scrollactive"
:offset="offset"
:always-track="alwaysTrack"
:duration="duration"
:click-to-scroll="clickToScroll"
:bezier-easing-value="easing"
><ul class="nav-center">
<li><a href="#section-1" class="scrollactive-item nav-item">Section 1</a></li>
<li><a href="#section-2" class="scrollactive-item nav-item">Section 2</a></li>
<li><a href="#section-3" class="scrollactive-item nav-item">Section 3</a></li>
<li><a href="#section-4" class="scrollactive-item nav-item">Section 4</a></li>
</ul></scrollactive
>
</div>
</header>
<div class="buttons">
<button @click="addNewElement()">Add new element</button>
<button @click="removeElement()">Remove last element</button>
<button
@click="alwaysTrack = !alwaysTrack"
v-text="'Always track ' + (alwaysTrack ? 'on' : 'off')"
></button>
<button
@click="clickToScroll = !clickToScroll"
v-text="'Click to scroll ' + (clickToScroll ? 'on' : 'off')"
></button>
<label for="duration">Duration</label>
<input type="number" v-model="duration" id="duration" /> <label for="offset">Offset</label>
<input type="number" v-model="offset" id="offset" />
<label for="easing">Easing (cubic-bezier format)</label>
<input v-model="easing" id="easing" />
</div>
<main>
<section id="section-1" class="section hero is-primary is-fullheight">
<div class="container"><h1 class="heading title is-1">Section 1</h1></div>
</section>
<section id="section-2" class="section hero is-info is-fullheight">
<div class="container"><h1 class="heading title is-1">Section 2</h1></div>
</section>
<section id="section-3" class="section hero is-danger is-fullheight">
<div class="container"><h1 class="heading title is-1">Section 3</h1></div>
</section>
<section id="section-4" class="section hero is-success is-fullheight">
<div class="container"><h1 class="heading title is-1">Section 4</h1></div>
</section>
</main>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="main.js"></script>
</body>
</html>
================================================
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 = '<a href="#section-'
.concat(e, '" class="scrollactive-item nav-item">Section ')
.concat(e, '</a>')),
document.querySelector('.nav-center').appendChild(n.firstChild);
var o = document.createElement('div');
(o.innerHTML = '<section id="section-'
.concat(e, '" class="section hero ')
.concat(
t,
' is-fullheight">\n <div class="container">\n <h1 class="heading title is-1">Section '
)
.concat(e, '</h1>\n </div>\n </section>\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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Scrollactive!</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.4.2/css/bulma.min.css"
/>
</head>
<body>
<div id="app">
<header id="site-header" class="nav is-fixed">
<div class="container">
<scrollactive
ref="scrollactive"
:offset="offset"
:always-track="alwaysTrack"
:duration="duration"
:click-to-scroll="clickToScroll"
:bezier-easing-value="easing"
>
<ul class="nav-center">
<li>
<a href="#section-1" class="scrollactive-item nav-item">Section 1</a>
</li>
<li>
<a href="#section-2" class="scrollactive-item nav-item">Section 2</a>
</li>
<li>
<a href="#section-3" class="scrollactive-item nav-item">Section 3</a>
</li>
<li>
<a href="#section-4" class="scrollactive-item nav-item">Section 4</a>
</li>
</ul>
</scrollactive>
</div>
</header>
<div class="buttons">
<button @click="addNewElement()">Add new element</button>
<button @click="removeElement()">Remove last element</button>
<button
@click="alwaysTrack = !alwaysTrack"
v-text="'Always track ' + (alwaysTrack ? 'on' : 'off')"
></button>
<button
@click="clickToScroll = !clickToScroll"
v-text="'Click to scroll ' + (clickToScroll ? 'on' : 'off')"
></button>
<label for="duration">Duration</label>
<input type="number" v-model="duration" id="duration" />
<label for="offset">Offset</label>
<input type="number" v-model="offset" id="offset" />
<label for="easing">Easing (cubic-bezier format)</label>
<input type="text" v-model="easing" id="easing" />
</div>
<main>
<section id="section-1" class="section hero is-primary is-fullheight">
<div class="container">
<h1 class="heading title is-1">Section 1</h1>
</div>
</section>
<section id="section-2" class="section hero is-info is-fullheight">
<div class="container">
<h1 class="heading title is-1">Section 2</h1>
</div>
</section>
<section id="section-3" class="section hero is-danger is-fullheight">
<div class="container">
<h1 class="heading title is-1">Section 3</h1>
</div>
</section>
<section id="section-4" class="section hero is-success is-fullheight">
<div class="container">
<h1 class="heading title is-1">Section 4</h1>
</div>
</section>
</main>
</div>
<script src="https://unpkg.com/vue"></script>
</body>
</html>
================================================
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 = `<a href="#section-${sectionNumber}" class="scrollactive-item nav-item">Section ${sectionNumber}</a>`;
document.querySelector('.nav-center').appendChild(menuItem.firstChild);
const section = document.createElement('div');
section.innerHTML = `<section id="section-${sectionNumber}" class="section hero ${colorClass} is-fullheight">
<div class="container">
<h1 class="heading title is-1">Section ${sectionNumber}</h1>
</div>
</section>
`;
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
================================================
<script>
import bezierEasing from 'bezier-easing';
import { ScrollContainer } from './ScrollContainer';
import {
forEach,
find,
getIdFromHash,
pushHashToUrl,
getSectionSelector,
getSectionIdFromElement,
} from './utils';
export default {
props: {
/**
* 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,
},
},
data() {
return {
observer: null,
items: [],
currentItem: null,
lastActiveItem: null,
scrollAnimationFrame: null,
bezierEasing,
};
},
computed: {
/**
* Computes the bezier easing string value into an array.
*/
cubicBezierArray() {
return this.bezierEasingValue.split(',');
},
scrollContainer() {
return new ScrollContainer(this.scrollContainerSelector);
},
},
mounted() {
this.resetOnDOMChange();
this.initScrollactiveItems();
this.removeActiveClass();
this.currentItem = this.getItemInsideWindow();
if (this.currentItem) this.currentItem.classList.add(this.activeClass);
if (this.scrollOnStart) this.scrollToHashElement();
this.scrollContainer.addScrollListener(this.onScroll);
},
updated() {
this.initScrollactiveItems();
},
beforeDestroy() {
this.scrollContainer.removeScrollListener();
window.cancelAnimationFrame(this.scrollAnimationFrame);
},
methods: {
/**
* Makes sure the component is recalculated whenever the DOM tree changes inside of the
* scrollactive wrapper.
*/
resetOnDOMChange() {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (!this.observer) {
this.observer = new MutationObserver(this.initScrollactiveItems);
// Calls this.initScrollactiveItems() whenever the DOM tree is changed inside of the wrapper
this.observer.observe(this.$refs['scrollactive-nav-wrapper'], {
childList: true,
subtree: true,
});
}
},
/**
* Sets the list of menu items, adding or removing the click listener depending on the
* clickToScroll prop.
*/
initScrollactiveItems() {
const elements = this.$el.querySelectorAll('.scrollactive-item');
const items = [];
forEach(elements, (menuElement) => {
const section = document.querySelector(getSectionSelector(menuElement));
if (!section) return;
items.push({ section, menuElement });
});
this.items = items;
if (this.clickToScroll) {
forEach(elements, (element) => element.addEventListener('click', this.onMenuItemClick));
} else {
forEach(elements, (element) => element.removeEventListener('click', this.onMenuItemClick));
}
},
/**
* Will be called when scrolling event is triggered to handle the addition of the active class
* in the current section item and fire the change event.
*/
onScroll(event) {
this.currentItem = this.getItemInsideWindow();
const sectionHasChanged = this.currentItem !== this.lastActiveItem;
if (sectionHasChanged) {
this.removeActiveClass();
this.$emit('itemchanged', event, this.currentItem, this.lastActiveItem);
this.lastActiveItem = this.currentItem;
}
// Check first because item might be null if not inside any section
if (this.currentItem) this.currentItem.classList.add(this.activeClass);
},
/**
* Gets the scrollactive item that corresponds to the current section inside the window
*/
getItemInsideWindow() {
let currentItem;
forEach(this.items, ({ menuElement, section }, index) => {
const isFirstItem = index === 0;
const distanceFromTop = this.scrollContainer.getDistanceFromTop();
const targetOffsetTop = this.getOffsetTop(section) - this.offset;
const isScreenPastSectionStart = distanceFromTop >= targetOffsetTop;
const isScreenBeforeSectionEnd = distanceFromTop < targetOffsetTop + section.offsetHeight;
const isScreenInsideSection = isScreenPastSectionStart && isScreenBeforeSectionEnd;
if (isFirstItem && this.highlightFirstItem) {
if (isScreenBeforeSectionEnd) currentItem = menuElement;
}
if (this.exact && isScreenInsideSection) currentItem = menuElement;
if (!this.exact && isScreenPastSectionStart) currentItem = menuElement;
});
return currentItem;
},
/**
* Handles the scrolling when clicking a menu item.
*/
async onMenuItemClick(event) {
event.preventDefault();
const menuItem = event.target;
const sectionSelector = getSectionSelector(menuItem);
const section = document.querySelector(sectionSelector);
if (!section) {
console.warn(
`[vue-scrollactive] Element '${sectionSelector}' was not found. Make sure it is set in the DOM.`
);
return;
}
/**
* Temporarily removes the scroll listener and the request animation frame so the active
* class will only be applied to the clicked element, and not all elements while the window
* is scrolling.
*/
if (!this.alwaysTrack) {
this.scrollContainer.removeScrollListener();
window.cancelAnimationFrame(this.scrollAnimationFrame);
this.removeActiveClass();
menuItem.classList.add(this.activeClass);
}
await this.scrollTo(section);
if (!this.alwaysTrack) {
this.scrollContainer.addScrollListener(this.onScroll);
this.currentItem = menuItem;
if (this.currentItem !== this.lastActiveItem) {
this.$emit('itemchanged', event, this.currentItem, this.lastActiveItem);
this.lastActiveItem = this.currentItem;
}
}
if (this.modifyUrl) {
pushHashToUrl(getSectionIdFromElement(menuItem));
}
},
/**
* Scrolls the page to the given target element.
*/
scrollTo(target) {
return new Promise((resolve) => {
const targetDistanceFromTop = this.getOffsetTop(target);
const startingY = this.scrollContainer.getDistanceFromTop();
const distanceFromTarget = targetDistanceFromTop - startingY;
const easing = this.bezierEasing(...this.cubicBezierArray);
let startingTime = null;
const step = (currentTime) => {
if (!startingTime) startingTime = currentTime;
let progress = currentTime - startingTime;
let progressPercentage = progress / this.duration;
if (progress >= this.duration) progress = this.duration;
if (progressPercentage >= 1) progressPercentage = 1;
const offset = this.scrollOffset || this.offset;
const perTick = startingY + easing(progressPercentage) * (distanceFromTarget - offset);
this.scrollContainer.scrollTo(0, perTick);
if (progress < this.duration) {
this.scrollAnimationFrame = window.requestAnimationFrame(step);
} else {
resolve();
}
};
window.requestAnimationFrame(step);
});
},
/**
* Gets the top offset position of an element in the document.
*/
getOffsetTop(element) {
let yPosition = 0;
let nextElement = element;
while (nextElement) {
yPosition += nextElement.offsetTop;
nextElement = nextElement.offsetParent;
}
if (this.scrollContainer.getOffsetTop()) {
yPosition -= this.scrollContainer.getOffsetTop();
}
return yPosition;
},
/**
* Removes the active class from all scrollactive items.
*/
removeActiveClass() {
// Must be called with 'call' to prevent bugs on some devices
forEach(this.items, ({ menuElement }) => {
menuElement.classList.remove(this.activeClass);
});
},
/**
* Scrolls the page to the element passed as a hash in URL, preventing weird native scroll
* jumps while maintaining the hash in the URL.
*/
scrollToHashElement() {
const { hash } = window.location;
if (!hash) return;
const hashElement = document.getElementById(getIdFromHash(hash));
if (!hashElement) return;
window.location.hash = ''; // Clears the hash to prevent scroll from jumping
setTimeout(() => {
const yPos = hashElement.offsetTop - this.offset;
this.scrollContainer.scrollTo(0, yPos);
// Sets the hash back with pushState so it won't jump to the element ignoring the offset
pushHashToUrl(hash);
}, 0);
},
},
};
</script>
<template>
<component :is="tag" ref="scrollactive-nav-wrapper" class="scrollactive-nav">
<slot />
</component>
</template>
================================================
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');
}
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
SYMBOL INDEX (28 symbols across 4 files)
FILE: dist/main.js
function n (line 3) | function n(o) {
function s (line 78) | function s(e) {
function l (line 86) | function l(e, t) {
function c (line 102) | function c(e) {
function d (line 132) | function d(e, t, n, o) {
function m (line 141) | function m(e, t, n) {
function v (line 163) | function v(e, t) {
function o (line 253) | function o(e, t) {
function r (line 256) | function r(e, t) {
function i (line 259) | function i(e) {
function a (line 262) | function a(e, t, n) {
function s (line 265) | function s(e, t, n) {
function l (line 268) | function l(e) {
function u (line 277) | function u(t) {
function a (line 355) | function a(e) {
function s (line 379) | function s(e, t) {
FILE: src/ScrollContainer.js
class ScrollContainer (line 1) | class ScrollContainer {
method constructor (line 2) | constructor(containerSelector) {
method addScrollListener (line 12) | addScrollListener(callback) {
method removeScrollListener (line 17) | removeScrollListener() {
method getDistanceFromTop (line 21) | getDistanceFromTop() {
method scrollTo (line 25) | scrollTo(x, y) {
method getOffsetTop (line 29) | getOffsetTop() {
FILE: src/sandbox/sandbox.js
method numberOfElements (line 18) | numberOfElements() {
method mounted (line 22) | mounted() {
method addNewElement (line 26) | addNewElement() {
method removeElement (line 43) | removeElement() {
FILE: webpack.config.js
constant TARGET (line 1) | const TARGET = process.env.TARGET;
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (76K chars).
[
{
"path": ".babelrc",
"chars": 154,
"preview": "{\n \"presets\": [\n [\n \"@babel/env\",\n {\n \"modules\": false\n }\n ]\n ],\n \"plugins\": [\n \"@babe"
},
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_"
},
{
"path": ".eslintrc.json",
"chars": 188,
"preview": "{\n \"extends\": [\"eslint:recommended\"],\n \"parserOptions\": {\n \"sourceType\": \"module\"\n },\n \"env\": {\n \"browser\": tr"
},
{
"path": ".gitignore",
"chars": 2682,
"preview": "\n# Created by https://www.gitignore.io/api/node,sass,macos,sublimetext,visualstudiocode\n\n### macOS ###\n# General\n.DS_Sto"
},
{
"path": ".prettierrc",
"chars": 106,
"preview": "{\n \"trailingComma\": \"es5\",\n \"tabWidth\": 2,\n \"semi\": true,\n \"singleQuote\": true,\n \"printWidth\": 100\n}\n"
},
{
"path": ".webpack/webpack.config.dev.js",
"chars": 947,
"preview": "const path = require('path');\nconst VueLoaderPlugin = require('vue-loader/lib/plugin');\nconst HtmlWebpackPlugin = requir"
},
{
"path": ".webpack/webpack.config.prod.js",
"chars": 772,
"preview": "const path = require('path');\nconst VueLoaderPlugin = require('vue-loader/lib/plugin');\n\nmodule.exports = {\n mode: 'pro"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "Copyright (c) 2017 Mauricio Dziedzinski\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of"
},
{
"path": "README.md",
"chars": 7311,
"preview": "# vue-scrollactive\n\nThis component makes it simple to highlight a menu item with an 'active' class as you scroll.\n\n- Hig"
},
{
"path": "dist/index.html",
"chars": 2747,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Scrollactive!</title>\n <link\n "
},
{
"path": "dist/main.js",
"chars": 25681,
"preview": "!(function (e) {\n var t = {};\n function n(o) {\n if (t[o]) return t[o].exports;\n var r = (t[o] = { i: o, l: !1, e"
},
{
"path": "package.json",
"chars": 1753,
"preview": "{\n \"name\": \"vue-scrollactive\",\n \"version\": \"0.9.3\",\n \"title\": \"Vue Scrollactive\",\n \"description\": \"Lightweight and s"
},
{
"path": "src/ScrollContainer.js",
"chars": 713,
"preview": "export class ScrollContainer {\n constructor(containerSelector) {\n let container = window;\n\n if (containerSelector"
},
{
"path": "src/index.js",
"chars": 295,
"preview": "import Scrollactive from './scrollactive.vue';\n\nconst Plugin = {};\n\nPlugin.install = (Vue) => {\n if (Plugin.install.ins"
},
{
"path": "src/sandbox/index.html",
"chars": 2972,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Scrollactive!</title>\n <link\n "
},
{
"path": "src/sandbox/normalize.scss",
"chars": 7725,
"preview": "/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ==========================="
},
{
"path": "src/sandbox/sandbox.js",
"chars": 1842,
"preview": "import Scrollactive from '../index';\nimport './normalize.scss';\nimport './sandbox.scss';\n\nVue.use(Scrollactive);\n\nconst "
},
{
"path": "src/sandbox/sandbox.scss",
"chars": 793,
"preview": ".nav.is-fixed {\n position: fixed;\n left: 0;\n right: 0;\n\n .is-active {\n color: #00d1b2;\n }\n}\n\n.section {\n paddin"
},
{
"path": "src/scrollactive.vue",
"chars": 12144,
"preview": "<script>\nimport bezierEasing from 'bezier-easing';\nimport { ScrollContainer } from './ScrollContainer';\nimport {\n forEa"
},
{
"path": "src/utils/find.js",
"chars": 132,
"preview": "// Must be called with 'call' to prevent bugs on some devices\nexport const find = (list, callback) => [].find.call(list,"
},
{
"path": "src/utils/forEach.js",
"chars": 138,
"preview": "// Must be called with 'call' to prevent bugs on some devices\nexport const forEach = (list, callback) => [].forEach.call"
},
{
"path": "src/utils/getIdFromHash.js",
"chars": 114,
"preview": "// Decoded in case there are special characters\nexport const getIdFromHash = (hash) => decodeURI(hash.substr(1));\n"
},
{
"path": "src/utils/getSectionIdFromElement.js",
"chars": 226,
"preview": "export const getSectionIdFromElement = (element) => {\n if (element.dataset.sectionSelector && element.dataset.sectionSe"
},
{
"path": "src/utils/getSectionSelector.js",
"chars": 257,
"preview": "import { getIdFromHash } from './getIdFromHash';\n\nexport const getSectionSelector = (element) => {\n if (element.dataset"
},
{
"path": "src/utils/index.js",
"chars": 294,
"preview": "export { forEach } from './forEach';\nexport { find } from './find';\nexport { getIdFromHash } from './getIdFromHash';\nexp"
},
{
"path": "src/utils/pushHashToUrl.js",
"chars": 413,
"preview": "/**\n * Pushes the given hash to the URL using primarily pushState if available to prevent the\n * scroll from jumping to "
},
{
"path": "webpack.config.js",
"chars": 201,
"preview": "const TARGET = process.env.TARGET;\n\nif (TARGET === 'production') {\n module.exports = require('./.webpack/webpack.config"
}
]
About this extraction
This page contains the full source code of the eddiemf/vue-scrollactive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (70.1 KB), approximately 18.8k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.