0

I am working on a blog layout which have its content on the left side and a 'table of contents' sidebar on the right. The sidebar is fixed with a full viewport height but scrollable incase the items inside are too many.

It is also worth noting that the active state changes on the links in the sidebar based on their corresponding content in the viewport. What I mean to say is that whenever a blog post is in the viewport, its link gets active state in the sidebar. And it is done by Intersection Observer API.

Now the core functionality works fine except there is one issue.

When there are a lot of blog posts, there will be a lot of links on the sidebar. So naturally the bottom links are not visible on the sidebar as they are at the bottom and can't be seen until the scrollbar is pulled down. Therefore, the active state is not visible as well.

Suppose a user tries to read blog post Text 8, the corresponding Text 8 link should've been visible on the sidebar but it is not. Only upto Text 7 is visible in the demo (based on my viewport).

What I wanted to achieve is how can I move the scrollbar up and down based on which blog posts the user is reading? I mean if the user is reading Text 8 then the sidebar will scroll downward and show Text 8 link. If he is reading Text 9, it will scroll to Text 9 link. Now if he decides to read Text 7 from Text 9 then the scrollbar will move upward two places and display Text 7 link.

I don't know if I could explain it properly but this is the best I could write.

It would be a great help if you could help me with this.

Here's the codepen.

Here's the snippet:

const asideContent = $('#aside-content .aside-content');
const asideContentItem = $('#aside-content a');

const callback = (entries, observer) => {
  $(entries).each((idx, item) => {
    const navItem = $('#' + item.target.id);

    if (item.isIntersecting) {
      $(asideContentItem).each((i, eachLink) => {
        if ($(eachLink).attr("href") === ('#' + $(navItem).attr('id'))) {
          $(eachLink).addClass('active');
        } else {
          $(eachLink).removeClass('active');
        }
      })
    }
  })
};

const options = {
  threshold: 0.2
};

const observer = new IntersectionObserver(callback, options);
const container = $('#main');
const targetElements = $('.main-content .inner-container');

$(targetElements).each((idx, item) => {
  observer.observe(item);
});
.header-content {
  background-color: blue;
  height: 800px;
  margin: 20px 0;
}

#main {
  margin: 20px auto;
}

.main-content {
  background-color: cyan;
}

#aside-content {
  position: sticky;
  top: 0;
  align-self: flex-start;
  background-color: red;
  height: 100vh;
  overflow-y: auto;
}

#aside-content a {
  display: block;
  margin: 40px auto;
  text-decoration: none;
}

#aside-content a.active {
  background-color: rgba(0, 255, 0, 0.5);
}

.aside-fixed {
  position: fixed;
  top: 0;
  right: 0;
}

footer {
  background-color: brown;
  height: 200px;
}
<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/litera/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="#">Test</a>
</nav>


<section class="container">
  <div class="row">
    <div class="col-md-12">
      <div class="header-content">
        <h3>header content</h3>
      </div>
    </div>
  </div>
</section>

<section class="container" id="main">
  <div class="row">

    <div class="col-8">
      <div class="main-content">
        <div id="text1" class="inner-container">
          <h2>Text 1</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit.
        </div>

        <div id="text2" class="inner-container">
          <h2>Text 2</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harumti aspernatur delectus mollitia libero similique assumenda quos sequi eligendi?
        </div>

        <div id="text3" class="inner-container">
          <h2>Text 3</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi, maxime repellat aperiam labore exercitationem enim possimus. Suscipit facilis debitis quidem excepturi?
        </div>

        <div id="text4" class="inner-container">
          <h2>Text 4</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text5" class="inner-container">
          <h2>Text 5</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text6" class="inner-container">
          <h2>Text 6</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text7" class="inner-container">
          <h2>Text 7</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text8" class="inner-container">
          <h2>Text 8</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text9" class="inner-container">
          <h2>Text 9</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>
      </div>
    </div>

    <div class="col-4" id="aside-content">
      <div class="aside-content">
        <h3>Table of Contents</h3>
        <div>
          <a href="#text1">Text 1</a>
          <a href="#text2">Text 2</a>
          <a href="#text3">Text 3</a>
          <a href="#text4">Text 4</a>
          <a href="#text5">Text 5</a>
          <a href="#text6">Text 6</a>
          <a href="#text7">Text 7</a>
          <a href="#text8">Text 8</a>
          <a href="#text9">Text 9</a>
        </div>
      </div>

    </div>
</section>

<footer>
  <h3>footer</h3>
</footer>
Zak
  • 860
  • 16
  • 39

1 Answers1

1

You can solve this by setting the scrollbar position to the specific offset as the active class changes. This could be achieved by editing your JS and adding the scrolling logic to the add addClass('active') section.

Basically you have to update the scrollbar position as the active class is added to any link.

document.querySelector("#aside-content").scrollTo(0,position)

And the position to which the scrollbar has to be set, can be fetched from the current active element's/link's offset.

