123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- #
- # Name : wow
- # Author : Matthieu Aussaguel, http://mynameismatthieu.com/, @mattaussaguel
- # Version : 1.1.2
- # 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)
-
- createEvent: (event, bubble = false, cancel = false, detail = null) ->
- if document.createEvent? # W3C DOM
- customEvent = document.createEvent('CustomEvent')
- customEvent.initCustomEvent(event, bubble, cancel, detail)
- else if document.createEventObject? # IE DOM < 9
- customEvent = document.createEventObject()
- customEvent.eventType = event
- else
- customEvent.eventName = event
-
- customEvent
-
- emitEvent: (elem, event) ->
- if elem.dispatchEvent? # W3C DOM
- elem.dispatchEvent(event)
- else if event of elem?
- elem[event]()
- else if "on#{event}" of elem?
- elem["on#{event}"]()
-
- addEvent: (elem, event, fn) ->
- if elem.addEventListener? # W3C DOM
- elem.addEventListener event, fn, false
- else if elem.attachEvent? # IE DOM
- elem.attachEvent "on#{event}", fn
- else # fallback
- elem[event] = fn
-
- removeEvent: (elem, event, fn) ->
- if elem.removeEventListener? # W3C DOM
- elem.removeEventListener event, fn, false
- else if elem.detachEvent? # IE DOM
- elem.detachEvent "on#{event}", fn
- else # fallback
- delete elem[event]
-
- innerHeight: ->
- if 'innerHeight' of window
- window.innerHeight
- else document.documentElement.clientHeight
-
- # 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
- callback: null
-
- constructor: (options = {}) ->
- @scrolled = true
- @config = @util().extend(options, @defaults)
- # Map of elements to animation names:
- @animationNameCache = new WeakMap()
- @wowEvent = @util().createEvent(@config.boxClass)
-
- init: ->
- @element = window.document.documentElement
- if document.readyState in ["interactive", "complete"]
- @start()
- else
- @util().addEvent document, '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
- if !@disabled()
- @util().addEvent window, 'scroll', @scrollHandler
- @util().addEvent window, 'resize', @scrollHandler
- @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
- @util().removeEvent window, 'scroll', @scrollHandler
- @util().removeEvent window, 'resize', @scrollHandler
- clearInterval @interval if @interval?
-
- sync: (element) ->
- @doSync(@element) if MutationObserver.notSupported
-
- doSync: (element) ->
- element ?= @element
- return unless element.nodeType is 1
- element = element.parentNode or element
- for box in element.querySelectorAll(".#{@config.boxClass}")
- unless box in @all
- @boxes.push box
- @all.push box
- if @stopped or @disabled()
- @resetStyle()
- else
- @applyStyle(box, true)
- @scrolled = true
-
- # show box element
- show: (box) ->
- @applyStyle(box)
- box.className = "#{box.className} #{@config.animateClass}"
- @config.callback(box) if @config.callback?
- @util().emitEvent(box, @wowEvent)
-
- @util().addEvent(box, 'animationend', @resetAnimation)
- @util().addEvent(box, 'oanimationend', @resetAnimation)
- @util().addEvent(box, 'webkitAnimationEnd', @resetAnimation)
- @util().addEvent(box, 'MSAnimationEnd', @resetAnimation)
-
- box
-
- 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.style.visibility = 'visible' for box in @boxes
-
- resetAnimation: (event) =>
- if event.type.toLowerCase().indexOf('animationend') >= 0
- target = event.target || event.srcElement
- target.className = target.className.replace(@config.animateClass, '').trim()
-
- 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, @util().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)
|