How it works
The v-b-scrollspy
directive has a few requirements to function properly:
- It must be applied on the element/component containing the
<b-nav>
or <b-list-group>
component(s) where you want to indicate which link is currently active. - Scrollspy requires
position: relative;
on the element you're spying on, usually the <body>
. - When spying on elements other than the
<body>
, be sure to have a height
set and overflow-y: scroll;
applied. - Anchors (
<a>
, <b-nav-item>
, <b-dropdown-item>
, <b-list-group-item>
) are required and must have an href
(either via the href
or to
props) that points to an element with that id
in the container you are spying on. When using the to
prop, either set the path
ending with #id-of-element
, or set the location property hash
to #id-of-element
.
When successfully implemented, your nav or list group will update accordingly, moving the active
state from one item to the next based on their associated targets.
Example using navs
Using v-b-scrollspy
on a <b-nav>
component to monitor the scrolling on <b-card-body>
. Scroll the area below the navbar and watch the active class change. The dropdown items will be highlighted as well.
<template>
<div>
<b-card no-body>
<b-nav pills card-header slot="header" v-b-scrollspy:nav-scroller>
<b-nav-item href="#fat" @click="scrollIntoView">@fat</b-nav-item>
<b-nav-item href="#mdo" @click="scrollIntoView">@mdo</b-nav-item>
<b-nav-item-dropdown text="Dropdown 1,2,3" right-alignment>
<b-dropdown-item href="#one" @click="scrollIntoView">one</b-dropdown-item>
<b-dropdown-item href="#two" @click="scrollIntoView">two</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
<b-dropdown-item href="#three" @click="scrollIntoView">three</b-dropdown-item>
</b-nav-item-dropdown>
<b-nav-item href="#pi0" @click="scrollIntoView">@pi0</b-nav-item>
</b-nav>
<b-card-body
id="nav-scroller"
ref="content"
style="position:relative; height:300px; overflow-y:scroll;"
>
<p>{{ text }}</p>
<h4 id="fat">@fat</h4>
<p v-for="i in 3">{{ text }}</p>
<h4 id="mdo">@mdo</h4>
<p v-for="i in 3">{{ text }}</p>
<h4 id="one">one</h4>
<p v-for="i in 2">{{ text }}</p>
<h4 id="two">two</h4>
<p>{{ text }}</p>
<h4 id="three">three</h4>
<p v-for="i in 2">{{ text }}</p>
<h4 id="pi0">@pi0</h4>
<p v-for="i in 3">{{ text }}</p>
</b-card-body>
</b-card>
</div>
</template>
<script>
export default {
methods: {
scrollIntoView(event) {
event.preventDefault()
const href = event.target.getAttribute('href')
const el = href ? document.querySelector(href) : null
if (el) {
this.$refs.content.scrollTop = el.offsetTop
}
}
},
data() {
return {
text: `
Quis magna Lorem anim amet ipsum do mollit sit cillum voluptate ex nulla
tempor. Laborum consequat non elit enim exercitation cillum aliqua
consequat id aliqua. Esse ex consectetur mollit voluptate est in duis
laboris ad sit ipsum anim Lorem. Incididunt veniam velit elit elit veniam
Lorem aliqua quis ullamco deserunt sit enim elit aliqua esse irure. Laborum
nisi sit est tempor laborum mollit labore officia laborum excepteur
commodo non commodo dolor excepteur commodo. Ipsum fugiat ex est consectetur
ipsum commodo tempor sunt in proident.
`
}
}
}
</script>
Example using nested navs
Scrollspy also works with nested <b-nav>
. If a nested <b-nav-item>
is active, its parent()s will also be active. Scroll the area next to the navbar and watch the active class change.
<template>
<b-container fluid>
<b-row>
<b-col cols="4">
<b-navbar v-b-scrollspy:scrollspy-nested class="flex-column">
<b-navbar-brand href="#">Navbar</b-navbar-brand>
<b-nav pills vertical>
<b-nav-item href="#item-1">Item 1</b-nav-item>
<b-nav pills vertical>
<b-nav-item class="ml-3 my-1" href="#item-1-1">Item 1-1</b-nav-item>
<b-nav-item class="ml-3 my-1" href="#item-1-2">Item 1-2</b-nav-item>
</b-nav>
<b-nav-item href="#item-2">Item 2</b-nav-item>
<b-nav-item href="#item-3">Item 3</b-nav-item>
<b-nav pills vertical>
<b-nav-item class="ml-3 my-1" href="#item-3-1">Item 3-1</b-nav-item>
<b-nav-item class="ml-3 my-1" href="#item-3-2">Item 3-2</b-nav-item>
</b-nav>
</b-nav>
</b-navbar>
</b-col>
<b-col cols="8">
<div id="scrollspy-nested" style="position:relative; height:350px; overflow-y:auto">
<h4 id="item-1" style="">Item 1</h4>
<p>{{ text }}</p>
<h5 id="item-1-1" style="">Item 1-1</h5>
<p>{{ text }}</p>
<h5 id="item-1-2" style="">Item 2-2</h5>
<p>{{ text }}</p>
<h4 id="item-2" style="">Item 2</h4>
<p>{{ text }}</p>
<h4 id="item-3" style="">Item 3</h4>
<p>{{ text }}</p>
<h5 id="item-3-1" style="">Item 3-1</h5>
<p>{{ text }}</p>
<h5 id="item-3-2" style="">Item 3-2</h5>
<p>{{ text }}</p>
</div>
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
data() {
return {
text: `
Quis magna Lorem anim amet ipsum do mollit sit cillum voluptate ex nulla
tempor. Laborum consequat non elit enim exercitation cillum aliqua
consequat id aliqua. Esse ex consectetur mollit voluptate est in duis
laboris ad sit ipsum anim Lorem. Incididunt veniam velit elit elit veniam
Lorem aliqua quis ullamco deserunt sit enim elit aliqua esse irure. Laborum
nisi sit est tempor laborum mollit labore officia laborum excepteur
commodo non commodo dolor excepteur commodo. Ipsum fugiat ex est consectetur
ipsum commodo tempor sunt in proident.
`
}
}
}
</script>
Example using list group
Scrollspy also works with <b-list-group>
when it contains <b-list-group-item>
s that have a local href
or to
. Scroll the area next to the list group and watch the active state change.
<template>
<b-container fluid>
<b-row>
<b-col cols="4">
<b-list-group v-b-scrollspy:listgroup-ex>
<b-list-group-item href="#list-item-1">Item 1</b-list-group-item>
<b-list-group-item href="#list-item-2">Item2</b-list-group-item>
<b-list-group-item href="#list-item-3">Item 3</b-list-group-item>
<b-list-group-item href="#list-item-4">Item 4</b-list-group-item>
<b-list-group-item href="#list-item-5">Item 5</b-list-group-item>
</b-list-group>
</b-col>
<b-col cols="8">
<div id="listgroup-ex" style="position:relative; overflow-y:auto; height:300px">
<h4 id="list-item-1">Item 1</h4>
<p>{{ text }}</p>
<h4 id="list-item-2">Item 2</h4>
<p>{{ text }}</p>
<h4 id="list-item-3">Item 3</h4>
<p>{{ text }}</p>
<h4 id="list-item-4">Item 4</h4>
<p>{{ text }}</p>
<h4 id="list-item-5">Item 5</h4>
<p>{{ text }}</p>
</div>
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
data() {
return {
text: `
Quis magna Lorem anim amet ipsum do mollit sit cillum voluptate ex nulla
tempor. Laborum consequat non elit enim exercitation cillum aliqua
consequat id aliqua. Esse ex consectetur mollit voluptate est in duis
laboris ad sit ipsum anim Lorem. Incididunt veniam velit elit elit veniam
Lorem aliqua quis ullamco deserunt sit enim elit aliqua esse irure. Laborum
nisi sit est tempor laborum mollit labore officia laborum excepteur
commodo non commodo dolor excepteur commodo. Ipsum fugiat ex est consectetur
ipsum commodo tempor sunt in proident.
`
}
}
}
</script>
When Vue Router (or Nuxt.js) is used, and you are generating your links with the to
prop, use one of the following methods to generate the appropriate href
on the rendered link:
<b-nav-item to="#id-of-element">link text</b-nav-item>
<b-nav-item :to="{ hash: '#id-of-element' }">link text</b-nav-item>
Scrollspy works with both history
and hash
routing modes, as long as the generated URL ends with #id-of-element
.
Directive syntax and usage
v-b-scrollspy:arg.mod1.mod2="option"
Where:
arg
is the ID (minus the #
) of the element to monitor scrolling on. Optional (defaults to body
. Can be overridden by option
) mod1
& mod2
can be an offset
number or string method
(see config object below). Order of the modifiers is not important. Both are optional option
can be a string identifying the element
to monitor scrolling on, a numeric offset
, or a configuration object (see below). Optional
Note: The directive is applied backwards compared to native Bootstrap v4. In BootstrapVue the v-b-scrollspy
directive is applied to the target element that has the links to be activated, and the arg or option specifies which element to monitor (spy) scrolling on.
The directive an be applied to any containing element or component that has <nav-item>
, <b-dropdown-item>
, <b-list-group-item>
(or <a>
tags with the appropriate classes), a long as they have rendered href
attributes that point to elements with the respective id
s in the scrolling element.
Config object properties
const config = {
element: 'body',
offset: 10,
method: 'auto',
throttle: 100
}
Property | Type | Default | Description |
element | String or Reference | 'body' | Element to be monitored for scrolling. Can be an ID (#foo ), a css Selector (#foo div ), or a reference to an element/component node. If a CSS string, then the first matching element is used. If an ID is used it must start with # . |
offset | Number | 10 | offset (in pixels) from top of scrolling viewport before triggering active state. |
method | String | 'auto' | position will calculate target offsets relative to the scroll container. offset will calculate the target offsets relative to the top of the window/viewport. auto will choose offset if scroll element is body , else the method is position . |
throttle | Number | 100 | Timeout in ms for resize events to stop firing before recalculating offsets. |
If args/modifiers and a value (object or number) is passed, the value takes precedence over the arg and modifiers.
If any of the options are invalid types, then an error is written to the console.
Config notes
- If scroll element is not present, then we assume scrolling on
<body>
- If scroll element is a CSS selector, the first found element is chosen
- If scroll element is not found, then ScrollSpy silently does nothing
Important! Requires relative positioning No matter the implementation method, scrollspy requires the use of position: relative;
on the element you're scrolling on. In most cases this is the <body>
. When scrollspying on elements other than the <body>
, be sure to have a CSS height
set and overflow-y: scroll;
applied.
Directive use examples
Assume <body>
is the scroll element, and use default offset of 10 pixels
<div>
<b-nav v-b-scrollspy>
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Assume <body>
is the scroll element, and use offset of 20 pixels
<div>
<b-nav v-b-scrollspy="20">
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Element with ID #foo
is the scroll element, and use default offset of 10 pixels
<div>
<b-nav v-b-scrollspy:foo>
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Element #foo
is the scroll element, and use offset of 20 pixels
<div>
<b-nav v-b-scrollspy:foo="20">
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Element #foo
is the scroll element, and use offset of 25 pixels
<div>
<b-nav v-b-scrollspy:foo.25>
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Element #foo
is the scroll element, and use default offset of 10 pixels (note single quotes around value)
<div>
<b-nav v-b-scrollspy="'#foo'">
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Pass object as config. element
can be a CSS ID (i.e #foo
), a CSS selector (i.e. body
), or a node reference
<div>
<b-nav v-b-scrollspy="{element: '#id', offset: 50}">
<b-nav-item href="#bar">Foo</b-nav-item>
<b-nav-item href="#baz">Bar</b-nav-item>
</b-nav>
</div>
Events
Whenever a target is activated, the event bv:scrollspy::activate
is emitted on $root
with the target's ID as the argument (i.e. #bar
)
const app = new Vue({
el: '#app',
created() {
this.$root.$on('bv::scrollspy::activate', this.onActivate)
},
methods: {
onActivate(target) {
console.log('Received event: "bv::scrollspy::activate" for target ', target)
}
}
})