Switch Between Hyperlink And Scrollspy in Bootstrap And Vue 3

scrollspy bootstrap 5 and vue

Sometimes in the navbar, we have a link that scrolls to an element on the same page. But the problem comes when we navigate to another page. This tutorial will show how to solve this problem using Vue 3 Scrollspy and Bootstrap 5 without needing too much code or packages.

Creating Vue 3 components and Setup the example.

First of all I use vue-cli and vue-router, here is a tutorial on how to get started and install these dependencies.

How to Create Vuejs Website with Vue CLI And Vue Router

We will create a Navbar component src\components\NavBar.vue. And we add the below code

<template>
    <nav class="navbar  navbar-expand-lg navbar-dark py-0 bg-dark1 "  >
      <div class="container-xl">
        <router-link class="navbar-brand" to="/">My Website</router-link>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse main-nav-bar " id="navbarSupportedContent">
          <ul class="navbar-nav ms-auto me-auto mb-2 mb-lg-0 ">
            <li class="nav-item me-3">
              <router-link class="nav-link " to="/"> Home</router-link>
            </li>
            <li  class="nav-item mx-3">
                <router-link class="nav-link " to="#about"> About</router-link>
            </li>
            <li class="nav-item me-3">
              <router-link class="nav-link " to="/contact"> Contact</router-link>
            </li>
          </ul>
        </div>
      </div>
    </nav>
</template>


<style scoped>
.bg-dark1{
  background-color: rgba(11, 9, 65, 0.842);
  
}

</style>

This is The bootstrap navigation bar code and I add vue-router links as we usually do that we will be imported later into src\App.vue

Let’s create another component and name it HomeSection.vue for displaying a dummy text in two sections (head and about) so we can test the scrolling code.

<template>
  
<div class="container">
  <div class="my-5" id="head">
<h2>Head</h2>
  <p>
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </p>
  <p>
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </p>
  <p>
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </p>
</div>


<div class="my-5" id="about">
<h2>---------------- About ----------------</h2>
  <p>
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </p>
  <p>
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </p>
  <p>
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
  </p>
</div>
</div>




</template>

Below is the code of src\App.vue

<template>
  <div>
    <NavBar/>
    <router-view/>
  </div>
</template>


<script>
import NavBar from '@/components/NavBar.vue'
export default {
  components:{NavBar},
}

</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;
}

nav a {
  font-weight: bold;
  color: #2c3e50;
}

nav a.router-link-exact-active {
  color: #42b983;
}
</style>

By default vue-router creates the home page view in the views folder src\views\HomeView.vue.

We will import the HomeSection.vue inside it.

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HomeSection msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
// @ is an alias to /src
import HomeSection from '@/components/HomeSection.vue'

export default {
  name: 'HomeView',
  components: {
    HomeSection
  }
}
</script>

We need to add the contact page in the views folder as src\views\ContactView.vue

<template>
    <div class="mt-5">this is contact page</div>
</template>

Finally the routes. Let’s edit the route file src\router\index.js

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {

//check if the section name existed parameters
    path: '/:sectionName?',
    name: 'home',
    component: HomeView
  },
  {
    path: '/contact',
    name: 'contact',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/ContactView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

Creating Scrolling to the elements in vue3

Let’s modify the NavBar.vue to be as the following

<template>
    <nav class="navbar  navbar-expand-lg navbar-dark py-0 bg-dark1 "  >
      <div class="container-xl">
        <router-link class="navbar-brand" to="/">My Website</router-link>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse main-nav-bar " id="navbarSupportedContent">
          <ul class="navbar-nav ms-auto me-auto mb-2 mb-lg-0 ">
            <li class="nav-item me-3">
              <router-link class="nav-link" :class="$route.href == '/'? 'active':'deactivate'" to="/"> Home</router-link>
            </li>
            <li  class="nav-item mx-3">

//link if we are on home page
                <a v-if="$route.name == 'home'" :class="$route.href == '/#about'? 'active':''" class="nav-link " href="#about"> About</a>

//link if we are not on home 
                <a v-if="$route.name != 'home'" :class="$route.href == '/#about'? 'active':''" class="nav-link " href="/#about"> About</a>
            </li>
            <li class="nav-item me-3">
              <router-link class="nav-link " :class="$route.href == '/contact'? 'active':''" to="/contact"> Contact</router-link>
            </li>
          </ul>
        </div>
      </div>
    </nav>
</template>


<style scoped>
.bg-dark1{
  background-color: rgba(11, 9, 65, 0.842);
  
}
.deactivate{
  color:rgba(255, 255, 255, 0.55);
}

</style>

We add the :class for all links for toggling the active link. And we used $route.href instead of the class $route.name because we have parameter on the home routes.

{
    path: '/:sectionName?',
    name: 'home',
    component: HomeView
  },

We change the about link to <a> tag so that the bootstrap Scrollspy working.

We created a condition for the about link, If we are on the home page we use href="#about" else use href="/#about" so the page can reload and give us the Scrollspy effect after navigating to the home route.

Finally the active class for home page, We created deactivate class to override the default active class of a vue-router as about URL and home URL use the same route named home.