-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
wow.coffee
227 lines (191 loc) · 6.8 KB
/
wow.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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#
# Name : wow
# Author : Matthieu Aussaguel, http://mynameismatthieu.com/, @mattaussaguel
# Version : 1.0.1
# Repo : https://github.com/matthieua/WOW
# Website : http://mynameismatthieu.com/wow
#
class Util
extend: (custom, defaults) ->
custom[key] ?= value for key, value of defaults
custom
isMobile: (agent) ->
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(agent)
# Minimalistic WeakMap shim, just in case.
WeakMap = @WeakMap or @MozWeakMap or \
class WeakMap
constructor: ->
@keys = []
@values = []
get: (key) ->
for item, i in @keys
if item is key
return @values[i]
set: (key, value) ->
for item, i in @keys
if item is key
@values[i] = value
return
@keys.push(key)
@values.push(value)
# Dummy MutationObserver, to avoid raising exceptions.
MutationObserver = @MutationObserver or @WebkitMutationObserver or @MozMutationObserver or \
class MutationObserver
constructor: ->
console?.warn 'MutationObserver is not supported by your browser.'
console?.warn 'WOW.js cannot detect dom mutations, please call .sync() after loading new content.'
@notSupported: true
observe: ->
# getComputedStyle shim, from http://stackoverflow.com/a/21797294
getComputedStyle = @getComputedStyle or \
(el, pseudo) ->
@getPropertyValue = (prop) ->
prop = 'styleFloat' if prop is 'float'
prop.replace(getComputedStyleRX, (_, char)->
char.toUpperCase()
) if getComputedStyleRX.test prop
el.currentStyle?[prop] or null
@
getComputedStyleRX = /(\-([a-z]){1})/g
class @WOW
defaults:
boxClass: 'wow'
animateClass: 'animated'
offset: 0
mobile: true
live: true
constructor: (options = {}) ->
@scrolled = true
@config = @util().extend(options, @defaults)
# Map of elements to animation names:
@animationNameCache = new WeakMap()
init: ->
@element = window.document.documentElement
if document.readyState in ["interactive", "complete"]
@start()
else
document.addEventListener 'DOMContentLoaded', @start
@finished = []
start: =>
@stopped = false
@boxes = (box for box in @element.querySelectorAll(".#{@config.boxClass}"))
@all = (box for box in @boxes)
if @boxes.length
if @disabled()
@resetStyle()
else
@applyStyle(box, true) for box in @boxes
window.addEventListener('scroll', @scrollHandler, false)
window.addEventListener('resize', @scrollHandler, false)
@interval = setInterval @scrollCallback, 50
if @config.live
new MutationObserver (records) =>
for record in records
@doSync(node) for node in record.addedNodes or []
.observe document.body,
childList: true
subtree: true
# unbind the scroll event
stop: ->
@stopped = true
window.removeEventListener('scroll', @scrollHandler, false)
window.removeEventListener('resize', @scrollHandler, false)
clearInterval @interval if @interval?
sync: (element) ->
@doSync(@element) if MutationObserver.notSupported
doSync: (element) ->
unless @stopped
element ?= @element
return unless element.nodeType is 1
element = element.parentNode or element
for box in element.querySelectorAll(".#{@config.boxClass}")
unless box in @all
@applyStyle(box, true)
@boxes.push box
@all.push box
@scrolled = true
# show box element
show: (box) ->
@applyStyle(box)
box.className = "#{box.className} #{@config.animateClass}"
applyStyle: (box, hidden) ->
duration = box.getAttribute('data-wow-duration')
delay = box.getAttribute('data-wow-delay')
iteration = box.getAttribute('data-wow-iteration')
@animate => @customStyle(box, hidden, duration, delay, iteration)
animate: (->
if 'requestAnimationFrame' of window
(callback) ->
window.requestAnimationFrame callback
else
(callback) ->
callback()
)()
resetStyle: ->
box.setAttribute('style', 'visibility: visible;') for box in @boxes
customStyle: (box, hidden, duration, delay, iteration) ->
@cacheAnimationName(box) if hidden
box.style.visibility = if hidden then 'hidden' else 'visible'
@vendorSet box.style, animationDuration: duration if duration
@vendorSet box.style, animationDelay: delay if delay
@vendorSet box.style, animationIterationCount: iteration if iteration
@vendorSet box.style, animationName: if hidden then 'none' else @cachedAnimationName(box)
box
vendors: ["moz", "webkit"]
vendorSet: (elem, properties) ->
for name, value of properties
elem["#{name}"] = value
elem["#{vendor}#{name.charAt(0).toUpperCase()}#{name.substr 1}"] = value for vendor in @vendors
vendorCSS: (elem, property) ->
style = getComputedStyle(elem)
result = style.getPropertyCSSValue(property)
result = result or style.getPropertyCSSValue("-#{vendor}-#{property}") for vendor in @vendors
result
animationName: (box) ->
try
animationName = @vendorCSS(box, 'animation-name').cssText
catch # Opera, fall back to plain property value
animationName = getComputedStyle(box).getPropertyValue('animation-name')
if animationName is 'none'
'' # SVG/Firefox, unable to get animation name?
else
animationName
cacheAnimationName: (box) ->
# https://bugzilla.mozilla.org/show_bug.cgi?id=921834
# box.dataset is not supported for SVG elements in Firefox
@animationNameCache.set(box, @animationName(box))
cachedAnimationName: (box) ->
@animationNameCache.get(box)
# fast window.scroll callback
scrollHandler: =>
@scrolled = true
scrollCallback: =>
if @scrolled
@scrolled = false
@boxes = for box in @boxes when box
if @isVisible(box)
@show(box)
continue
box
@stop() unless @boxes.length or @config.live
# Calculate element offset top
offsetTop: (element) ->
# SVG elements don't have an offsetTop in Firefox.
# This will use their nearest parent that has an offsetTop.
# Also, using ('offsetTop' of element) causes an exception in Firefox.
element = element.parentNode while element.offsetTop is undefined
top = element.offsetTop
top += element.offsetTop while element = element.offsetParent
top
# check if box is visible
isVisible: (box) ->
offset = box.getAttribute('data-wow-offset') or @config.offset
viewTop = window.pageYOffset
viewBottom = viewTop + Math.min(@element.clientHeight, innerHeight) - offset
top = @offsetTop(box)
bottom = top + box.clientHeight
top <= viewBottom and bottom >= viewTop
util: ->
@_util ?= new Util()
disabled: ->
not @config.mobile and @util().isMobile(navigator.userAgent)