### KUTE.js - The Light Tweening Engine # by dnp_theme # Licensed under MIT-License ### ((root, factory) -> if typeof define == 'function' and define.amd define [], factory # AMD. Register as an anonymous module. else if typeof exports == 'object' module.exports = factory() # Node, not strict CommonJS else root.KUTE = factory() return ) this, -> 'use strict' # set a custom scope for KUTE.js g = if typeof global != 'undefined' then global else window time = g.performance tweens = [] tick = null # tick must be null!! #supported properties _colors = [ 'color' 'backgroundColor' ] _boxModel = [ 'top' 'left' 'width' 'height' ] _transform = [ 'translate3d' 'translateX' 'translateY' 'translateZ' 'rotate' 'translate' 'rotateX' 'rotateY' 'rotateZ' 'skewX' 'skewY' 'scale' ] _scroll = [ 'scroll' ] _opacity = [ 'opacity' ] _all = _colors.concat(_opacity, _boxModel, _transform) al = _all.length _defaults = {} #all properties default values #populate default values object i = 0 while i < al p = _all[i] if _colors.indexOf(p) != -1 _defaults[p] = 'rgba(0,0,0,0)' # _defaults[p] = {r:0,g:0,b:0,a:1}; // no unit/suffix else if _boxModel.indexOf(p) != -1 _defaults[p] = 0 else if p == 'translate3d' # px _defaults[p] = [ 0 0 0 ] else if p == 'translate' # px _defaults[p] = [ 0 0 ] else if p == 'rotate' or /X|Y|Z/.test(p) # deg _defaults[p] = 0 else if p == 'scale' or p == 'opacity' # unitless _defaults[p] = 1 p = null i++ # default tween options, since 1.6.1 defaultOptions = duration: 700 delay: 0 offset: 0 repeat: 0 repeatDelay: 0 yoyo: false easing: 'linear' keepHex: false getPrefix = -> `var i` `var i` #returns browser prefix div = document.createElement('div') i = 0 pf = [ 'Moz' 'moz' 'Webkit' 'webkit' 'O' 'o' 'Ms' 'ms' ] s = [ 'MozTransform' 'mozTransform' 'WebkitTransform' 'webkitTransform' 'OTransform' 'oTransform' 'MsTransform' 'msTransform' ] i = 0 pl = pf.length while i < pl if s[i] of div.style return pf[i] i++ div = null return property = (p) -> # returns prefixed property | property r = if !(p of document.body.style) then true else false f = getPrefix() # is prefix required for property | prefix if r then f + p.charAt(0).toUpperCase() + p.slice(1) else p selector = (el, multi) -> # a public selector utility nl = undefined if multi nl = if el instanceof Object or typeof el == 'object' then el else document.querySelectorAll(el) else nl = if typeof el == 'object' then el else if /^#/.test(el) then document.getElementById(el.replace('#', '')) else document.querySelector(el) if nl == null and el != 'window' throw new TypeError('Element not found or incorrect selector: ' + el) nl radToDeg = (a) -> a * 180 / Math.PI trueDimension = (d, p) -> `var i` #true dimension returns { v = value, u = unit } x = parseInt(d) or 0 mu = [ 'px' '%' 'deg' 'rad' 'em' 'rem' 'vh' 'vw' ] y = undefined i = 0 l = mu.length while i < l if typeof d == 'string' and d.indexOf(mu[i]) != -1 y = mu[i] break i++ y = if y != undefined then y else if p then 'deg' else 'px' { v: x u: y } trueColor = (v) -> # replace transparent and transform any color to rgba()/rgb() if /rgb|rgba/.test(v) # first check if it's a rgb string vrgb = v.replace(/\s|\)/, '').split('(')[1].split(',') y = if vrgb[3] then vrgb[3] else null if !y return { r: parseInt(vrgb[0]) g: parseInt(vrgb[1]) b: parseInt(vrgb[2]) } else return { r: parseInt(vrgb[0]) g: parseInt(vrgb[1]) b: parseInt(vrgb[2]) a: parseFloat(y) } else if /^#/.test(v) fromHex = hexToRGB(v) return { r: fromHex.r g: fromHex.g b: fromHex.b } else if /transparent|none|initial|inherit/.test(v) return { r: 0 g: 0 b: 0 a: 0 } else if !/^#|^rgb/.test(v) # maybe we can check for web safe colors h = document.getElementsByTagName('head')[0] h.style.color = v webColor = g.getComputedStyle(h, null).color webColor = if /rgb/.test(webColor) then webColor.replace(/[^\d,]/g, '').split(',') else [ 0 0 0 ] h.style.color = '' return { r: parseInt(webColor[0]) g: parseInt(webColor[1]) b: parseInt(webColor[2]) } return rgbToHex = (r, g, b) -> # transform rgb to hex or vice-versa | webkit browsers ignore HEX, always use RGB/RGBA '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) hexToRGB = (hex) -> shr = /^#?([a-f\d])([a-f\d])([a-f\d])$/i # Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") hex = hex.replace(shr, (m, r, g, b) -> r + r + g + g + b + b ) result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) if result then r: parseInt(result[1], 16) g: parseInt(result[2], 16) b: parseInt(result[3], 16) else null getInlineStyle = (el, p) -> `var i` # get transform style for element from cssText for .to() method, the sp is for transform property if !el return # if the scroll applies to `window` it returns as it has no styling css = el.style.cssText.replace(/\s/g, '').split(';') trsf = {} #the transform object # if we have any inline style in the cssText attribute, usually it has higher priority i = 0 csl = css.length while i < csl if /transform/i.test(css[i]) tps = css[i].split(':')[1].split(')') #all transform properties k = 0 tpl = tps.length - 1 while k < tpl tpv = tps[k].split('(') tp = tpv[0] tv = tpv[1] #each transform property if _transform.indexOf(tp) != -1 trsf[tp] = if /translate3d/.test(tp) then tv.split(',') else tv k++ i++ trsf getCurrentStyle = (el, p) -> # get computed style property for element for .to() method styleAttribute = el.style computedStyle = g.getComputedStyle(el, null) or el.currentStyle pp = property(p) styleValue = if styleAttribute[p] and !/auto|initial|none|unset/.test(styleAttribute[p]) then styleAttribute[p] else computedStyle[pp] # s the property style value if p != 'transform' and (pp of computedStyle or pp of styleAttribute) if styleValue if pp == 'filter' # handle IE8 opacity filterValue = parseInt(styleValue.split('=')[1].replace(')', '')) return parseFloat(filterValue / 100) else return styleValue else return _defaults[p] return getAll = -> tweens removeAll = -> tweens = [] return add = (tw) -> tweens.push tw return remove = (tw) -> `var i` i = tweens.indexOf(tw) if i != -1 tweens.splice i, 1 return stop = -> if tick _cancelAnimationFrame tick tick = null return canTouch = 'ontouchstart' of g or navigator and navigator.msMaxTouchPoints or false touchOrWheel = if canTouch then 'touchstart' else 'mousewheel' mouseEnter = 'mouseenter' _requestAnimationFrame = g.requestAnimationFrame or g.webkitRequestAnimationFrame or (c) -> setTimeout c, 16 _cancelAnimationFrame = g.cancelAnimationFrame or g.webkitCancelRequestAnimationFrame or (c) -> clearTimeout c transformProperty = property('transform') body = document.body html = document.getElementsByTagName('HTML')[0] scrollContainer = if navigator and /webkit/i.test(navigator.userAgent) or document.compatMode == 'BackCompat' then body else html isIE = if navigator and new RegExp('MSIE ([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) != null then parseFloat(RegExp.$1) else false isIE8 = isIE == 8 isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) # we optimize morph depending on device type # KUTE.js INTERPOLATORS interpolate = g.Interpolate = {} number = interpolate.number = (a, b, v) -> # number1, number2, progress a = +a b -= a a + b * v unit = interpolate.unit = (a, b, u, v) -> # number1, number2, unit, progress a = +a b -= a a + b * v + u color = interpolate.color = (a, b, v, h) -> # rgba1, rgba2, progress, convertToHex(true/false) _c = {} c = undefined n = number ep = ')' cm = ',' r = 'rgb(' ra = 'rgba(' for c of b `c = c` _c[c] = if c != 'a' then number(a[c], b[c], v) >> 0 or 0 else if a[c] and b[c] then (number(a[c], b[c], v) * 100 >> 0) / 100 else null if h then rgbToHex(_c.r, _c.g, _c.b) else if !_c.a then r + _c.r + cm + _c.g + cm + _c.b + ep else ra + _c.r + cm + _c.g + cm + _c.b + cm + _c.a + ep translate = interpolate.translate = if isMobile then ((a, b, u, v) -> translation = {} for ax of b translation[ax] = (if a[ax] == b[ax] then b[ax] else a[ax] + (b[ax] - (a[ax])) * v >> 0) + u if translation.x or translation.y then 'translate(' + translation.x + ',' + translation.y + ')' else 'translate3d(' + translation.translateX + ',' + translation.translateY + ',' + translation.translateZ + ')' ) else ((a, b, u, v) -> translation = {} for ax of b translation[ax] = (if a[ax] == b[ax] then b[ax] else ((a[ax] + (b[ax] - (a[ax])) * v) * 100 >> 0) / 100) + u if translation.x or translation.y then 'translate(' + translation.x + ',' + translation.y + ')' else 'translate3d(' + translation.translateX + ',' + translation.translateY + ',' + translation.translateZ + ')' ) rotate = interpolate.rotate = (a, b, u, v) -> rotation = {} for rx of b rotation[rx] = if rx == 'z' then 'rotate(' + ((a[rx] + (b[rx] - (a[rx])) * v) * 100 >> 0) / 100 + u + ')' else rx + '(' + ((a[rx] + (b[rx] - (a[rx])) * v) * 100 >> 0) / 100 + u + ')' if rotation.z then rotation.z else (rotation.rotateX or '') + (rotation.rotateY or '') + (rotation.rotateZ or '') skew = interpolate.skew = (a, b, u, v) -> skewProp = {} for sx of b skewProp[sx] = sx + '(' + ((a[sx] + (b[sx] - (a[sx])) * v) * 10 >> 0) / 10 + u + ')' (skewProp.skewX or '') + (skewProp.skewY or '') scale = interpolate.scale = (a, b, v) -> 'scale(' + ((a + (b - a) * v) * 1000 >> 0) / 1000 + ')' DOM = {} ticker = (t) -> `var i` i = 0 while i < tweens.length if update.call(tweens[i], t) i++ else tweens.splice i, 1 tick = _requestAnimationFrame(ticker) return update = (t) -> `var p` `var i` t = t or time.now() if t < @_startTime and @playing return true elapsed = Math.min((t - (@_startTime)) / @options.duration, 1) progress = @options.easing(elapsed) # calculate progress for p of @valuesEnd DOM[p] @element, p, @valuesStart[p], @valuesEnd[p], progress, @options #render the CSS update if @options.update @options.update.call() # fire the updateCallback if elapsed == 1 if @options.repeat > 0 if isFinite(@options.repeat) @options.repeat-- if @options.yoyo # handle yoyo @reversed = !@reversed reverse.call this @_startTime = if @options.yoyo and !@reversed then t + @options.repeatDelay else t #set the right time for delay return true else if @options.complete @options.complete.call() scrollOut.call this # unbind preventing scroll when scroll tween finished i = 0 ctl = @options.chain.length while i < ctl # start animating chained tweens @options.chain[i].start() i++ #stop ticking when finished close.call this return false true perspective = -> el = @element ops = @options if ops.perspective != undefined and transformProperty of @valuesEnd # element perspective @valuesStart[transformProperty]['perspective'] = @valuesEnd[transformProperty]['perspective'] # element transform origin / we filter it out for svgTransform to fix the Firefox transformOrigin bug https://bugzilla.mozilla.org/show_bug.cgi?id=923193 if ops.transformOrigin != undefined and !('svgTransform' of @valuesEnd) el.style[property('transformOrigin')] = ops.transformOrigin # set transformOrigin for CSS3 transforms only if ops.perspectiveOrigin != undefined el.style[property('perspectiveOrigin')] = ops.perspectiveOrigin # element perspective origin if ops.parentPerspective != undefined el.parentNode.style[property('perspective')] = ops.parentPerspective + 'px' # parent perspective if ops.parentPerspectiveOrigin != undefined el.parentNode.style[property('perspectiveOrigin')] = ops.parentPerspectiveOrigin # parent perspective origin return prepareStart = {} crossCheck = {} parseProperty = boxModel: (p, v) -> if !(p of DOM) DOM[p] = (l, p, a, b, v) -> l.style[p] = (if v > 0.99 or v < 0.01 then (number(a, b, v) * 10 >> 0) / 10 else number(a, b, v) >> 0) + 'px' return boxValue = trueDimension(v) if boxValue.u == '%' then boxValue.v * @element.offsetWidth / 100 else boxValue.v transform: (p, v) -> if !(transformProperty of DOM) DOM[transformProperty] = (l, p, a, b, v, o) -> l.style[p] = (a.perspective or '') + (if 'translate' of a then translate(a.translate, b.translate, 'px', v) else '') + (if 'rotate' of a then rotate(a.rotate, b.rotate, 'deg', v) else '') + (if 'skew' of a then skew(a.skew, b.skew, 'deg', v) else '') + (if 'scale' of a then scale(a.scale, b.scale, v) else '') return # process each transform property if /translate/.test(p) if p == 'translate3d' t3d = v.split(',') t3d0 = trueDimension(t3d[0]) t3d1 = trueDimension(t3d[1], t3d2 = trueDimension(t3d[2])) return { translateX: if t3d0.u == '%' then t3d0.v * @element.offsetWidth / 100 else t3d0.v translateY: if t3d1.u == '%' then t3d1.v * @element.offsetHeight / 100 else t3d1.v translateZ: if t3d2.u == '%' then t3d2.v * (@element.offsetHeight + @element.offsetWidth) / 200 else t3d2.v } else if /^translate(?:[XYZ])$/.test(p) t1d = trueDimension(v) percentOffset = if /X/.test(p) then @element.offsetWidth / 100 else if /Y/.test(p) then @element.offsetHeight / 100 else (@element.offsetWidth + @element.offsetHeight) / 200 return if t1d.u == '%' then t1d.v * percentOffset else t1d.v else if p == 'translate' tv = if typeof v == 'string' then v.split(',') else v t2d = {} t2dv = undefined t2d0 = trueDimension(tv[0]) t2d1 = if tv.length then trueDimension(tv[1]) else v: 0 u: 'px' if tv instanceof Array t2d.x = if t2d0.u == '%' then t2d0.v * @element.offsetWidth / 100 else t2d0.v t2d.y = if t2d1.u == '%' then t2d1.v * @element.offsetHeight / 100 else t2d1.v else t2dv = trueDimension(tv) t2d.x = if t2dv.u == '%' then t2dv.v * @element.offsetWidth / 100 else t2dv.v t2d.y = 0 return t2d else if /rotate|skew/.test(p) if /^rotate(?:[XYZ])$|skew(?:[XY])$/.test(p) r3d = trueDimension(v, true) return if r3d.u == 'rad' then radToDeg(r3d.v) else r3d.v else if p == 'rotate' r2d = {} r2dv = trueDimension(v, true) r2d.z = if r2dv.u == 'rad' then radToDeg(r2dv.v) else r2dv.v return r2d else if p == 'scale' return parseFloat(v) # this must be parseFloat(v) return unitless: (p, v) -> # scroll | opacity if /scroll/.test(p) and !(p of DOM) DOM[p] = (l, p, a, b, v) -> l.scrollTop = number(a, b, v) >> 0 return else if p == 'opacity' if !(p of DOM) if isIE8 DOM[p] = (l, p, a, b, v) -> st = 'alpha(opacity=' ep = ')' l.style.filter = st + (number(a, b, v) * 100 >> 0) + ep return else DOM[p] = (l, p, a, b, v) -> l.style.opacity = (number(a, b, v) * 100 >> 0) / 100 return parseFloat v colors: (p, v) -> # colors if !(p of DOM) DOM[p] = (l, p, a, b, v, o) -> l.style[p] = color(a, b, v, o.keepHex) return trueColor v preparePropertiesObject = (obj, fn) -> # this, props object, type: start/end element = @element propertiesObject = if fn == 'start' then @valuesStart else @valuesEnd skewObject = {} rotateObject = {} translateObject = {} transformObject = {} for x of obj if _transform.indexOf(x) != -1 # transform object gets built here if /^translate(?:[XYZ]|3d)$/.test(x) #process translate3d ta = [ 'X' 'Y' 'Z' ] #coordinates // translate[x] = pp(x, obj[x]); f = 0 while f < 3 a = ta[f] if /3d/.test(x) translateObject['translate' + a] = parseProperty.transform.call(this, 'translate' + a, obj[x][f]) else translateObject['translate' + a] = if 'translate' + a of obj then parseProperty.transform.call(this, 'translate' + a, obj['translate' + a]) else 0 f++ transformObject['translate'] = translateObject else if /^rotate(?:[XYZ])$|^skew(?:[XY])$/.test(x) #process rotation/skew ap = if /rotate/.test(x) then 'rotate' else 'skew' ra = [ 'X' 'Y' 'Z' ] rtp = if ap == 'rotate' then rotateObject else skewObject r = 0 while r < 3 v = ra[r] if obj[ap + v] != undefined and x != 'skewZ' rtp[ap + v] = parseProperty.transform.call(this, ap + v, obj[ap + v]) r++ transformObject[ap] = rtp else if /(rotate|translate|scale)$/.test(x) #process 2d translation / rotation transformObject[x] = parseProperty.transform.call(this, x, obj[x]) propertiesObject[transformProperty] = transformObject else if _boxModel.indexOf(x) != -1 propertiesObject[x] = parseProperty.boxModel.call(this, x, obj[x]) else if _opacity.indexOf(x) != -1 or x == 'scroll' propertiesObject[x] = parseProperty.unitless.call(this, x, obj[x]) else if _colors.indexOf(x) != -1 propertiesObject[x] = parseProperty.colors.call(this, x, obj[x]) else if x of parseProperty # or any other property from css/ attr / svg / third party plugins propertiesObject[x] = parseProperty[x].call(this, x, obj[x]) return reverse = -> `var p` if @options.yoyo for p of @valuesEnd tmp = @valuesRepeat[p] @valuesRepeat[p] = @valuesEnd[p] @valuesEnd[p] = tmp @valuesStart[p] = @valuesRepeat[p] return close = -> # when animation is finished reset repeat, yoyo&reversed tweens if @repeat > 0 @options.repeat = @repeat if @options.yoyo and @reversed == true reverse.call this @reversed = false @playing = false setTimeout (-> if !tweens.length stop() return ), 48 # when all animations are finished, stop ticking after ~3 frames return preventScroll = (e) -> # prevent mousewheel or touch events while tweening scroll data = document.body.getAttribute('data-tweening') if data and data == 'scroll' e.preventDefault() return scrollOut = -> #prevent scroll when tweening scroll if 'scroll' of @valuesEnd and document.body.getAttribute('data-tweening') document.removeEventListener touchOrWheel, preventScroll, false document.removeEventListener mouseEnter, preventScroll, false document.body.removeAttribute 'data-tweening' return scrollIn = -> if 'scroll' of @valuesEnd and !document.body.getAttribute('data-tweening') document.addEventListener touchOrWheel, preventScroll, false document.addEventListener mouseEnter, preventScroll, false document.body.setAttribute 'data-tweening', 'scroll' return processEasing = (fn) -> #process easing function if typeof fn == 'function' return fn else if typeof fn == 'string' return easing[fn] # regular Robert Penner Easing Functions return getStartValues = -> `var p` `var p` # stack transform props for .to() chains startValues = {} currentStyle = getInlineStyle(@element, 'transform') deg = [ 'rotate' 'skew' ] ax = [ 'X' 'Y' 'Z' ] for p of @valuesStart if _transform.indexOf(p) != -1 r2d = /(rotate|translate|scale)$/.test(p) if /translate/.test(p) and p != 'translate' startValues['translate3d'] = currentStyle['translate3d'] or _defaults[p] else if r2d # 2d transforms startValues[p] = currentStyle[p] or _defaults[p] else if !r2d and /rotate|skew/.test(p) # all angles d = 0 while d < 2 a = 0 while a < 3 s = deg[d] + ax[a] if _transform.indexOf(s) != -1 and s of @valuesStart startValues[s] = currentStyle[s] or _defaults[s] a++ d++ else if p != 'scroll' if p == 'opacity' and isIE8 # handle IE8 opacity currentOpacity = getCurrentStyle(@element, 'filter') startValues['opacity'] = if typeof currentOpacity == 'number' then currentOpacity else _defaults['opacity'] else if _all.indexOf(p) != -1 startValues[p] = getCurrentStyle(@element, p) or d[p] else # plugins register here startValues[p] = if p of prepareStart then prepareStart[p].call(this, p, @valuesStart[p]) else 0 else startValues[p] = if @element == scrollContainer then g.pageYOffset or scrollContainer.scrollTop else @element.scrollTop for p of currentStyle # also add to startValues values from previous tweens if _transform.indexOf(p) != -1 and !(p of @valuesStart) startValues[p] = currentStyle[p] or _defaults[p] @valuesStart = {} preparePropertiesObject.call this, startValues, 'start' if transformProperty of @valuesEnd # let's stack transform for sp of @valuesStart[transformProperty] # sp is the object corresponding to the transform function objects translate / rotate / skew / scale if sp != 'perspective' if typeof @valuesStart[transformProperty][sp] == 'object' for spp of @valuesStart[transformProperty][sp] # 3rd level if typeof @valuesEnd[transformProperty][sp] == 'undefined' @valuesEnd[transformProperty][sp] = {} if typeof @valuesStart[transformProperty][sp][spp] == 'number' and typeof @valuesEnd[transformProperty][sp][spp] == 'undefined' @valuesEnd[transformProperty][sp][spp] = @valuesStart[transformProperty][sp][spp] else if typeof @valuesStart[transformProperty][sp] == 'number' if typeof @valuesEnd[transformProperty][sp] == 'undefined' # scale @valuesEnd[transformProperty][sp] = @valuesStart[transformProperty][sp] return # core easing functions easing = g.Easing = {} easing.linear = (t) -> t easing.easingSinusoidalIn = (t) -> -Math.cos(t * Math.PI / 2) + 1 easing.easingSinusoidalOut = (t) -> Math.sin t * Math.PI / 2 easing.easingSinusoidalInOut = (t) -> -0.5 * (Math.cos(Math.PI * t) - 1) easing.easingQuadraticIn = (t) -> t * t easing.easingQuadraticOut = (t) -> t * (2 - t) easing.easingQuadraticInOut = (t) -> if t < .5 then 2 * t * t else -1 + (4 - (2 * t)) * t easing.easingCubicIn = (t) -> t * t * t easing.easingCubicOut = (t) -> --t * t * t + 1 easing.easingCubicInOut = (t) -> if t < .5 then 4 * t * t * t else (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 easing.easingQuarticIn = (t) -> t * t * t * t easing.easingQuarticOut = (t) -> 1 - (--t * t * t * t) easing.easingQuarticInOut = (t) -> if t < .5 then 8 * t * t * t * t else 1 - (8 * --t * t * t * t) easing.easingQuinticIn = (t) -> t * t * t * t * t easing.easingQuinticOut = (t) -> 1 + --t * t * t * t * t easing.easingQuinticInOut = (t) -> if t < .5 then 16 * t * t * t * t * t else 1 + 16 * --t * t * t * t * t easing.easingCircularIn = (t) -> -(Math.sqrt(1 - (t * t)) - 1) easing.easingCircularOut = (t) -> Math.sqrt 1 - ((t = t - 1) * t) easing.easingCircularInOut = (t) -> if (t *= 2) < 1 then -0.5 * (Math.sqrt(1 - (t * t)) - 1) else 0.5 * (Math.sqrt(1 - ((t -= 2) * t)) + 1) easing.easingExponentialIn = (t) -> 2 ** (10 * (t - 1)) - 0.001 easing.easingExponentialOut = (t) -> 1 - 2 ** (-10 * t) easing.easingExponentialInOut = (t) -> if (t *= 2) < 1 then 0.5 * 2 ** (10 * (t - 1)) else 0.5 * (2 - 2 ** (-10 * (t - 1))) easing.easingBackIn = (t) -> s = 1.70158 t * t * ((s + 1) * t - s) easing.easingBackOut = (t) -> s = 1.70158 --t * t * ((s + 1) * t + s) + 1 easing.easingBackInOut = (t) -> s = 1.70158 * 1.525 if (t *= 2) < 1 return 0.5 * t * t * ((s + 1) * t - s) 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2) easing.easingElasticIn = (t) -> s = undefined _kea = 0.1 _kep = 0.4 if t == 0 return 0 if t == 1 return 1 if !_kea or _kea < 1 _kea = 1 s = _kep / 4 else s = _kep * Math.asin(1 / _kea) / Math.PI * 2 -(_kea * 2 ** (10 * (t -= 1)) * Math.sin((t - s) * Math.PI * 2 / _kep)) easing.easingElasticOut = (t) -> s = undefined _kea = 0.1 _kep = 0.4 if t == 0 return 0 if t == 1 return 1 if !_kea or _kea < 1 _kea = 1 s = _kep / 4 else s = _kep * Math.asin(1 / _kea) / Math.PI * 2 _kea * 2 ** (-10 * t) * Math.sin((t - s) * Math.PI * 2 / _kep) + 1 easing.easingElasticInOut = (t) -> s = undefined _kea = 0.1 _kep = 0.4 if t == 0 return 0 if t == 1 return 1 if !_kea or _kea < 1 _kea = 1 s = _kep / 4 else s = _kep * Math.asin(1 / _kea) / Math.PI * 2 if (t *= 2) < 1 return -0.5 * _kea * 2 ** (10 * (t -= 1)) * Math.sin((t - s) * Math.PI * 2 / _kep) _kea * 2 ** (-10 * (t -= 1)) * Math.sin((t - s) * Math.PI * 2 / _kep) * 0.5 + 1 easing.easingBounceIn = (t) -> 1 - easing.easingBounceOut(1 - t) easing.easingBounceOut = (t) -> if t < 1 / 2.75 7.5625 * t * t else if t < 2 / 2.75 7.5625 * (t -= 1.5 / 2.75) * t + 0.75 else if t < 2.5 / 2.75 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375 else 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375 easing.easingBounceInOut = (t) -> if t < 0.5 return easing.easingBounceIn(t * 2) * 0.5 easing.easingBounceOut(t * 2 - 1) * 0.5 + 0.5 # single Tween object construct Tween = (targetElement, startObject, endObject, options) -> @element = if 'scroll' of endObject and (targetElement == undefined or targetElement == null) then scrollContainer else targetElement # element animation is applied to @playing = false @reversed = false @paused = false @_startTime = null @_pauseTime = null @_startFired = false @options = {} for o of options @options[o] = options[o] @options.rpr = options.rpr or false # internal option to process inline/computed style at start instead of init true/false @valuesRepeat = {} # internal valuesRepeat @valuesEnd = {} # valuesEnd @valuesStart = {} # valuesStart preparePropertiesObject.call this, endObject, 'end' # valuesEnd if @options.rpr @valuesStart = startObject else preparePropertiesObject.call this, startObject, 'start' # valuesStart if @options.perspective != undefined and transformProperty of @valuesEnd # element transform perspective perspectiveString = 'perspective(' + parseInt(@options.perspective) + 'px)' @valuesEnd[transformProperty].perspective = perspectiveString for e of @valuesEnd if e of crossCheck and !@options.rpr crossCheck[e].call this # this is where we do the valuesStart and valuesEnd check for fromTo() method @options.chain = [] # chained Tweens @options.easing = if options.easing and typeof processEasing(options.easing) == 'function' then processEasing(options.easing) else easing[defaultOptions.easing] # you can only set a core easing function as default @options.repeat = options.repeat or defaultOptions.repeat @options.repeatDelay = options.repeatDelay or defaultOptions.repeatDelay @options.yoyo = options.yoyo or defaultOptions.yoyo @options.duration = options.duration or defaultOptions.duration # duration option | default @options.delay = options.delay or defaultOptions.delay # delay option | default @repeat = @options.repeat # we cache the number of repeats to be able to put it back after all cycles finish return TweenProto = Tween.prototype = start: (t) -> # move functions that use the ticker outside the prototype to be in the same scope with it scrollIn.call this if @options.rpr getStartValues.apply this # on start we reprocess the valuesStart for TO() method perspective.apply this # apply the perspective and transform origin for e of @valuesEnd if e of crossCheck and @options.rpr crossCheck[e].call this # this is where we do the valuesStart and valuesEnd check for to() method @valuesRepeat[e] = @valuesStart[e] # now it's a good time to start tweens.push this @playing = true @paused = false @_startFired = false @_startTime = t or time.now() @_startTime += @options.delay if !@_startFired if @options.start @options.start.call() @_startFired = true !tick and ticker() this play: -> if @paused and @playing @paused = false if @options.resume @options.resume.call() @_startTime += time.now() - (@_pauseTime) add this !tick and ticker() # restart ticking if stopped this resume: -> @play() pause: -> if !@paused and @playing remove this @paused = true @_pauseTime = time.now() if @options.pause @options.pause.call() this stop: -> if !@paused and @playing remove this @playing = false @paused = false scrollOut.call this if @options.stop @options.stop.call() @stopChainedTweens() close.call this this chain: -> @options.chain = arguments this stopChainedTweens: -> `var i` i = 0 ctl = @options.chain.length while i < ctl @options.chain[i].stop() i++ return TweensTO = (els, vE, o) -> `var i` # .to @tweens = [] options = [] i = 0 tl = els.length while i < tl options[i] = o or {} o.delay = o.delay or defaultOptions.delay options[i].delay = if i > 0 then o.delay + (o.offset or defaultOptions.offset) else o.delay @tweens.push to(els[i], vE, options[i]) i++ return TweensFT = (els, vS, vE, o) -> `var i` # .fromTo @tweens = [] options = [] i = 0 l = els.length while i < l options[i] = o or {} o.delay = o.delay or defaultOptions.delay options[i].delay = if i > 0 then o.delay + (o.offset or defaultOptions.offset) else o.delay @tweens.push fromTo(els[i], vS, vE, options[i]) i++ return ws = TweensTO.prototype = TweensFT.prototype = start: (t) -> `var i` t = t or time.now() i = 0 tl = @tweens.length while i < tl @tweens[i].start t i++ this stop: -> `var i` i = 0 tl = @tweens.length while i < tl @tweens[i].stop() i++ this pause: -> `var i` i = 0 tl = @tweens.length while i < tl @tweens[i].pause() i++ this chain: -> @tweens[@tweens.length - 1].options.chain = arguments this play: -> `var i` i = 0 tl = @tweens.length while i < tl @tweens[i].play() i++ this resume: -> @play() to = (element, endObject, options) -> options = options or {} options.rpr = true new Tween(selector(element), endObject, endObject, options) fromTo = (element, startObject, endObject, options) -> options = options or {} new Tween(selector(element), startObject, endObject, options) allTo = (elements, endObject, options) -> new TweensTO(selector(elements, true), endObject, options) allFromTo = (elements, f, endObject, options) -> new TweensFT(selector(elements, true), f, endObject, options) { property: property getPrefix: getPrefix selector: selector processEasing: processEasing defaultOptions: defaultOptions to: to fromTo: fromTo allTo: allTo allFromTo: allFromTo ticker: ticker tick: tick tweens: tweens update: update dom: DOM parseProperty: parseProperty prepareStart: prepareStart crossCheck: crossCheck Tween: Tween truD: trueDimension truC: trueColor rth: rgbToHex htr: hexToRGB getCurrentStyle: getCurrentStyle }