Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Feat: ng-[event]-prepare class for structural animations (ngIf etc) #13408

Merged
merged 1 commit into from
Dec 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/content/guide/animations.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down
1 change: 1 addition & 0 deletions src/ngAnimate/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions src/ngAnimate/animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
28 changes: 28 additions & 0 deletions src/ngAnimate/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,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
Expand Down
1 change: 1 addition & 0 deletions src/ngAnimate/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
19 changes: 19 additions & 0 deletions test/ngAnimate/animationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
36 changes: 36 additions & 0 deletions test/ngAnimate/integrationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,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) {
Expand Down