StackOverflow_25839711: how-to-know-which-is-the-top-element-showing-in-div

Illustration of answer to http://stackoverflow.com/questions/25839711/how-to-know-which-is-the-top-element-showing-in-div.

by ExpertSystem

HTML

<script src="http://code.angularjs.org/1.2.24/angular.min.js"></script>
<div ng-controller="mainCtrl">
    <pre>Closest to .inner-div's top: {{closestToTop}}</pre>
    <hr />
    <div class="outer-div">
        <div class="inner-div" detect-closest-to-top="true">
            <div class="item" ng-repeat="item in [1,2,3,4,5,6,7,8,9,10]">
                Item {{item}}
            </div>
        </div>
    </div>
</div>

<!-- -=-=-=-=- -=[IGNORE BELOW THIS LINE]=- -=-=-=-=- -->
<!-- Page Footer -->
<small class="footer" ng-controller="footerCtrl">
    <b>Angular v{{version.full}}</b> ({{version.codeName}})
    <span class="right">
        &copy;
        <a ng-href="{{author.url}}" target="_blank">{{author.name}}</a>
    </span>
</small>

CSS

body {
    padding: 10px;
}

.outer-div {
    border: 2px dotted ForestGreen;
    height: 100px;
    overflow-y: scroll;
    padding: 10px;
}

.inner-div {
    border: 2px dotted DarkOrchid;
    height: 300px;
    overflow-y: scroll;
    padding: 10px;
}

.item {
    border: 1px dashed LimeGreen;
    height: 50px;
    margin: 5px;
}

/* -=-=-=-=- -=[IGNORE BELOW THIS LINE]=- -=-=-=-=- */
/* Page Footer */
body { padding-bottom: 22px; }
.footer { background-color: White; border-top: 1px solid rgba(100, 100, 100, 0.33); bottom: 0; display: block; font-size: 0.75em; height: 20px; left: 0; margin: 0 5px; overflow: auto; padding: 5px; position: fixed; right: 0; }
.right { float: right; }

JavaScript

var app = angular.module('myApp', []);
app.controller('mainCtrl', function ($scope) {
    
});

app.directive('detectClosestToTop', function ($timeout) {
    function findClosest(root, verbose) {
        var log = verbose ? console.log.bind(console) : function () {};
    
        var closestElem;
        var closestTop = root.scrollHeight;
        var minTop     = root.offsetTop + root.scrollTop;
        
        log('Min top:', minTop);
    
        [].forEach.call(root.children, function (child) {
            var childTop = child.offsetTop;
            if ((childTop >= minTop) && (childTop < closestTop)) {
                closestElem = child;
                closestTop  = childTop;
            }
            log(childTop, child.textContent.trim());
        });
    
        log('Closest:', closestTop, ((closestElem || {}).textContent || '').trim());
        return closestElem;
    }
    
    return {
        restrict: 'A',
        link: function (scope, elem, attrs) {
            var verboseVal = angular.lowercase(attrs.detectClosestToTop);
            var verbose    = ['true', '1', 'yes', 'on'].indexOf(verboseVal) !== -1;

            function detectClosest() {
                var closestToTop = findClosest(elem[0], verbose) || {};
                scope.closestToTop = (closestToTop.textContent || '').trim();
            }

            function onScroll() {                
                scope.$apply(detectClosest);
            }
 
            elem.on('scroll', onScroll);
            scope.$on('$destroy', function () {
                elem.off('scroll', onScroll);
            });

            $timeout(detectClosest);
        }
    };
});

/* -=-=-=-=- -=[IGNORE BELOW THIS LINE]=- -=-=-=-=- */
/* Page Footer */
app.controller('footerCtrl', function ($scope) {
    $scope.version = angular.version;
    $scope.author  = {
        name: 'ExpertSystem',
        url:  '//stackoverflow.com/users/2341938/expertsystem'
    };
});