forked from thenikso/angular-inview
/
angular-inview.coffee
123 lines (108 loc) · 3.69 KB
/
angular-inview.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
'use strict'
# author Nicola Peduzzi
# fork https://github.com/thenikso/angular-inview
angular.module('angular-inview', [])
# inViewContainer
.directive 'inViewContainer', ->
restrict: 'AC'
controller: ($scope) ->
@items = []
@addItem = (item) ->
item.scope = $scope
@items.push item
@removeItem = (item) ->
@items = (i for i in @items when i isnt item)
@
link: (scope, element, attrs, controller) ->
check = debounce -> checkInView controller.items
element.bind 'scroll', check
scope.$on '$destroy', ->
element.unbind 'scroll', check
# inView
# Evaluate the expression passet to the attribute `in-view` when the DOM
# element is visible in the viewport.
# In the expression the following variables will be provided:
# $inview: boolean indicating if the element is in view
# $inviewpart: string either 'top', 'bottom' or 'both'
# An additional `in-view-offset` attribute can be specified to set an offset
# that will displace the inView calculation.
# Usage:
# <any in-view="{expression}" [in-view-offset="{number}"]></any>
.directive 'inView', ($parse) ->
restrict: 'A'
require: '?^inViewContainer'
link: (scope, element, attrs, container) ->
return unless attrs.inView
inViewFunc = $parse(attrs.inView)
item =
element: element
wasInView: no
offset: 0
scope: scope
callback: ($inview, $inviewpart) -> @scope.$apply =>
inViewFunc @scope,
'$inview': $inview
'$inviewpart': $inviewpart
container?.addItem item
if attrs.inViewOffset?
attrs.$observe 'inViewOffset', (offset) ->
item.offset = offset
do checkInViewDebounced
checkInViewItems.push item
do checkInViewDebounced
scope.$on '$destroy', ->
container?.removeItem item
removeInViewItem item
getViewportHeight = ->
height = window.innerHeight
return height if height
mode = document.compatMode
if mode or not $?.support?.boxModel
height = if mode is 'CSS1Compat' then document.documentElement.clientHeight else document.body.clientHeight
height
offsetTop = (el) ->
result = 0
parent = el.parentElement
while el
result += el.offsetTop
el = el.offsetParent
while parent
result -= parent.scrollTop if parent.scrollTop?
parent = parent.parentElement
result
# Object items are:
# {
# element: <angular.element>,
# offset: <number>,
# wasInView: <bool>,
# callback: <funciton taking 2 parameters: $inview and $inviewpart>
# }
checkInViewItems = []
removeInViewItem = (item) ->
checkInViewItems = (i for i in checkInViewItems when i isnt item)
checkInView = (items) ->
viewportTop = 0
viewportBottom = viewportTop + getViewportHeight()
for item in items
elementTop = offsetTop item.element[0]
elementHeight = item.element[0].offsetHeight
elementBottom = elementTop + elementHeight
inView = elementTop > viewportTop and elementBottom < viewportBottom
isBottomVisible = elementBottom + item.offset > viewportTop and elementTop < viewportTop
isTopVisible = elementTop - item.offset < viewportBottom and elementBottom > viewportBottom
inViewWithOffset = inView or isBottomVisible or isTopVisible or (elementTop < viewportTop and elementBottom > viewportBottom)
if inViewWithOffset
inviewpart = (isTopVisible and 'top') or (isBottomVisible and 'bottom') or 'both'
unless item.wasInView and item.wasInView == inviewpart
item.wasInView = inviewpart
item.callback yes, inviewpart
else if not inView and item.wasInView
item.wasInView = no
item.callback no
debounce = (f, t) ->
timer = null
->
clearTimeout timer if timer?
timer = setTimeout f, (t ? 100)
checkInViewDebounced = debounce -> checkInView checkInViewItems
angular.element(window).bind 'checkInView click ready scroll resize', checkInViewDebounced