22

I recently made a website in AngularJs. I am still in the learning phase. I wish to fix an element on a page after it reaches the top. I have tried all sorts of Javascript and Jquery functions. However, they don't seem to be working.

I also tried using Angular UI's ui-scrollfix but it is also not working.

I am sharing my code. It is a partial page. Please advise me a method to achieve the above mentioned effect.

<div class="row pdiv">


    <div class="col-md-8 pdiv col-md-offset-2">
        <h3><b>About Us</b></h3>
        <ul class="nav nav-justified">
            <li role="presentation"><a href="" ng-click="scrollTo('weAre')">What are    we?</a></li>
            <li role="presentation"><a href="" ng-click="scrollTo('brandsAssociation')">Brands Associations</a></li>
            <li role="presentation"><a href="" ng-click="scrollTo('knowUs')">Know Us</a></li>
            <li role="presentation"><a href="" ng-click="scrollTo('motto')">Our Motto</a></li>
        </ul>
    </div>

    <div id="weAre" class="col-md-8 pdiv col-md-offset-2">
    <br>
    <h4><b>What are we?</b></h4>
    <p>Some content goes here.</p>
    <br>
    <br>
    <br>
    <br>
    <br>
    <br>
    </div>

    <div id="brandsAssociation" class="col-md-8 pdiv col-md-offset-2">
    <br>
    <h4><b>Brands Associations</b></h4>
    <p>Some content goes here.</p>
    <br>
    <br>
    <br>
    <br>
    <br>
    <br>
    </div>

    <div id="knowUs" class="col-md-8 pdiv col-md-offset-2">
    <br>
    <h4><b>Know Us</b></h4>
    <p>Some content goes here.</p>
    <br>
    <br>
    <br>
    <br>
    <br>
    <br>
    </div>

    <div id="motto" class="col-md-8 pdiv col-md-offset-2">
    <br>
    <h4><b>Our Motto</b></h4>
    <p>Some content goes here.</p>
    <br>
    <br>
    <br>
    <br>
    <br>
    <br>
    </div>

</div>


<a href="" ng-click="scrollTo('header')"><span id="toTop" class="glyphicon glyphicon-chevron-up"></span></a>

I need to fix the ul class .nav .nav-justified after it hits the top of the page.

I am using bootstrap.

here are the javascript dependencies.

<script src="angular/angular.min.js"></script>
<script src="angular/angular-route.js"></script>
<script src="js/jquery.js"></script>
<script src="js/bootstrap.min.js"></script>

Please help...

3 Answers 3

53

To fix your ul to the top when it hits the top of the page on scroll, you can put a directive on it that checks for the window's scrollTop() exceeding the ul element's offset top. When that occurs, the directive can just add a class to the element that fixes it to the top.

So your ul markup would look like this, with new directive set-class-when-at-top on it:

<ul class="nav nav-justified" set-class-when-at-top="fix-to-top">

That directive would add the CSS class fix-to-top to the element when the element hits the top of the page. The directive definition would look like this:

app.directive('setClassWhenAtTop', function ($window) {
  var $win = angular.element($window); // wrap window object as jQuery object

  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var topClass = attrs.setClassWhenAtTop, // get CSS class from directive's attribute value
          offsetTop = element.offset().top; // get element's offset top relative to document

      $win.on('scroll', function (e) {
        if ($win.scrollTop() >= offsetTop) {
          element.addClass(topClass);
        } else {
          element.removeClass(topClass);
        }
      });
    }
  };
});

If you wanted to get a bit cheeky, you could even reduce your scroll handler to just one line:

$win.on('scroll', function (e) {
  element[($win.scrollTop() >= offsetTop) ? 'addClass' : 'removeClass'](topClass);
});

And the fix-to-top CSS class would just be something like this:

.fix-to-top {
  position: fixed;
  top: 0;
}

Here's a fiddle.

Sign up to request clarification or add additional context in comments.

3 Comments