document.querySelector("#aside-content > div > div > a.active").offsetTop

Therefore just after adding class the above stated steps can be done

$(eachLink).addClass('active');

var position = document.querySelector("#aside-content > div > div > a.active").offsetTop;

document.querySelector("#aside-content").scrollTo(0,position);

But, the above code will always set the position, and due to which at first link will set the scrolling to itself and not let "table of contents" text to be visible ever. To fix this situation, updating logic can be made conditional by checking if the active element is first child of the scrolling div.

So, the overall working code should be as follow:

const asideContent = $('#aside-content .aside-content');
const asideContentItem = $('#aside-content a');

const callback = (entries, observer) => {
  $(entries).each((idx, item) => {
    const navItem = $('#' + item.target.id);

    if (item.isIntersecting) {
      $(asideContentItem).each((i, eachLink) => {
        if ($(eachLink).attr("href") === ('#' + $(navItem).attr('id'))) {
          $(eachLink).addClass('active');

          if (document.querySelector("#aside-content > div > div > a.active").parentNode.firstElementChild != document.querySelector("#aside-content > div > div > a.active")) {
            document.querySelector("#aside-content").scrollTo(0, document.querySelector("#aside-content > div > div > a.active").offsetTop);
          } else {
            document.querySelector("#aside-content").scrollTo(0, 0);
          }


        } else {
          $(eachLink).removeClass('active');
        }
      })
    }
  })
};

const options = {
  threshold: 0.2
};

const observer = new IntersectionObserver(callback, options);
const container = $('#main');
const targetElements = $('.main-content .inner-container');

$(targetElements).each((idx, item) => {
  observer.observe(item);
});
.header-content {
  background-color: blue;
  height: 800px;
  margin: 20px 0;
}

#main {
  margin: 20px auto;
}

.main-content {
  background-color: cyan;
}

#aside-content {
  position: sticky;
  top: 0;
  align-self: flex-start;
  background-color: red;
  height: 100vh;
  overflow-y: auto;
}

#aside-content a {
  display: block;
  margin: 40px auto;
  text-decoration: none;
}

#aside-content a.active {
  background-color: rgba(0, 255, 0, 0.5);
}

.aside-fixed {
  position: fixed;
  top: 0;
  right: 0;
}

footer {
  background-color: brown;
  height: 200px;
}
<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/litera/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="#">Test</a>
</nav>


<section class="container">
  <div class="row">
    <div class="col-md-12">
      <div class="header-content">
        <h3>header content</h3>
      </div>
    </div>
  </div>
</section>

<section class="container" id="main">
  <div class="row">

    <div class="col-8">
      <div class="main-content">
        <div id="text1" class="inner-container">
          <h2>Text 1</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit.
        </div>

        <div id="text2" class="inner-container">
          <h2>Text 2</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harumti aspernatur delectus mollitia libero similique assumenda quos sequi eligendi?
        </div>

        <div id="text3" class="inner-container">
          <h2>Text 3</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi, maxime repellat aperiam labore exercitationem enim possimus. Suscipit facilis debitis quidem excepturi?
        </div>

        <div id="text4" class="inner-container">
          <h2>Text 4</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text5" class="inner-container">
          <h2>Text 5</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text6" class="inner-container">
          <h2>Text 6</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text7" class="inner-container">
          <h2>Text 7</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text8" class="inner-container">
          <h2>Text 8</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>

        <div id="text9" class="inner-container">
          <h2>Text 9</h2>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet provident recusandae, omnis laborum ad, pariatur debitis hic velit magni, expedita dicta vel nemo asperiores aperiam quia soluta esse consequuntur! Praesentium voluptate delectus, consequatur
          dolore veniam vitae sint error ea, facere nisi reprehenderit harum doloremque asperiores repudiandae eligendi.
        </div>
      </div>
    </div>

    <div class="col-4" id="aside-content">
      <div class="aside-content">
        <h3>Table of Contents</h3>
        <div>
          <a href="#text1">Text 1</a>
          <a href="#text2">Text 2</a>
          <a href="#text3">Text 3</a>
          <a href="#text4">Text 4</a>
          <a href="#text5">Text 5</a>
          <a href="#text6">Text 6</a>
          <a href="#text7">Text 7</a>
          <a href="#text8">Text 8</a>
          <a href="#text9">Text 9</a>
        </div>
      </div>

    </div>
</section>

<footer>
  <h3>footer</h3>
</footer>
Avinash Karhana
  • 659
  • 4
  • 16
  • Hey I think the ```else``` statement is not necessary in the conditional. I tried console.logging in both parts but the else statement never fires. – Zak Mar 27 '21 at 11:12
  • 1
    It will fire if current active element is the first element. (Try moving to Text 1 from any other Text Link, either via scrolling the paragraph or clicking Link) – Avinash Karhana Mar 28 '21 at 13:19
  • 1
    Thanks mate. Big help – Zak Mar 28 '21 at 14:09