User2419686
User2419686

Reputation: 78

jQuery position().top behaving the same as offset().top inside fixed parent

I was trying to make a position aware scroll bar that used the top value of a fixed div instead of the window.

The jQuery documentation describes the .position() function as follows,

The .position() method allows us to retrieve the current position of an element (specifically its margin box) relative to the offset parent

The .offset() method is describe as follows

Get the current coordinates of the first element, or set the coordinates of every element, in the set of matched elements, relative to the document.

What I am gathering here is that .position() should be relative to the parent, while offset() is always relative to the document.

I am trying to make my menu buttons high light relative to the current scrolled position in a fixed div, and not the window. However what I'm getting back from .position().top never seems to match up with the fixed div's .scrollTop()

Here is a fiddle with my problem in it. If I switch things to work relative to the window, everything works great.

The second I try to make it relative to the parent div, then it just goes haywire.

https://jsfiddle.net/cs83083/0kb5tm41/6/

For the fiddle adversed, here is the code!

Any insight is appreciated!

HTML

<div class="wrapper">
  <div class="menu-left">
    <div class="menu-item" data-target="sec1">
      Section 1
    </div>
    <div class="menu-item" data-target="sec2">
      Section 2
    </div>
    <div class="menu-item" data-target="sec3">
      Section 3
    </div>
    <div class="menu-item" data-target="sec4">
      Section 4
    </div>
  </div>

  <div class="page-content">
    <h2 class="header" id="sec1">Section 1</h2>
    <div class="text-block">
        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

    </div>

    <h2 class="header" id="sec2">Section 2</h2>
    <div class="text-block">
        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

    </div>

    <h2 class="header" id="sec3">Section 3</h2>
    <div class="text-block">
        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

    </div>

    <h2 class="header" id="sec4">Section 4</h2>
    <div class="text-block">

        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
    </div>
   </div>
 </div>

CSS

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
}

.wrapper {
  padding-left: 240px;
  display: block;
}

.menu-left {
  background-color: #CCC !important;
  height: 100%;
  display: block;
  width: 240px;
  margin-left: -240px;
  position: fixed;
  z-index: 1000;
}

.page-content {
  background-color: #666;
  height: 100%;
  width: 100%;
  position: fixed;
  padding: 10px;
  overflow: scroll;
}

.menu-item {
  border-bottom: 1px solid #000;
  padding: 10px;
}

.menu-item:first-child {
  border-top: 1px solid #000;
}

.text-block {
  border: 1px solid #000;
  width: 600px;
  display: block;
  padding: 10px;
}

.menu-item:hover,
.active {
  background: #666;
  color: #fff;
}

JavaScript

container = $(".page-content");

$(".menu-item").click(function(e) {
  data_target = $(e.target).attr("data-target");
  container.animate({
    scrollTop: $("#" + data_target).offset().top - container.offset().top + container.scrollTop()
  }, 2000);
});

$('.menu-item').on('click', function(event) {
  $('.menu-item').removeClass('active');
  $(this).addClass('active');
});

container.on('scroll', function() {
//$(window).on('scroll', function() {
  $('.header').each(function() {

    if(container.scrollTop()  >= $(this).offset().top) {
    //if(container.scrollTop()  >= $(this).position().top) {
    //if ($(window).scrollTop() >= $(this).offset().top) {
      var id = $(this).attr('id');
      $('.menu-item').removeClass('active');
      $('div[data-target=' + id + ']').addClass('active');
    }
  });
});

Upvotes: 0

Views: 1014

Answers (1)

Joe B.
Joe B.

Reputation: 1174

It looks like your biggest issue is that you are getting the offsets of the header with in the .on('scroll') along with the containers scrollTop(). Which means they both "refresh" each time you scroll. You need to store the initial offsets upon the document load and then run the scroll.

Also, because you are already setting the addClass/removeClass based on the scrollTop, you don't need to add it again when you 'click.' That is probably why you're getting erratic behavior.

Here is the pen I drafted up

var headerIds = [];
var headerOffset = [];

$('.header').each(function(){
  headerIds.push(this.id)
  headerOffset.push($(this).offset().top)
})

$(".menu-item").click(function(e) {
  data_target = $(e.target).attr("data-target");
  container.animate({
    scrollTop: $("#" + data_target).offset().top - container.offset().top + container.scrollTop() 
  }, 2000);
});

$(container).on("scroll", function(e) {
  headerIds.forEach(function(el, i){
    if ($(container).scrollTop() > (headerOffset[i]-20)) {
      $('.menu-item').removeClass('active');
      $('.menu-item:nth-of-type(' + (i + 1) + ')').addClass('active');
      var id = $(this).attr('id');
    }
  });
});

Upvotes: 1

Related Questions