<!DOCTYPE html>
<html id="ng-app" ng-app="basicApp">

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>angular-inview basic example</title>
  <style type="text/css">
    #hud {
      background: white;
      border: 1px solid gray;
      bottom: 0;
      margin: 20px;
      min-width: 220px;
      position: fixed;
      right: 0;
      top: 0;
      width: 25%;
      overflow: auto;
    }
  
    .appeared {
      border: red thick solid;
    }
    .card {
      background: white;
      width: 350px;
      height: 300px;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
      border-radius: 3px 3px;
      margin: 0 auto 10px;
      -webkit-transition: all 400ms ease;
      -moz-transition: all 400ms ease;
      -o-transition: all 400ms ease;
      transition: all 400ms ease;
      -webkit-transform: translate3d(0px, 100px, 0) rotateX(-30deg) scale(1.25, 1.25);
      -moz-transform: translate3d(0px, 100px, 0) rotateX(-30deg) scale(1.25, 1.25);
      transform: translate3d(0px, 100px, 0) rotateX(-30deg) scale(1.25, 1.25);
      opacity: 0;
    }
    .card.appeared {
      -webkit-transform: translate3d(0px, 0px, 0px) rotateX(0) scale(1, 1);
      -moz-transform: translate3d(0px, 0px, 0px) rotateX(0) scale(1, 1);
      transform: translate3d(0px, 0px, 0px) rotateX(0) scale(1, 1);
      opacity: 1;
    }
  </style>
</head>

<body ng-controller="basicCtrl">

  <div id="hud">
    <ul>
      <li ng-repeat="l in inviewLogs" ng-bind-html="l.message"></li>
    </ul>
  </div>

  <ul id="lines">
    <li class="card" ng-repeat="t in testLines" in-view="lineInView($index, $inview, $inviewpart, $event)" ng-class="{'appeared':appeared === $index}">This is test line #{{$index}}
    </li>
  </ul>


  <script src="https://code.angularjs.org/1.3.5/angular.js"></script>
  <script src="angular-inview.js"></script>
  <script type="text/javascript">
    angular.module('basicApp', ['angular-inview']).controller('basicCtrl', function basicCtrl($scope, $sce) {
      var logId = 0;
      $scope.testLines = [];
      for (var i = 20; i >= 0; i--) {
        $scope.testLines.push(i);
      }
      $scope.inviewLogs = [];
      $scope.lineInView = function(index, inview, inviewpart, event) {

        var inViewReport = inview ? '<strong>enters</strong>' : '<strong>exit</strong>';
        if (typeof(inviewpart) != 'undefined') {
          inViewReport = '<strong>' + inviewpart + '</strong> part ' + inViewReport;
          if (inviewpart === 'top') {
            $scope.appeared = index;
          }
        }
        $scope.inviewLogs.unshift({
          id: logId++,
          message: $sce.trustAsHtml('Line <em>#' + index + '</em>: ' + inViewReport)
        });
        console.log(event);
        return false;
      }
    });
  </script>
</body>

</html>
// Code goes here

/* Styles go here */

