1

I'm trying to create an affix element om my page and I'm using pure AngularJs, no jquery.

I tried some different solutions to achieve this, but none of them seem to work. The problem is to get the offsetTop of the element i want to fix after the scroll.

This is running inside a directive that is executed on every scroll on the page.

This is the code i tried:

.directive("scroll", ['$window', function ($window) {
    return function(scope, element, attrs) {
        angular.element($window).bind("scroll", function() {

            //This code returns: '0'
            var filter= document.getElementById('filter').offsetTop;

            //This code returns: Error: [jqLite:nosel] 
            var filter = angular.element('#filter').prop('offsetTop');

            //This code returns: [object HTMLDivElement]
            var filter = angular.element(document.querySelector('.filter')).prop('offsetTop');

            //This code returns: '0'
            var filter = angular.element(document.getElementsByClassName('filter')).prop('offsetTop');

            [.. more code ..]

        })
    }
}]);

The problem is, none of those codes are working. Or it has an undefined value, or it returns a value of 0, even when this element is on the pageYoffset with a value of 700.

How can i get the offsetTop value of this element using pure AngularJs or vanilla Javascript? I just don't to load Jquery in the page to just use in this function.

celsomtrindade
  • 4,501
  • 18
  • 61
  • 116
  • Not sure if angular has specific functions for this. You might be better off asking how to do this using vanilla javascript. – Jorg Sep 11 '15 at 01:18
  • @Jorg Yes, i just don't want to load jquery just to create this function. If its vanilla JS or Angular, it's ok. – celsomtrindade Sep 11 '15 at 01:21
  • This question needs more information. When / where are you running this code? What do you see if you inspect the element in your console (hint, select it then in the console run `$0.offsetTop`)? – Phil Sep 11 '15 at 01:26
  • @Phil I updated my question. But I'm running the code from inside a directive that is called everytime i scroll. Also, i tried to hint the `$0.offsetTop` inside the console and the result was also `0`. Wich is weird, since it's almost in the middle of the page. – celsomtrindade Sep 11 '15 at 01:36
  • 1
    @CelsomTrindade then I guess you need to use a different property. `offsetTop` returns the [number of pixels from the top of the *closest relatively positioned* parent element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop) – Phil Sep 11 '15 at 01:44
  • Humm Ok, I understand. Do you know what other approach i could try to get this result? I need to find the distance from the top of the page (and in some cases, from the bottom of the page). – celsomtrindade Sep 11 '15 at 01:48
  • Try [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) – Phil Sep 11 '15 at 02:55
  • @Phil thanks for the help! I've made an update in my answer with a fully working code! – celsomtrindade Sep 11 '15 at 15:29

2 Answers2

3

After a lot of research and changing/unifying a lot of code, i came up with this solution. This code is going to selecet the element and create an affix effect.

The code:

  • Only use AngularJs (without Jquery);
  • Parent element with an ID and the directive and a child element with;
  • Child element with the content you want;
  • Custom css class to apply the effect (the css could also be moved inside the directive, but since I'm using sass with other functions, it's better to keep it separated for me);

page.html

<div class="parentElement" affix-me id="affix">
    <div class="childElement">
        <!-- your content here -->
    </div>
</div>

style.css

.affix {
    position:fixed !important;
    top: 0; //Change this value to match your needs;
    z-index: 990;
}

directive.js

.directive('affixMe', ['$timeout','$window', function($timeout, $window) {
    return {
        restrict: 'A',
        link: function(scope, element) {
            scope.width = element.prop('offsetWidth');
            var elWidth = scope.width + 'px',
                elChild = angular.element(element[0].querySelector(':first-child'));
                elChild.css('width', elWidth);
            angular.element($window).bind("scroll", function() {
                var affixElement = document.getElementById('affix'),
                    xPosition = 0,
                    yPosition = 0;
                function getPosition(element) {
                    while(element) {
                        yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
                        element = element.offsetParent;
                    }
                }
                getPosition(affixElement);
                if (yPosition >= 0) {
                    elChild.removeClass('affix');
                } else if ( yPosition < 0) {
                    elChild.addClass('affix');
                };
            });
        }
    };
}])

To do: ##

I didn't do this at the moment, but it would be nice to have something to check if the element reached the bottom of the page, or the end of it's parent element, and then stop the affix on the top and apply it at the bottom.

In my case, i have a 400px footer and the affix element always overlap a little on the footer.

If anyone can help with this, it would be great!



========== Old Answer ==========
Based on an answer here I've made some adjustments to make it work with my code.

This is the code I'm using:

scope.affixFiltro = false;

var filtro = document.getElementById('filtro'),
    filtroOffset,
    yPosition = 0;

function getPosition(element) {
    while(element) {
        yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
        element = element.offsetParent;
    }
}
getPosition(filtro);
if (yPosition >= 0) {
    filtroOffset = yPosition;
} else if ( yPosition < 0) {
    filtroOffset = 0;
};

//Enable/Disable affix class
if (filtroOffset == 0) { //check offset
    scope.affixFiltro = true; //enable affix class
} else {
    scope.affixFiltro = false; //disable affix class
}

Basically, since the offsetTop get the distance from the first relative positioned parent, we need to loop until we get to the top of the page.

Community
  • 1
  • 1
celsomtrindade
  • 4,501
  • 18
  • 61
  • 116
  • This directive is great for a simple affix solution without jQuery - thanks for taking the time to post it. – rob_james Apr 05 '17 at 22:03
  • Thanks @rob_james I published this code into my [github account](https://github.com/celsomtrindade/Affix). There will be a new version of this affix, with lots of improvements making it simpler. Feel free to follow =D I'm just testing it within different scenarios. – celsomtrindade Apr 05 '17 at 22:49
1

Try to look this do documentation: https://docs.angularjs.org/api/ng/service/$anchorScroll. And have de same doubt: AngularJS $anchorScroll yOffset within a child element

Community
  • 1
  • 1
Willian Lopes
  • 173
  • 1
  • 4
  • thanks for the hint. I used this information to build a directive with the affix effect. I update my own answer with the code. – celsomtrindade Sep 11 '15 at 15:30