Permalink
Comparing changes
Open a pull request
- 2 commits
- 8 files changed
- 0 commit comments
- 2 contributors
Commits on Jan 25, 2016
…ations The new prepare class is added before the animation is pushed to the queue and removed before the animation runs, i.e. it is immediately available when a structural animation (enter, leave, move) is initialized. The class can be used to apply CSS to explicitly hide these elements to prevent a flash of content before the animation runs. This can happen if a structural animation (such as ng-if) sits at the bottom of a tree which has ng-class animations on the parents. Because child animations are spaced out with requestAnimationFrame, the ng-enter class might not be applied in time, so the ng.if element is briefly visible before its animation starts.
This simple tip can help to diagnose the error. Closes #12958
Unified
Split
Showing
with
130 additions
and 1 deletion.
- +4 −1 docs/content/error/$injector/modulerr.ngdoc
- +31 −0 docs/content/guide/animations.ngdoc
- +1 −0 src/ngAnimate/.jshintrc
- +10 −0 src/ngAnimate/animation.js
- +28 −0 src/ngAnimate/module.js
- +1 −0 src/ngAnimate/shared.js
- +19 −0 test/ngAnimate/animationSpec.js
- +36 −0 test/ngAnimate/integrationSpec.js
| @@ -6,6 +6,9 @@ | ||
| This error occurs when a module fails to load due to some exception. The error | ||
| message above should provide additional context. | ||
|
|
||
| A common reason why the module fails to load is that you've forgotten to | ||
| include the file with the defined module or that the file couldn't be loaded. | ||
|
|
||
| ### Using `ngRoute` | ||
|
|
||
| In AngularJS `1.2.0` and later, `ngRoute` has been moved to its own module. | ||
| @@ -24,4 +27,4 @@ angular.module('ng').filter('tel', function (){}); | ||
|
|
||
| Instead create your own module and add it as a dependency to your application's top-level module. | ||
| See [#9692](https://github.com/angular/angular.js/issues/9692) and | ||
| [#7709](https://github.com/angular/angular.js/issues/7709) for more information | ||
| [#7709](https://github.com/angular/angular.js/issues/7709) for more information | ||
| @@ -274,6 +274,37 @@ myModule.directive('my-directive', ['$animate', function($animate) { | ||
| }]); | ||
| ``` | ||
|
|
||
| ## Preventing flicker before an animation starts | ||
|
|
||
| When nesting elements with structural animations such as `ngIf` into elements that have class-based | ||
| animations such as `ngClass`, it sometimes happens that before the actual animation starts, there is a brief flicker or flash of content | ||
| where the animated element is briefly visible. | ||
|
|
||
| To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as an animation is initialized, | ||
| but removed before the actual animation starts (after waiting for a $digest). This class is only added for *structural* | ||
| animations (`enter`, `move`, and `leave`). | ||
|
|
||
| Here's an example where you might see flickering: | ||
|
|
||
| ```html | ||
| <div ng-class="{red: myProp}"> | ||
| <div ng-class="{blue: myProp}"> | ||
| <div class="message" ng-if="myProp"></div> | ||
| </div> | ||
| </div> | ||
| ``` | ||
|
|
||
| It is possible that during the `enter` event, the `.message` div will be briefly visible before it starts animating. | ||
| In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: | ||
|
|
||
| ```css | ||
| .message.ng-enter-prepare { | ||
| opacity: 0; | ||
| } | ||
|
|
||
| /* Other animation styles ... */ | ||
| ``` | ||
|
|
||
| ## More about animations | ||
|
|
||
| For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}. | ||
| @@ -29,6 +29,7 @@ | ||
| "REMOVE_CLASS_SUFFIX": false, | ||
| "EVENT_CLASS_PREFIX": false, | ||
| "ACTIVE_CLASS_SUFFIX": false, | ||
| "PREPARE_CLASS_SUFFIX": false, | ||
|
|
||
| "TRANSITION_DURATION_PROP": false, | ||
| "TRANSITION_DELAY_PROP": false, | ||
| @@ -135,6 +135,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { | ||
| options.tempClasses = null; | ||
| } | ||
|
|
||
| var prepareClassName; | ||
| if (isStructural) { | ||
| prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; | ||
| $$jqLite.addClass(element, prepareClassName); | ||
| } | ||
|
|
||
| animationQueue.push({ | ||
| // this data is used by the postDigest code and passed into | ||
| // the driver step function | ||
| @@ -357,6 +363,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) { | ||
| if (tempClasses) { | ||
| $$jqLite.addClass(element, tempClasses); | ||
| } | ||
| if (prepareClassName) { | ||
| $$jqLite.removeClass(element, prepareClassName); | ||
| prepareClassName = null; | ||
| } | ||
| } | ||
|
|
||
| function updateAnimationRunners(animation, newRunner) { | ||
| @@ -253,6 +253,34 @@ | ||
| * the CSS class once an animation has completed.) | ||
| * | ||
| * | ||
| * ### The `ng-[event]-prepare` class | ||
| * | ||
| * This is a special class that can be used to prevent unwanted flickering / flash of content before | ||
| * the actual animation starts. The class is added as soon as an animation is initialized, but removed | ||
| * before the actual animation starts (after waiting for a $digest). | ||
| * It is also only added for *structural* animations (`enter`, `move`, and `leave`). | ||
| * | ||
| * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` | ||
| * into elements that have class-based animations such as `ngClass`. | ||
| * | ||
| * ```html | ||
| * <div ng-class="{red: myProp}"> | ||
| * <div ng-class="{blue: myProp}"> | ||
| * <div class="message" ng-if="myProp"></div> | ||
| * </div> | ||
| * </div> | ||
| * ``` | ||
| * | ||
| * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. | ||
| * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: | ||
| * | ||
| * ```css | ||
| * .message.ng-enter-prepare { | ||
| * opacity: 0; | ||
| * } | ||
| * | ||
| * ``` | ||
| * | ||
| * ## JavaScript-based Animations | ||
| * | ||
| * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared | ||
| @@ -21,6 +21,7 @@ var ADD_CLASS_SUFFIX = '-add'; | ||
| var REMOVE_CLASS_SUFFIX = '-remove'; | ||
| var EVENT_CLASS_PREFIX = 'ng-'; | ||
| var ACTIVE_CLASS_SUFFIX = '-active'; | ||
| var PREPARE_CLASS_SUFFIX = '-prepare'; | ||
|
|
||
| var NG_ANIMATE_CLASSNAME = 'ng-animate'; | ||
| var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; | ||
| @@ -513,6 +513,25 @@ describe('$$animation', function() { | ||
| expect(captureLog[1].element).toBe(child); | ||
| expect(captureLog[2].element).toBe(grandchild); | ||
| })); | ||
|
|
||
|
|
||
| they('should add the preparation class before the $prop-animation is pushed to the queue', | ||
| ['enter', 'leave', 'move'], function(animationType) { | ||
| inject(function($$animation, $rootScope, $animate) { | ||
| var runner = $$animation(element, animationType); | ||
| expect(element).toHaveClass('ng-' + animationType + '-prepare'); | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| they('should remove the preparation class before the $prop-animation starts', | ||
| ['enter', 'leave', 'move'], function(animationType) { | ||
| inject(function($$animation, $rootScope, $$rAF) { | ||
| var runner = $$animation(element, animationType); | ||
| $rootScope.$digest(); | ||
| expect(element).not.toHaveClass('ng-' + animationType + '-prepare'); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("grouped", function() { | ||
| @@ -315,6 +315,42 @@ describe('ngAnimate integration tests', function() { | ||
| }); | ||
| }); | ||
|
|
||
| it('should add the preparation class for an enter animation before a parent class-based animation is applied', function() { | ||
| module('ngAnimateMock'); | ||
| inject(function($animate, $compile, $rootScope, $rootElement, $document) { | ||
| element = jqLite( | ||
| '<div ng-class="{parent:exp}">' + | ||
| '<div ng-if="exp">' + | ||
| '</div>' + | ||
| '</div>' | ||
| ); | ||
|
|
||
| ss.addRule('.ng-enter', 'transition:2s linear all;'); | ||
| ss.addRule('.parent-add', 'transition:5s linear all;'); | ||
|
|
||
| $rootElement.append(element); | ||
| jqLite($document[0].body).append($rootElement); | ||
|
|
||
| $compile(element)($rootScope); | ||
| $rootScope.exp = true; | ||
| $rootScope.$digest(); | ||
|
|
||
| var parent = element; | ||
| var child = element.find('div'); | ||
|
|
||
| expect(parent).not.toHaveClass('parent'); | ||
| expect(parent).toHaveClass('parent-add'); | ||
| expect(child).not.toHaveClass('ng-enter'); | ||
| expect(child).toHaveClass('ng-enter-prepare'); | ||
|
|
||
| $animate.flush(); | ||
| expect(parent).toHaveClass('parent parent-add parent-add-active'); | ||
| expect(child).toHaveClass('ng-enter ng-enter-active'); | ||
| expect(child).not.toHaveClass('ng-enter-prepare'); | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| it('should pack level elements into their own RAF flush', function() { | ||
| module('ngAnimateMock'); | ||
| inject(function($animate, $compile, $rootScope, $rootElement, $document) { | ||