// Generated by CoffeeScript 1.7.1
(function() {
  'use strict';
  var addWindowInViewItem, bindWindowEvents, checkInView, debounce, getBoundingClientRect, getViewportHeight, removeWindowInViewItem, trackInViewContainer, triggerInViewCallback, unbindWindowEvents, untrackInViewContainer, windowCheckInView, windowEventsHandler, _containersControllers, _windowEventsHandlerBinded, _windowInViewItems,
    __slice = [].slice;

  angular.module('angular-inview', []).directive('inView', [
    '$parse', function($parse) {
      return {
        restrict: 'A',
        require: '?^inViewContainer',
        link: function(scope, element, attrs, containerController) {
          var inViewFunc, item, options, performCheck, _ref, _ref1;
          if (!attrs.inView) {
            return;
          }
          inViewFunc = $parse(attrs.inView);
          item = {
            element: element,
            wasInView: false,
            offset: 0,
            customDebouncedCheck: null,
            callback: function($event, $inview, $inviewpart) {
              if ($event == null) {
                $event = {};
              }
              return scope.$apply((function(_this) {
                return function() {
                  $event.inViewTarget = element[0];
                  return inViewFunc(scope, {
                    '$event': $event,
                    '$inview': $inview,
                    '$inviewpart': $inviewpart
                  });
                };
              })(this));
            }
          };
          if ((attrs.inViewOptions != null) && (options = scope.$eval(attrs.inViewOptions))) {
            item.offset = options.offset || [options.offsetTop || 0, options.offsetBottom || 0];
            if (options.debounce) {
              item.customDebouncedCheck = debounce((function(event) {
                return checkInView([item], element[0], event);
              }), options.debounce);
            }
          }
          performCheck = (_ref = (_ref1 = item.customDebouncedCheck) != null ? _ref1 : containerController != null ? containerController.checkInView : void 0) != null ? _ref : windowCheckInView;
          if (containerController != null) {
            containerController.addItem(item);
          } else {
            addWindowInViewItem(item);
          }
          setTimeout(performCheck);
          return scope.$on('$destroy', function() {
            if (containerController != null) {
              containerController.removeItem(item);
            }
            return removeWindowInViewItem(item);
          });
        }
      };
    }
  ]).directive('inViewContainer', function() {
    return {
      restrict: 'AC',
      controller: [
        '$element', function($element) {
          this.items = [];
          this.addItem = function(item) {
            return this.items.push(item);
          };
          this.removeItem = function(item) {
            var i;
            return this.items = (function() {
              var _i, _len, _ref, _results;
              _ref = this.items;
              _results = [];
              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                i = _ref[_i];
                if (i !== item) {
                  _results.push(i);
                }
              }
              return _results;
            }).call(this);
          };
          this.checkInView = (function(_this) {
            return function(event) {
              var i, _i, _len, _ref;
              _ref = _this.items;
              for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                i = _ref[_i];
                if (i.customDebouncedCheck != null) {
                  i.customDebouncedCheck();
                }
              }
              return checkInView((function() {
                var _j, _len1, _ref1, _results;
                _ref1 = this.items;
                _results = [];
                for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
                  i = _ref1[_j];
                  if (i.customDebouncedCheck == null) {
                    _results.push(i);
                  }
                }
                return _results;
              }).call(_this), $element[0], event);
            };
          })(this);
          return this;
        }
      ],
      link: function(scope, element, attrs, controller) {
        element.bind('scroll', controller.checkInView);
        trackInViewContainer(controller);
        return scope.$on('$destroy', function() {
          element.unbind('scroll', controller.checkInView);
          return untrackInViewContainer(controller);
        });
      }
    };
  });

  _windowInViewItems = [];

  addWindowInViewItem = function(item) {
    _windowInViewItems.push(item);
    return bindWindowEvents();
  };

  removeWindowInViewItem = function(item) {
    var i;
    _windowInViewItems = (function() {
      var _i, _len, _results;
      _results = [];
      for (_i = 0, _len = _windowInViewItems.length; _i < _len; _i++) {
        i = _windowInViewItems[_i];
        if (i !== item) {
          _results.push(i);
        }
      }
      return _results;
    })();
    return unbindWindowEvents();
  };

  _containersControllers = [];

  trackInViewContainer = function(controller) {
    _containersControllers.push(controller);
    return bindWindowEvents();
  };

  untrackInViewContainer = function(container) {
    var c;
    _containersControllers = (function() {
      var _i, _len, _results;
      _results = [];
      for (_i = 0, _len = _containersControllers.length; _i < _len; _i++) {
        c = _containersControllers[_i];
        if (c !== container) {
          _results.push(c);
        }
      }
      return _results;
    })();
    return unbindWindowEvents();
  };

  _windowEventsHandlerBinded = false;

  windowEventsHandler = function(event) {
    var c, _i, _len;
    for (_i = 0, _len = _containersControllers.length; _i < _len; _i++) {
      c = _containersControllers[_i];
      c.checkInView(event);
    }
    if (_windowInViewItems.length) {
      return windowCheckInView(event);
    }
  };

  bindWindowEvents = function() {
    if (_windowEventsHandlerBinded) {
      return;
    }
    _windowEventsHandlerBinded = true;
    return angular.element(window).bind('checkInView click ready scroll resize', windowEventsHandler);
  };

  unbindWindowEvents = function() {
    if (!_windowEventsHandlerBinded) {
      return;
    }
    if (_windowInViewItems.length || _containersControllers.length) {
      return;
    }
    _windowEventsHandlerBinded = false;
    return angular.element(window).unbind('checkInView click ready scroll resize', windowEventsHandler);
  };

  triggerInViewCallback = function(event, item, inview, isTopVisible, isBottomVisible) {
    var elOffsetTop, inviewpart;
    if (inview) {
      elOffsetTop = getBoundingClientRect(item.element[0]).top + window.pageYOffset;
      inviewpart = (isTopVisible && 'top') || (isBottomVisible && 'bottom') || 'both';
      if (!(item.wasInView && item.wasInView === inviewpart && elOffsetTop === item.lastOffsetTop)) {
        item.lastOffsetTop = elOffsetTop;
        item.wasInView = inviewpart;
        return item.callback(event, true, inviewpart);
      }
    } else if (item.wasInView) {
      item.wasInView = false;
      return item.callback(event, false);
    }
  };

  checkInView = function(items, container, event) {
    var bounds, boundsBottom, boundsTop, element, item, viewport, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3, _results;
    viewport = {
      top: 0,
      bottom: getViewportHeight()
    };
    if (container && container !== window) {
      bounds = getBoundingClientRect(container);
      if (bounds.top > viewport.bottom || bounds.bottom < viewport.top) {
        for (_i = 0, _len = items.length; _i < _len; _i++) {
          item = items[_i];
          triggerInViewCallback(event, item, false);
        }
        return;
      }
      if (bounds.top > viewport.top) {
        viewport.top = bounds.top;
      }
      if (bounds.bottom < viewport.bottom) {
        viewport.bottom = bounds.bottom;
      }
    }
    _results = [];
    for (_j = 0, _len1 = items.length; _j < _len1; _j++) {
      item = items[_j];
      element = item.element[0];
      bounds = getBoundingClientRect(element);
      boundsTop = bounds.top + parseInt((_ref = (_ref1 = item.offset) != null ? _ref1[0] : void 0) != null ? _ref : item.offset);
      boundsBottom = bounds.bottom + parseInt((_ref2 = (_ref3 = item.offset) != null ? _ref3[1] : void 0) != null ? _ref2 : item.offset);
      if (boundsTop < viewport.bottom && boundsBottom >= viewport.top) {
        _results.push(triggerInViewCallback(event, item, true, boundsBottom > viewport.bottom, boundsTop < viewport.top));
      } else {
        _results.push(triggerInViewCallback(event, item, false));
      }
    }
    return _results;
  };

  getViewportHeight = function() {
    var height, mode, _ref;
    height = window.innerHeight;
    if (height) {
      return height;
    }
    mode = document.compatMode;
    if (mode || !(typeof $ !== "undefined" && $ !== null ? (_ref = $.support) != null ? _ref.boxModel : void 0 : void 0)) {
      height = mode === 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight;
    }
    return height;
  };

  getBoundingClientRect = function(element) {
    var el, parent, top;
    if (element.getBoundingClientRect != null) {
      return element.getBoundingClientRect();
    }
    top = 0;
    el = element;
    while (el) {
      top += el.offsetTop;
      el = el.offsetParent;
    }
    parent = element.parentElement;
    while (parent) {
      if (parent.scrollTop != null) {
        top -= parent.scrollTop;
      }
      parent = parent.parentElement;
    }
    return {
      top: top,
      bottom: top + element.offsetHeight
    };
  };

  debounce = function(f, t) {
    var timer;
    timer = null;
    return function() {
      var args;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      if (timer != null) {
        clearTimeout(timer);
      }
      return timer = setTimeout((function() {
        return f.apply(null, args);
      }), t != null ? t : 100);
    };
  };

  windowCheckInView = function(event) {
    var i, _i, _len;
    for (_i = 0, _len = _windowInViewItems.length; _i < _len; _i++) {
      i = _windowInViewItems[_i];
      if (i.customDebouncedCheck != null) {
        i.customDebouncedCheck();
      }
    }
    return checkInView((function() {
      var _j, _len1, _results;
      _results = [];
      for (_j = 0, _len1 = _windowInViewItems.length; _j < _len1; _j++) {
        i = _windowInViewItems[_j];
        if (i.customDebouncedCheck == null) {
          _results.push(i);
        }
      }
      return _results;
    })(), null, event);
  };

}).call(this);