What does $animate.pin() function does? Isn't it the same in ng-animate module?
It's awkward to set variables at the beginning of the directive. It would be better to place it into the 'link', but creating several instances of $window is awkward too. It's a way to do it but not the best i guess. I think with ng-class and a little function will work as well.
Really cool solution but It's not working in Mobile devices (Phonegap Hybrid App). Can anybody plz suggest something? (May be because $window is browser based service, it may not work in Mobile apps. If I am correct then what could be the answer for Mobile devices)
18

I started using MikeJ's great answer to get started, but quickly realized a few shortcomings:

  1. Didn't account for when content above the element changes dynamically after the directive is first parsed
  2. Content below the fixed element moved up the height of the element when it became fixed and was removed from the normal document flow
  3. If this element is being fixed below something else (like a top menu) you may have some trouble calculating the right spot; you need to fix it before the offset top is past where the $win.scrollTop() is, so that it doesn't disappear behind that menu and then get fixed after.

To fix these, I came up with a modified version:

function setClassWhenAtTop($window) {
    var $win = angular.element($window);

    return {
        restrict: "A",
        link: function (scope, element, attrs) {
            var topClass = attrs.setClassWhenAtTop,
                topPadding = parseInt(attrs.paddingWhenAtTop, 10),
                parent = element.parent(),
                offsetTop;

            $win.on("scroll", function () {
                // dynamic page layout so have to recalculate every time;
                // take offset of parent because after the element gets fixed
                // it now has a different offset from the top
                offsetTop = parent.offset().top - topPadding;
                if ($win.scrollTop() >= offsetTop) {
                    element.addClass(topClass);
                    parent.height(element.height());
                } else {
                    element.removeClass(topClass);
                    parent.css("height", null);
                }
            });
        }
    };
}

This requires the element you are fixing to be wrapped in an empty parent that only contains the element to fix. This is to handle both knowing where the original offset of the element was (for putting it back into the document flow) and to have the height of the original element to keep the document flow as it was. In addition, pass in an attribute for paddingWhenAtTop to fix it sooner (or later if desired).

Usage in the HTML changes like so:

<div>
  <ul class="nav nav-justified" set-class-when-at-top="fix-to-top" padding-when-at-top="50">
</div>

Comments

10

Here is my attempt to make it full angularjs :

JS

.directive('setClassWhenAtTop', ['$window', function($window) {
    var $win = angular.element($window); // wrap window object as jQuery object

    return {
        restrict: 'A',
        link: function (scope, element, attrs)
        {
            var topClass = attrs.setClassWhenAtTop, // get CSS class from directive's attribute value
                topPadding = parseInt(attrs.paddingWhenAtTop, 10),
                offsetTop = element.prop('offsetTop'); // get element's offset top relative to document

            $win.on('scroll', function (e) {
                if ($window.pageYOffset + topPadding >= offsetTop) {
                    element.addClass(topClass);
                } else {
                    element.removeClass(topClass);
                }
            });
        }
    };
}])

CSS

.fix-to-top {
    position: fixed;
    top: 55px;
    height: 50px;
    z-index: 999;
    width: 100%;
}

HTML

<div class="navigation-bar" set-class-when-at-top="fix-to-top" padding-when-at-top="55"> 
...

Main changes to skip jquery :

parent.offset().top => element.prop('offsetTop')
$win.scrollTop() => $window.pageYOffset

Tip of the day : Would you please stop always giving angularjs title questions jquery answers ! Or at least indicate it clearly in your title or in your answer requirements ;-)

3 Comments

Thanks for the pure Angular approach! I was a bit confused after not using angular for a while why the functions weren't defined for me in the other solutions :D
Thanks for the heads-up, I was trying to figure out why the snippet wasn't working at all until I've seen this answer. I've edited the main answer, hopefully to make it clearer.
I needed to change if ($window.pageYOffset + topPadding >= offsetTop) to if ($window.pageYOffset + topPadding > offsetTop) ,i.e. remove the equality operator to go to the else block to remove class and get it working.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.