OLD | NEW |
(Empty) | |
| 1 part of angular.animate; |
| 2 |
| 3 /** |
| 4 * The optimizer tracks elements and running animations. It's used to control |
| 5 * and optionally skip certain animations that are deemed "expensive" such as |
| 6 * running animations on child elements while the dom parent is also running an |
| 7 * animation. |
| 8 */ |
| 9 @Injectable() |
| 10 class AnimationOptimizer { |
| 11 final Map<dom.Element, Set<Animation>> _elements = new Map<dom.Element, |
| 12 Set<Animation>>(); |
| 13 final Map<Animation, dom.Element> _animations = new Map<Animation, |
| 14 dom.Element>(); |
| 15 |
| 16 final Map<dom.Node, bool> _alwaysAnimate = new Map<dom.Node, bool>(); |
| 17 final Map<dom.Node, bool> _alwaysAnimateChildren = new Map<dom.Node, bool>(); |
| 18 |
| 19 Expando _expando; |
| 20 |
| 21 AnimationOptimizer(this._expando); |
| 22 |
| 23 /** |
| 24 * Track an animation that is running against a dom element. Usually, this |
| 25 * should occur when an animation starts. |
| 26 */ |
| 27 void track(Animation animation, dom.Element forElement) { |
| 28 if (forElement != null) { |
| 29 var animations = _elements.putIfAbsent(forElement, () => |
| 30 new Set<Animation>()); |
| 31 animations.add(animation); |
| 32 _animations[animation] = forElement; |
| 33 } |
| 34 } |
| 35 |
| 36 /** |
| 37 * Stop tracking an animation. If it's the last tracked animation on an |
| 38 * element forget about that element as well. |
| 39 */ |
| 40 void forget(Animation animation) { |
| 41 var element = _animations.remove(animation); |
| 42 if (element != null) { |
| 43 var animationsOnElement = _elements[element]; |
| 44 animationsOnElement.remove(animation); |
| 45 // It may be more efficient just to keep sets around even after |
| 46 // animations complete. |
| 47 if (animationsOnElement.length == 0) { |
| 48 _elements.remove(element); |
| 49 } |
| 50 } |
| 51 } |
| 52 |
| 53 /** |
| 54 * Since we can't overload forget... |
| 55 */ |
| 56 void detachAlwaysAnimateOptions(dom.Element element) { |
| 57 _alwaysAnimate.remove(element); |
| 58 _alwaysAnimateChildren.remove(element); |
| 59 } |
| 60 |
| 61 /** |
| 62 * Control animation for a specific element, ignoring every other option. |
| 63 * [mode] "always" will always animate this element. |
| 64 * [mode] "never" will never animate this element. |
| 65 * [mode] "auto" will detect if a parent animation is running or has child a
nimations set. |
| 66 */ |
| 67 void alwaysAnimate(dom.Element element, String mode) { |
| 68 if (mode == "always") { |
| 69 _alwaysAnimate[element] = true; |
| 70 } else if (mode == "never") { |
| 71 _alwaysAnimate[element] = false; |
| 72 } else if (mode == "auto") { |
| 73 _alwaysAnimate.remove(element); |
| 74 } |
| 75 } |
| 76 |
| 77 /** |
| 78 * Control animation for child elements, ignoring running animations unless 'a
uto' is provided as an option. |
| 79 * [mode] "always" will always animate children, unless it is specifically m
arked not to by [alwaysAnimate]. |
| 80 * [mode] "never" will never animate children. |
| 81 * [mode] "auto" will detect if a parent animation is running or has child a
nimations set. |
| 82 */ |
| 83 void alwaysAnimateChildren(dom.Element element, String mode) { |
| 84 if (mode == "always") { |
| 85 _alwaysAnimateChildren[element] = true; |
| 86 } else if (mode == "never") { |
| 87 _alwaysAnimateChildren[element] = false; |
| 88 } else if (mode == "auto") { |
| 89 _alwaysAnimateChildren.remove(element); |
| 90 } |
| 91 } |
| 92 |
| 93 /** |
| 94 * Returns true if there is tracked animation on the given element. |
| 95 */ |
| 96 bool _isAnimating(dom.Element element) { |
| 97 return _elements.containsKey(element); |
| 98 } |
| 99 |
| 100 /** |
| 101 * Given all the information this optimizer knows about currently executing |
| 102 * animations, return [true] if this element can be animated in an ideal case |
| 103 * and [false] if the optimizer thinks that it should not execute. |
| 104 */ |
| 105 bool shouldAnimate(dom.Node node) { |
| 106 bool alwaysAnimate = _alwaysAnimate[node]; |
| 107 if (alwaysAnimate != null) { |
| 108 return alwaysAnimate; |
| 109 } |
| 110 |
| 111 // If there are 'always allow' or 'always prevent' animations declared, |
| 112 // fallback to the automatic detection of running parent animations. By |
| 113 // default, we assume that we can run. |
| 114 bool autoDecision = true; |
| 115 |
| 116 node = node.parentNode; |
| 117 while (node != null) { |
| 118 // Does this node give us animation information about our children? |
| 119 alwaysAnimate = _alwaysAnimateChildren[node]; |
| 120 if (alwaysAnimate != null) { |
| 121 return alwaysAnimate; |
| 122 } |
| 123 |
| 124 // If we hit a running parent animation, we still need to continue up |
| 125 // the dom tree to see if there is or is not an 'alwaysAnimateChildren' |
| 126 // decision somewhere. |
| 127 if (autoDecision |
| 128 && node.nodeType == dom.Node.ELEMENT_NODE |
| 129 && _isAnimating(node)) { |
| 130 // If there is an already running animation, don't animate. |
| 131 autoDecision = false; |
| 132 } |
| 133 |
| 134 // If we hit a null parent, try to break out of shadow dom. |
| 135 if (node.parentNode == null) { |
| 136 var probe = _findElementProbe(node); |
| 137 if (probe != null && probe.parent != null) { |
| 138 // Escape shadow dom! |
| 139 node = probe.parent.element; |
| 140 } else { |
| 141 // If we can't go any further, return the auto decision because we |
| 142 // havent hit any other more important optimizations. |
| 143 return autoDecision; |
| 144 } |
| 145 } else { |
| 146 node = node.parentNode; |
| 147 } |
| 148 } |
| 149 |
| 150 return autoDecision; |
| 151 } |
| 152 |
| 153 // Search and find the element probe for a given node. |
| 154 ElementProbe _findElementProbe(dom.Node node) { |
| 155 while (node != null) { |
| 156 if (_expando[node] != null) { |
| 157 return _expando[node]; |
| 158 } |
| 159 node = node.parentNode; |
| 160 } |
| 161 return null; |
| 162 } |
| 163 } |
OLD | NEW |