Vue Sticky Directive
This package provides a Vue.js directive, `v-sticky`, designed to make elements "sticky" within their designated container, mimicking the CSS `position: sticky` behavior. It enables configurable sticky effects through various attributes such as `sticky-offset` (allowing top and bottom breakpoints), `sticky-side` (to specify sticking to the top, bottom, or both), and `sticky-z-index` for layer control. Additionally, it offers an `on-stick` callback function that notifies when an element's sticky state changes. The current version, 0.0.10, indicates it's an early-stage project. While its release cadence isn't explicitly defined, the versioning suggests a more cautious release cycle, with the last known commit in May 2021. A key differentiator is its straightforward, directive-based API for Vue 2 applications, providing a robust solution for contextual sticky elements that can be scoped to a specific `sticky-container` rather than just the viewport.
Common errors
-
Failed to resolve directive: sticky
cause The `v-sticky` directive has not been properly registered with the Vue instance, either globally via `Vue.use(Sticky)` or locally within a component's `directives` option.fixEnsure `import Sticky from 'vue-sticky-directive'; Vue.use(Sticky);` is called early in your application's entry point (e.g., `main.js`), or that `directives: { Sticky }` is included in the component options where `v-sticky` is used. -
TypeError: Cannot read properties of undefined (reading 'top') or similar errors related to sticky options.
cause `sticky-offset` or other options are being passed with incorrect types or formats, particularly when directly providing numbers instead of bound expressions or objects.fixEnsure `sticky-offset` is an object with `top` and/or `bottom` properties (e.g., `{ top: 0, bottom: 0 }`) and that it's bound using `v-bind` (shorthand `:`) if it's a dynamic value or expression, like `:sticky-offset="{ top: 50 }"`. -
The element with 'v-sticky' directive is not sticking as expected.
cause This often occurs because the `sticky-container` ancestor is not properly defined, has insufficient height, or does not have `overflow: auto` or `scroll` if it's meant to be a scrollable container. Additionally, CSS conflicts (e.g., `position: relative` on a parent overriding the sticky behavior) can prevent proper sticking.fixVerify that an ancestor element has the `sticky-container` attribute and that this container has a defined height and is scrollable if intended. Check for conflicting `position` CSS properties on parent elements that might interfere with the directive's functionality.
Warnings
- breaking This directive is designed for Vue 2 applications. It is not compatible with Vue 3 due to fundamental changes in the Vue 3 API, including directive registration and instance initialization methods. Direct usage in a Vue 3 project will result in errors.
- gotcha The sticky element must have a `sticky-container` parent (or nearest ancestor) to correctly define its relative boundaries. If no `sticky-container` is found, it will fallback to its direct parent, which might not be the desired scrollable area, leading to unexpected behavior where the element either doesn't stick or sticks to the wrong boundaries.
- gotcha When setting `sticky-offset`, direct numeric values (e.g., `sticky-offset="10"`) are not supported. It requires either a bound VM variable name (e.g., `:sticky-offset="myOffsetVar"`) or a JavaScript expression in object form (e.g., `:sticky-offset="{ top: 10, bottom: 20 }"`). Providing a simple number will be misinterpreted.
Install
-
npm install vue-sticky-directive -
yarn add vue-sticky-directive -
pnpm add vue-sticky-directive
Imports
- Sticky
import { Sticky } from 'vue-sticky-directive'import Sticky from 'vue-sticky-directive'
- Sticky (for local registration)
const Sticky = require('vue-sticky-directive')import Sticky from 'vue-sticky-directive'
- v-sticky
<div :sticky="{ top: 10 }"></div><div v-sticky sticky-offset="{ top: 10 }"></div>
Quickstart
<template>
<div id="app">
<header>
<h1>My Awesome Page</h1>
<p>Scroll down to see the sticky navigation!</p>
</header>
<div class="content-wrapper" sticky-container>
<nav v-sticky sticky-offset='{ top: 0 }' sticky-side="top" sticky-z-index="10" :on-stick="handleStickChange">
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
<li><a href="#section3">Section 3</a></li>
</ul>
</nav>
<main>
<section id="section1">
<h2>Section 1</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>...</p>
</section>
<section id="section2">
<h2>Section 2</h2>
<p>Another long section to demonstrate scrolling.</p>
<p>...</p>
</section>
<section id="section3">
<h2>Section 3</h2>
<p>The final section. Keep scrolling!</p>
<p>...</p>
</section>
</main>
</div>
<footer>
<p>Page footer</p>
</footer>
</div>
</template>
<script>
import Vue from 'vue';
import Sticky from 'vue-sticky-directive';
Vue.use(Sticky); // Register globally
export default {
name: 'App',
data() {
return {
isSticked: false
};
},
methods: {
handleStickChange(state) {
console.log('Sticky state changed:', state);
this.isSticked = state.sticked;
}
}
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
header, footer {
background: #f0f0f0;
padding: 20px;
text-align: center;
}
.content-wrapper {
display: flex;
min-height: 1500px; /* Make content scrollable */
}
nav {
width: 200px;
background: #e9e9e9;
padding: 15px;
}
nav.is-sticky {
background-color: #d0d0d0;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
main {
flex-grow: 1;
padding: 20px;
}
section {
min-height: 500px;
margin-bottom: 30px;
border-bottom: 1px solid #eee;
}
</style>