Let's build Vue directives: Scrolling elements into view
Tags: vue, javascript
Published: 2021-10-03 15:14:24

> TL: DR - take me to the code  
>   
> 1\. [Vue 2 Code Sandbox](https://codesandbox.io/s/v-scroll-vue-2-9rli3?file=/src/main.js)  
> 2\. [Vue 3 Code Sandbox](https://codesandbox.io/s/v-scroll-vue-2-9rli3?file=/src/main.js)  
> 3\. [Difference between Vue 2 and Vue 3 implementation](https://v3.vuejs.org/guide/migration/custom-directives.html#overview.)

It is one of those features that slowly but steadily found its way into most of the content-heavy pages, such as blogs or documentations. Scrolling elements into view gives your user an intuitive shortcut to the content they'd like to read about.

Reasons for a Vue directive
---------------------------

You might wonder - why would I need Javascript, even more, a Vue directive for this? I can use built-in HTML and CSS properties, such as `href="#headline"` in combination with `scroll: smooth`.

In fact, you can. If you don't mind a few limitations:

*   A `fixed` navbar might be in the way of your heading
*   You have to give every heading a fixed id or create it with Javascript
*   Globally applied smooth scrolling sometimes leads to some funny side effects when using SPA routers

You could arguably also use vanilla Javascript. Then again, the nature of Vue directives allows for a quick and reusable implementation.

> Please note that this article focuses on Vue 3, in which custom directives are handled slightly different than in Vue 2. Check out the linked code sandboxes in TL:DR for differences in `main.js` and the `vScroll.js` directive file.

Setup a Vue 3 Project with Vite
-------------------------------

We'll use [Vite](https://vitejs.dev/guide/) to spin up a basic application. You can alternatively use [Vue CLI](https://cli.vuejs.org/guide/).

Change into a directory of your choice and type:

```shell
# 1: Init your project
npm init vite@latest # using Vite with npm
# yarn create vite   # using Vite with yarn
# vue create .       # using Vue CLI

# 2: Change into the created folder and start the dev server
cd vite-project
npm install
npm run dev
```

Make the app object available for directive registration
--------------------------------------------------------

Before registering our custom directives, let's make a small adjustment in Vue's main file. It uses `createApp` on the fly, but we need the created app object to register components on.

> This step is optional, you could also chain `.directive()` as part of the createApp bootstrapping process.

```js
// Inside main.js: Change this 
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

// ------

// to 
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// ... register directives here

app.mount('#app')
```

With `app.directive(directiveName, directiveFunction)`, we're now able to register our own directives everywhere in the app.

Create the scroll directive
---------------------------

For this purpose, we require two browser functions, both of which are available in all major browsers.

*   `domElement.getBoundingClientRect()` to receive the element's coordinates
*   `window.scroll()` to scroll to the calculated part of the window

What's left for us to do is to:

*   calculate the difference between the current window position and the element (1)
*   make the window scroll to that calculated position (2)

So let's move ahead and create a folder in the `/src` folder named `/directives`. Inside it, create a file named `vScroll.js` and paste the following code into it:

```js
const vScroll = {
  mounted: (el) => {
    el.style.cursor = 'pointer';
    el.addEventListener('click', () => {
      const coord = el.getBoundingClientRect().top + window.scrollY; //(1)
      window.scroll({ top: coord, behavior: 'smooth' });  //(2)
    });
  },
};

export default vScroll;
```

Now let's go back into the `main.js` file and register `vScroll`:

```js
import { createApp } from 'vue'
import App from './App.vue'
import vScroll from './directives/vScroll'

const app = createApp(App)

app.directive('scroll', vScroll)

app.mount('#app')
```

Use the directive on the template
---------------------------------

Now what's left to do is to check whether the directive works as intended. Let's go ahead and replace the content of the `App.vue` file with a [Lorem Ipsum template](https://blog.q-bit.me/lorem-ipsum-template/).

Try adding `v-scroll` to any of the h2 - tags. Now, when clicking on them, they will be smoothly scrolled to the upper part of the window.

```html
<h2 v-scroll>Cursus mattis</h2>
```

What about the fixed navbar problem?
------------------------------------

The solution in a nutshell: You can bind values to directives! In this case to add a vertical offset when scrolling to an element. These bindings can be accessed the same way as the element itself, given they are passed as an argument in the directive's lifecycle function. In our case, `binding.value` will resolve to a number we want to subtract from the scroll-top position.

In the `vScroll.js` file:

```js
const vScroll = {
  mounted: (el, binding) => {
    el.style.cursor = 'pointer';
    el.addEventListener('click', () => {
      let coord = 0;
      coord = binding.value
        ? el.getBoundingClientRect().top + window.scrollY - binding.value
        : el.getBoundingClientRect().top + window.scrollY;
        window.scroll({ top: coord, behavior: 'smooth' });
    });
  },
};
```

In the HTML template:

```html
<h2 v-scroll="200">
  Pharetra magna
</h2>
<p>
  Congue quisque egestas diam in arcu cursus euismod quis viverra. 
  Pharetra magna ac placerat vestibulum lectus mauris ultrices.....
</p>
```

If everything went well, you now have a functional directive you can include in any new or existing project.

There's more to it
------------------

As you can see, we achieved this functionality fairly easily. You can move even further and extend the directive. For example, dynamically add an icon before the text or mutate the element's style whenever a user interacts with it. If you'd like to get an even deeper understanding of Vue directives, make sure to take a look into the official docs for Vue 3.

https://v3.vuejs.org/guide/custom-directive.html