-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.coffee
108 lines (85 loc) · 3.01 KB
/
index.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
# A dictionary for storing data per-element
counter = 0
instances = {}
# Support toggling of global disabled state
disabled = false
export disable = -> disabled = true
export enable = ->
disabled = false
update instance for id, instance of instances
# Create instance after the element has been added to DOM
startObserving = (el, binding) ->
# If an indvidual instance is disabled, just add the in viewport classes
# to reveal the element
if binding?.value?.disabled || directive.defaults.disabled || disabled
el.classList.add.apply el.classList, [ 'in-viewport' ]
return
# Create the instance object
instance = observer: makeObserver el, binding
# Generate a unique id that will be store in a data value on the element
id = 'i' + counter++
el.setAttribute 'data-in-viewport', id
instances[id] = instance
# Make the instance
makeObserver = (el, { value = {}, modifiers }) ->
# Make the default root
root = value.root || directive.defaults.root
root = switch typeof root
when 'function' then root()
when 'string' then document.querySelector root
when 'object' then root # Expects to be a DOMElement
# Make the default margin
margin = if typeof value == 'string' then value
else value.margin || directive.defaults.margin
# Make the observer callback
callback = ([..., entry]) -> update { el, entry, modifiers }
# Make the observer instance
observer = new IntersectionObserver callback,
root: root
rootMargin: margin
threshold: [0,1]
# Start observing the element and return the observer
observer.observe el
return observer
# Update element classes based on current intersection state
update = ({ el, entry, modifiers }) ->
# Destructure the entry to just what's needed
{
boundingClientRect: target
rootBounds: root
isIntersecting: inViewport
} = entry
# If rootBounds was null (sometimes happens in an iframe), make it from the
# window
root = { top: 0, bottom: window.innerHeight } unless root
# Apply classes to element
el.classList.toggle 'in-viewport', inViewport
el.classList.toggle 'above-viewport', target.top < root.top
el.classList.toggle 'below-viewport', target.bottom > root.bottom + 1
# If set to update "once", remove listeners if in viewport
removeObserver el if modifiers.once and inViewport
# Compare two objects. Doing JSON.stringify to conpare as a quick way to
# deep compare objects
objIsSame = (obj1, obj2) -> JSON.stringify(obj1) == JSON.stringify(obj2)
# Remove scrollMonitor listeners
removeObserver = (el) ->
id = el.getAttribute 'data-in-viewport'
if instance = instances[id]
instance.observer?.disconnect()
delete instances[id]
# Mixin definition
export default directive =
# Define overrideable defaults
defaults:
root: undefined
margin: '0px 0px -1px 0px'
disabled: false
# Init
inserted: (el, binding) -> startObserving el, binding
# If the value changed, re-init observer
componentUpdated: (el, binding) ->
return if objIsSame binding.value, binding.oldValue
removeObserver el
startObserving el, binding
# Cleanup
unbind: (el) -> removeObserver el