UNPKG

26.3 kBJavaScriptView Raw
1/*
2MooFx
3*/"use strict"
4
5// requires
6
7var color = require("./color"),
8 frame = require("./frame")
9
10var cancelFrame = frame.cancel,
11 requestFrame = frame.request
12
13var prime = require("prime/prime"),
14 array = require("prime/es5/array"),
15 string = require("prime/types/string")
16
17var camelize = string.camelize,
18 clean = string.clean,
19 capitalize = string.capitalize
20
21var map = array.map,
22 forEach = array.forEach,
23 indexOf = array.indexOf
24
25var elements = require("elements/lib/elements")
26
27var fx = require("./fx")
28
29// util
30
31var hyphenated = {}
32var hyphenate = function(self){
33 return hyphenated[self] || (hyphenated[self] = string.hyphenate(self))
34}
35
36var round = function(n){
37 return Math.round(n * 1e3) / 1e3
38}
39
40// compute > node > property
41
42var compute = global.getComputedStyle ? function(node){
43 var cts = getComputedStyle(node)
44 return function(property){
45 return cts ? cts.getPropertyValue(hyphenate(property)) : ""
46 }
47} : /*(css3)?*/function(node){
48 var cts = node.currentStyle
49 return function(property){
50 return cts ? cts[camelize(property)] : ""
51 }
52}/*:null*/
53
54// pixel ratio retriever
55
56var test = document.createElement("div")
57
58var cssText = "border:none;margin:none;padding:none;visibility:hidden;position:absolute;height:0;";
59
60// returns the amount of pixels that takes to make one of the unit
61
62var pixelRatio = function(element, u){
63 var parent = element.parentNode, ratio = 1
64 if (parent){
65 test.style.cssText = cssText + ("width:100" + u + ";")
66 parent.appendChild(test)
67 ratio = test.offsetWidth / 100
68 parent.removeChild(test)
69 }
70 return ratio
71}
72
73// mirror 4 values
74
75var mirror4 = function(values){
76 var length = values.length
77 if (length === 1) values.push(values[0], values[0], values[0])
78 else if (length === 2) values.push(values[0], values[1])
79 else if (length === 3) values.push(values[1])
80 return values
81}
82
83// regular expressions strings
84
85var sLength = "([-.\\d]+)(%|cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vm)",
86 sLengthNum = sLength + "?",
87 sBorderStyle = "none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset|inherit"
88
89// regular expressions
90
91var rgLength = RegExp(sLength, "g"),
92 rLengthNum = RegExp(sLengthNum),
93 rgLengthNum = RegExp(sLengthNum, "g"),
94 rBorderStyle = RegExp(sBorderStyle)
95
96// normalize > css
97
98var parseString = function(value){
99 return (value == null) ? "" : value + ""
100}
101
102var parseOpacity = function(value, normalize){
103 if (value == null || value === "") return normalize ? "1" : ""
104 return (isFinite((value = +value))) ? (value < 0 ? "0" : value + "") : "1"
105}
106
107try {test.style.color = "rgba(0,0,0,0.5)"} catch(e){}
108var rgba = /^rgba/.test(test.style.color)
109
110var parseColor = function(value, normalize){
111 var black = "rgba(0,0,0,1)", c
112 if (!value || !(c = color(value, true))) return normalize ? black : ""
113 if (normalize) return "rgba(" + c + ")"
114
115 var alpha = c[3]
116 if (alpha === 0) return "transparent"
117 return (!rgba || alpha === 1) ? "rgb(" + c.slice(0, 3) + ")" : "rgba(" + c + ")"
118}
119
120var parseLength = function(value, normalize){
121 if (value == null || value === "") return normalize ? "0px" : ""
122 var match = string.match(value, rLengthNum)
123 return match ? match[1] + (match[2] || "px") : value // cannot be parsed. probably "auto"
124}
125
126var parseBorderStyle = function(value, normalize){
127 if (value == null || value === "") return normalize ? "none" : ""
128 var match = value.match(rBorderStyle)
129 return match ? value : normalize ? "none" : ""
130}
131
132var parseBorder = function(value, normalize){
133 var normalized = "0px none rgba(0,0,0,1)"
134 if (value == null || value === "") return normalize ? normalized : ""
135 if (value === 0 || value === "none") return normalize ? normalized : value + ""
136
137 var c
138 value = value.replace(color.x, function(match){
139 c = match
140 return ""
141 })
142
143 var s = value.match(rBorderStyle),
144 l = value.match(rgLengthNum)
145
146 return clean([
147 parseLength(l ? l[0] : "", normalize),
148 parseBorderStyle(s ? s[0] : "", normalize),
149 parseColor(c, normalize)
150 ].join(" "))
151}
152
153var parseShort4 = function(value, normalize){
154 if (value == null || value === "") return normalize ? "0px 0px 0px 0px" : ""
155 return clean(mirror4(map(clean(value).split(" "), function(v){
156 return parseLength(v, normalize)
157 })).join(" "))
158}
159
160var parseShadow = function(value, normalize, len){
161 var transparent = "rgba(0,0,0,0)",
162 normalized = (len === 3) ? transparent + " 0px 0px 0px" : transparent + " 0px 0px 0px 0px"
163
164 if (value == null || value === "") return normalize ? normalized : ""
165 if (value === "none") return normalize ? normalized : value
166
167 var colors = [], value = clean(value).replace(color.x, function(match){
168 colors.push(match)
169 return ""
170 })
171
172 return map(value.split(","), function(shadow, i){
173
174 var c = parseColor(colors[i], normalize),
175 inset = /inset/.test(shadow),
176 lengths = shadow.match(rgLengthNum) || ["0px"]
177
178 lengths = map(lengths, function(m){
179 return parseLength(m, normalize)
180 })
181
182 while (lengths.length < len) lengths.push("0px")
183
184 var ret = inset ? ["inset", c] : [c]
185
186 return ret.concat(lengths).join(" ")
187
188 }).join(", ")
189}
190
191var parse = function(value, normalize){
192 if (value == null || value === "") return "" // cant normalize "" || null
193 return value.replace(color.x, function(match){
194 return parseColor(match, normalize)
195 }).replace(rgLength, function(match){
196 return parseLength(match, normalize)
197 })
198}
199
200// get && set
201
202var getters = {}, setters = {}, parsers = {}, aliases = {}
203
204var getter = function(key){
205 return getters[key] || (getters[key] = (function(){
206 var alias = aliases[key] || key,
207 parser = parsers[key] || parse
208
209 return function(){
210 return parser(compute(this)(alias), true)
211 }
212
213 }()))
214}
215
216var setter = function(key){
217 return setters[key] || (setters[key] = (function(){
218
219 var alias = aliases[key] || key,
220 parser = parsers[key] || parse
221
222 return function(value){
223 this.style[alias] = parser(value, false)
224 }
225
226 }()))
227}
228
229// parsers
230
231var trbl = ["Top", "Right", "Bottom", "Left"], tlbl = ["TopLeft", "TopRight", "BottomRight", "BottomLeft"]
232
233forEach(trbl, function(d){
234 var bd = "border" + d
235 forEach([ "margin" + d, "padding" + d, bd + "Width", d.toLowerCase()], function(n){
236 parsers[n] = parseLength
237 })
238 parsers[bd + "Color"] = parseColor
239 parsers[bd + "Style"] = parseBorderStyle
240
241 // borderDIR
242 parsers[bd] = parseBorder
243 getters[bd] = function(){
244 return [
245 getter(bd + "Width").call(this),
246 getter(bd + "Style").call(this),
247 getter(bd + "Color").call(this)
248 ].join(" ")
249 }
250})
251
252forEach(tlbl, function(d){
253 parsers["border" + d + "Radius"] = parseLength
254})
255
256parsers.color = parsers.backgroundColor = parseColor
257parsers.width = parsers.height = parsers.fontSize = parsers.backgroundSize = parseLength
258
259// margin + padding
260
261forEach(["margin", "padding"], function(name){
262 parsers[name] = parseShort4
263 getters[name] = function(){
264 return map(trbl, function(d){
265 return getter(name + d).call(this)
266 }, this).join(" ")
267 }
268})
269
270// borders
271
272// borderDIRWidth, borderDIRStyle, borderDIRColor
273
274parsers.borderWidth = parseShort4
275
276parsers.borderStyle = function(value, normalize){
277 if (value == null || value === "") return normalize ? mirror4(["none"]).join(" ") : ""
278 value = clean(value).split(" ")
279 return clean(mirror4(map(value, function(v){
280 parseBorderStyle(v, normalize)
281 })).join(" "))
282}
283
284parsers.borderColor = function(value, normalize){
285 if (!value || !(value = string.match(value, color.x))) return normalize ? mirror4(["rgba(0,0,0,1)"]).join(" ") : ""
286 return clean(mirror4(map(value, function(v){
287 return parseColor(v, normalize)
288 })).join(" "))
289}
290
291forEach(["Width", "Style", "Color"], function(name){
292 getters["border" + name] = function(){
293 return map(trbl, function(d){
294 return getter("border" + d + name).call(this)
295 }, this).join(" ")
296 }
297})
298
299// borderRadius
300
301parsers.borderRadius = parseShort4
302
303getters.borderRadius = function(){
304 return map(tlbl, function(d){
305 return getter("border" + d + "Radius").call(this)
306 }, this).join(" ")
307}
308
309// border
310
311parsers.border = parseBorder
312
313getters.border = function(){
314 var pvalue
315 for (var i = 0; i < trbl.length; i++){
316 var value = getter("border" + trbl[i]).call(this)
317 if (pvalue && value !== pvalue) return null
318 pvalue = value
319 }
320 return pvalue
321}
322
323// zIndex
324
325parsers.zIndex = parseString
326
327// opacity
328
329parsers.opacity = parseOpacity
330
331/*(css3)?*/
332
333var filterName = (test.style.MsFilter != null && "MsFilter") || (test.style.filter != null && "filter")
334
335if (filterName && test.style.opacity == null){
336
337 var matchOp = /alpha\(opacity=([\d.]+)\)/i
338
339 setters.opacity = function(value){
340 value = ((value = parseOpacity(value)) === "1") ? "" : "alpha(opacity=" + Math.round(value * 100) + ")"
341 var filter = compute(this)(filterName)
342 return this.style[filterName] = matchOp.test(filter) ? filter.replace(matchOp, value) : filter + " " + value
343 }
344
345 getters.opacity = function(){
346 var match = compute(this)(filterName).match(matchOp)
347 return (!match ? 1 : match[1] / 100) + ""
348 }
349
350}/*:*/
351
352var parseBoxShadow = parsers.boxShadow = function(value, normalize){
353 return parseShadow(value, normalize, 4)
354}
355
356var parseTextShadow = parsers.textShadow = function(value, normalize){
357 return parseShadow(value, normalize, 3)
358}
359
360// Aliases
361
362forEach(['Webkit', "Moz", "ms", "O", null], function(prefix){
363 forEach([
364 "transition", "transform", "transformOrigin", "transformStyle", "perspective", "perspectiveOrigin", "backfaceVisibility"
365 ], function(style){
366 var cc = prefix ? prefix + capitalize(style) : style
367 if (prefix === "ms") hyphenated[cc] = "-ms-" + hyphenate(style)
368 if (test.style[cc] != null) aliases[style] = cc
369 })
370})
371
372var transitionName = aliases.transition,
373 transformName = aliases.transform
374
375// manually disable css3 transitions in Opera, because they do not work properly.
376
377if (transitionName === "OTransition") transitionName = null
378
379
380// this takes care of matrix decomposition on browsers that support only 2d transforms but no CSS3 transitions.
381// basically, IE9 (and Opera as well, since we disabled CSS3 transitions manually)
382
383var parseTransform2d, Transform2d
384
385/*(css3)?*/
386
387if (!transitionName && transformName) (function(){
388
389 var unmatrix = require("./unmatrix2d")
390
391 var v = "\\s*([-\\d\\w.]+)\\s*"
392
393 var rMatrix = RegExp("matrix\\(" + [v, v, v, v, v, v] + "\\)")
394
395 var decomposeMatrix = function(matrix){
396
397 var d = unmatrix.apply(null, matrix.match(rMatrix).slice(1)) || [[0, 0], 0, 0, [0, 0]]
398
399 return [
400
401 "translate(" + map(d[0], function(v){return round(v) + "px"}) + ")",
402 "rotate(" + round(d[1] * 180 / Math.PI) + "deg)",
403 "skewX(" + round(d[2] * 180 / Math.PI) + "deg)",
404 "scale(" + map(d[3], round) + ")"
405
406 ].join(" ")
407
408 }
409
410 var def0px = function(value){return value || "0px"},
411 def1 = function(value){return value || "1"},
412 def0deg = function(value){return value || "0deg"}
413
414 var transforms = {
415
416 translate: function(value){
417 if (!value) value = "0px,0px"
418 var values = value.split(",")
419 if (!values[1]) values[1] = "0px"
420 return map(values, clean) + ""
421 },
422 translateX: def0px,
423 translateY: def0px,
424 scale: function(value){
425 if (!value) value = "1,1"
426 var values = value.split(",")
427 if (!values[1]) values[1] = values[0]
428 return map(values, clean) + ""
429 },
430 scaleX: def1,
431 scaleY: def1,
432 rotate: def0deg,
433 skewX: def0deg,
434 skewY: def0deg
435
436 }
437
438 Transform2d = prime({
439
440 constructor: function(transform){
441
442 var names = this.names = []
443 var values = this.values = []
444
445 transform.replace(/(\w+)\(([-.\d\s\w,]+)\)/g, function(match, name, value){
446 names.push(name)
447 values.push(value)
448 })
449
450 },
451
452 identity: function(){
453 var functions = []
454 forEach(this.names, function(name){
455 var fn = transforms[name]
456 if (fn) functions.push(name + "(" + fn() + ")")
457 })
458 return functions.join(" ")
459 },
460
461 sameType: function(transformObject){
462 return this.names.toString() === transformObject.names.toString()
463 },
464
465 // this is, basically, cheating.
466 // retrieving the matrix value from the dom, rather than calculating it
467
468 decompose: function(){
469 var transform = this.toString()
470
471 test.style.cssText = cssText + hyphenate(transformName) + ":" + transform + ";"
472 document.body.appendChild(test)
473 var m = compute(test)(transformName)
474 if (!m || m === "none") m = "matrix(1, 0, 0, 1, 0, 0)"
475 document.body.removeChild(test)
476 return decomposeMatrix(m)
477 }
478
479 })
480
481 Transform2d.prototype.toString = function(clean){
482 var values = this.values, functions = []
483 forEach(this.names, function(name, i){
484 var fn = transforms[name]
485 if (!fn) return
486 var value = fn(values[i])
487 if (!clean || value !== fn()) functions.push(name + "(" + value + ")")
488 })
489 return functions.length ? functions.join(" ") : "none"
490 }
491
492 Transform2d.union = function(from, to){
493
494 if (from === to) return // nothing to do
495
496 var fromMap, toMap
497
498 if (from === "none"){
499
500 toMap = new Transform2d(to)
501 to = toMap.toString()
502 from = toMap.identity()
503 fromMap = new Transform2d(from)
504
505 } else if (to === "none"){
506
507 fromMap = new Transform2d(from)
508 from = fromMap.toString()
509 to = fromMap.identity()
510 toMap = new Transform2d(to)
511
512 } else {
513
514 fromMap = new Transform2d(from)
515 from = fromMap.toString()
516 toMap = new Transform2d(to)
517 to = toMap.toString()
518
519 }
520
521 if (from === to) return // nothing to do
522
523 if (!fromMap.sameType(toMap)){
524
525 from = fromMap.decompose()
526 to = toMap.decompose()
527
528 }
529
530 if (from === to) return // nothing to do
531
532 return [from, to]
533
534 }
535
536 // this parser makes sure it never gets "matrix"
537
538 parseTransform2d = parsers.transform = function(transform){
539 if (!transform || transform === "none") return "none"
540 return new Transform2d(rMatrix.test(transform) ? decomposeMatrix(transform) : transform).toString(true)
541 }
542
543 // this getter makes sure we read from the dom only the first time
544 // this way we save the actual transform and not "matrix"
545 // setting matrix() will use parseTransform2d as well, thus setting the decomposed matrix
546
547 getters.transform = function(){
548 var s = this.style
549 return s[transformName] || (s[transformName] = parseTransform2d(compute(this)(transformName)))
550 }
551
552
553})()/*:*/
554
555// tries to match from and to values
556
557var prepare = function(node, property, to){
558
559 var parser = parsers[property] || parse,
560 from = getter(property).call(node), // "normalized" by the getter
561 to = parser(to, true) // normalize parsed property
562
563 if (from === to) return
564
565 if (parser === parseLength || parser === parseBorder || parser === parseShort4){
566
567 var toAll = to.match(rgLength), i = 0 // this should always match something
568
569 if (toAll) from = from.replace(rgLength, function(fromFull, fromValue, fromUnit){
570
571 var toFull = toAll[i++],
572 toMatched = toFull.match(rLengthNum),
573 toUnit = toMatched[2]
574
575 if (fromUnit !== toUnit){
576 var fromPixels = (fromUnit === "px") ? fromValue : pixelRatio(node, fromUnit) * fromValue
577 return round(fromPixels / pixelRatio(node, toUnit)) + toUnit
578 }
579
580 return fromFull
581
582 })
583
584 if (i > 0) setter(property).call(node, from)
585
586 }/*(css3)?*/else if (parser === parseTransform2d){ // IE9/Opera
587
588 return Transform2d.union(from, to)
589
590 }/*:*/
591
592 return (from !== to) ? [from, to] : null
593
594}
595
596// BrowserAnimation
597
598var BrowserAnimation = prime({
599
600 inherits: fx,
601
602 constructor: function BrowserAnimation(node, property){
603
604 var _getter = getter(property),
605 _setter = setter(property)
606
607 this.get = function(){
608 return _getter.call(node)
609 }
610
611 this.set = function(value){
612 return _setter.call(node, value)
613 }
614
615 BrowserAnimation.parent.constructor.call(this, this.set)
616
617 this.node = node
618 this.property = property
619
620 }
621
622})
623
624var JSAnimation
625
626/*(css3)?*/
627
628JSAnimation = prime({
629
630 inherits: BrowserAnimation,
631
632 constructor: function JSAnimation(){
633 return JSAnimation.parent.constructor.apply(this, arguments)
634 },
635
636 start: function(to){
637
638 this.stop()
639
640 if (this.duration === 0){
641 this.cancel(to)
642 return this
643 }
644
645 var fromTo = prepare(this.node, this.property, to)
646
647 if (!fromTo){
648 this.cancel(to)
649 return this
650 }
651
652 JSAnimation.parent.start.apply(this, fromTo)
653
654 if (!this.cancelStep) return this
655
656 // the animation would have started but we need additional checks
657
658 var parser = parsers[this.property] || parse
659
660 // complex interpolations JSAnimation can't handle
661 // even CSS3 animation gracefully fail with some of those edge cases
662 // other "simple" properties, such as `border` can have different templates
663 // because of string properties like "solid" and "dashed"
664
665 if ((parser === parseBoxShadow || parser === parseTextShadow || parser === parse) &&
666 (this.templateFrom !== this.templateTo)){
667 this.cancelStep()
668 delete this.cancelStep
669 this.cancel(to)
670 }
671
672 return this
673 },
674
675 parseEquation: function(equation){
676 if (typeof equation === "string") return JSAnimation.parent.parseEquation.call(this, equation)
677 }
678
679
680})/*:*/
681
682// CSSAnimation
683
684var remove3 = function(value, a, b, c){
685 var index = indexOf(a, value)
686 if (index !== -1){
687 a.splice(index, 1)
688 b.splice(index, 1)
689 c.splice(index, 1)
690 }
691}
692
693var CSSAnimation = prime({
694
695 inherits: BrowserAnimation,
696
697 constructor: function CSSAnimation(node, property){
698 CSSAnimation.parent.constructor.call(this, node, property)
699
700 this.hproperty = hyphenate(aliases[property] || property)
701
702 var self = this
703
704 this.bSetTransitionCSS = function(time){
705 self.setTransitionCSS(time)
706 }
707
708 this.bSetStyleCSS = function(time){
709 self.setStyleCSS(time)
710 }
711
712 this.bComplete = function(){
713 self.complete()
714 }
715 },
716
717 start: function(to){
718
719 this.stop()
720
721 if (this.duration === 0){
722 this.cancel(to)
723 return this
724 }
725
726 var fromTo = prepare(this.node, this.property, to)
727
728 if (!fromTo){
729 this.cancel(to)
730 return this
731 }
732
733 this.to = fromTo[1]
734 // setting transition styles immediately will make good browsers behave weirdly
735 // because DOM changes are always deferred, so we requestFrame
736 this.cancelSetTransitionCSS = requestFrame(this.bSetTransitionCSS)
737
738 return this
739 },
740
741 setTransitionCSS: function(time){
742 delete this.cancelSetTransitionCSS
743 this.resetCSS(true)
744 // firefox flickers if we set css for transition as well as styles at the same time
745 // so, other than deferring transition styles we defer actual styles as well on a requestFrame
746 this.cancelSetStyleCSS = requestFrame(this.bSetStyleCSS)
747 },
748
749 setStyleCSS: function(time){
750 delete this.cancelSetStyleCSS
751 var duration = this.duration
752 // we use setTimeout instead of transitionEnd because some browsers (looking at you foxy)
753 // incorrectly set event.propertyName, so we cannot check which animation we are canceling
754 this.cancelComplete = setTimeout(this.bComplete, duration)
755 this.endTime = time + duration
756 this.set(this.to)
757 },
758
759 complete: function(){
760 delete this.cancelComplete
761 this.resetCSS()
762 this.callback(this.endTime)
763 },
764
765 stop: function(hard){
766 if (this.cancelExit){
767 this.cancelExit()
768 delete this.cancelExit
769 } else if (this.cancelSetTransitionCSS){
770 // if cancelSetTransitionCSS is set, means nothing is set yet
771 this.cancelSetTransitionCSS() //so we cancel and we're good
772 delete this.cancelSetTransitionCSS
773 } else if (this.cancelSetStyleCSS){
774 // if cancelSetStyleCSS is set, means transition css has been set, but no actual styles.
775 this.cancelSetStyleCSS()
776 delete this.cancelSetStyleCSS
777 // if its a hard stop (and not another start on top of the current animation)
778 // we need to reset the transition CSS
779 if (hard) this.resetCSS()
780 } else if (this.cancelComplete){
781 // if cancelComplete is set, means style and transition css have been set, not yet completed.
782 clearTimeout(this.cancelComplete)
783 delete this.cancelComplete
784 // if its a hard stop (and not another start on top of the current animation)
785 // we need to reset the transition CSS set the current animation styles
786 if (hard){
787 this.resetCSS()
788 this.set(this.get())
789 }
790 }
791 return this
792 },
793
794 resetCSS: function(inclusive){
795 var rules = compute(this.node),
796 properties = rules(transitionName + "Property").replace(/\s+/g, "").split(","),
797 durations = rules(transitionName + "Duration").replace(/\s+/g, "").split(","),
798 equations = rules(transitionName + "TimingFunction").replace(/\s+/g, "").match(/cubic-bezier\([\d-.,]+\)/g)
799
800 remove3("all", properties, durations, equations)
801 remove3(this.hproperty, properties, durations, equations)
802
803 if (inclusive){
804 properties.push(this.hproperty)
805 durations.push(this.duration + "ms")
806 equations.push("cubic-bezier(" + this.equation + ")")
807 }
808
809 var nodeStyle = this.node.style
810
811 nodeStyle[transitionName + "Property"] = properties
812 nodeStyle[transitionName + "Duration"] = durations
813 nodeStyle[transitionName + "TimingFunction"] = equations
814 },
815
816 parseEquation: function(equation){
817 if (typeof equation === "string") return CSSAnimation.parent.parseEquation.call(this, equation, true)
818 }
819
820})
821
822// elements methods
823
824var BaseAnimation = transitionName ? CSSAnimation : JSAnimation
825
826var moofx = function(x, y){
827 return (typeof x === "function") ? fx(x) : elements(x, y)
828}
829
830elements.implement({
831
832 // {properties}, options or
833 // property, value options
834 animate: function(A, B, C){
835
836 var styles = A, options = B
837
838 if (typeof A === "string"){
839 styles = {}
840 styles[A] = B
841 options = C
842 }
843
844 if (options == null) options = {}
845
846 var type = typeof options
847
848 options = type === "function" ? {
849 callback: options
850 } : (type === "string" || type === "number") ? {
851 duration: options
852 } : options
853
854 var callback = options.callback || function(){},
855 completed = 0,
856 length = 0
857
858 options.callback = function(t){
859 if (++completed === length) callback(t)
860 }
861
862 for (var property in styles){
863
864 var value = styles[property],
865 property = camelize(property)
866
867 this.forEach(function(node){
868 length++
869 var self = elements(node), anims = self._animations || (self._animations = {})
870 var anim = anims[property] || (anims[property] = new BaseAnimation(node, property))
871 anim.setOptions(options).start(value)
872 })
873 }
874
875 return this
876
877 },
878
879 // {properties} or
880 // property, value
881 style: function(A, B){
882
883 var styles = A
884
885 if (typeof A === "string"){
886 styles = {}
887 styles[A] = B
888 }
889
890 for (var property in styles){
891 var value = styles[property],
892 set = setter(property = camelize(property))
893
894 this.forEach(function(node){
895 var self = elements(node), anims = self._animations, anim
896 if (anims && (anim = anims[property])) anim.stop(true)
897 set.call(node, value)
898 })
899 }
900
901 return this
902
903 },
904
905 compute: function(property){
906
907 property = camelize(property)
908 var node = this[0]
909
910 // return default matrix for transform, instead of parsed (for consistency)
911
912 if (property === "transform" && parseTransform2d) return compute(node)(transformName)
913
914 var value = getter(property).call(node)
915
916 // unit conversion to `px`
917
918 return (value != null) ? value.replace(rgLength, function(match, value, unit){
919 return (unit === "px") ? match : pixelRatio(node, unit) * value + "px"
920 }) : ''
921
922 }
923
924})
925
926moofx.parse = function(property, value, normalize){
927 return (parsers[camelize(property)] || parse)(value, normalize)
928}
929
930module.exports = moofx