我想有一个基于ng-if
javascript的动画,动画元素离开。但是,如果控制ng-if
的布尔值在元素离开DOM之前变回true
,我希望重用现有的元素,而不是创建一个新元素。
这个问题可以用一个简单的动画来说明,这个动画除了占用时间之外什么都不做:
app.animation('.toggle', function($window) {
return {
leave: function(element, done) {
$window.setTimeout(done, 2000);
}
};
});
与HTML类似
<button ng-click="toggle = !toggle">Toggle me a lot!</button>
<p ng-if="toggle" class="toggle">Should only ever be one of these</p>
如果您可以快速连续多次对具有toggle
类的元素执行ng-if
条件,那么其中几个将在DOM中结束。是否有可能在任何时候DOM中最多只包含其中一个?
我的理由是,它感觉更期望(至少对我来说)已经在屏幕上的元素对状态的变化做出反应,从它在动画中的当前位置,而不是创建一个新的,好像以前的一个已经完全消失了。我的实际用例是使用Angular UI路由器视图,在状态之间来回移动会导致同一个模板多次出现在DOM中,但我希望这个问题的答案可以为更复杂的路由情况提供解决方案。
我意识到我可以使用ng-hide或ng-class,但我希望在动画结束时从DOM中删除元素。这也将(希望)使这个问题的答案更类似于UI路由情况,因为UI -view的行为更像ng-if,它在状态变化时被添加/删除到DOM中。
您可以在http://plnkr.co/edit/hvEshhqOiC31q9wSjtL1?p=preview
试试用ng-hide代替ng-if。来自:https://docs.angularjs.org/api/ng/directive/ngIf
ngIf指令基于{expression}移除或重新创建DOM树的一部分。如果赋给ngIf的表达式的值为false,则该元素将被从DOM中移除,否则该元素的克隆将被重新插入到DOM中。
所以如果你想防止DOM重复使用ng-hide,因为它只会设置之前存在的DOM元素(你的p标签)的显示为:none,当ng-hide的值为true时。
回复你的编辑:为什么不只是在没有动画的时候开火呢?http://plnkr.co/edit/WqtjvytwEfOjhwvfhgvF?p=preview
app.animation('.toggle:not(.ng-animate)', function($window) {
return {
leave: function(element, done) {
$window.setTimeout(done, 2000);
}
};
});
我破解了ng-if
的源代码,想出了一种使用类似指令的方法
- 如果离开动画被打断,则不重新创建元素
- 在动画结束之前不会破坏离开元素的作用域,所以如果离开被打断,所有的绑定/事件等仍然有效。
下面是指令的代码,我称之为animIf
。它不像ngIf
那样是多元素的,我强烈怀疑在某些情况下它是危险的,因为测试是有限的。
app.directive('animIf', function($animate) {
return {
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
link: function($scope, $element, $attr, ctrl, $transclude) {
var latestValue, block, childScope, enterPromise, leavePromise;
$scope.$watch($attr.animIf, function ngIfWatchAction(value) {
latestValue = value;
if (value) {
if (leavePromise) {
// Cancelling leaving animation
// still removes the element from the DOM,
// so we immediately put it back in
$animate.cancel(leavePromise);
leavePromise = null;
enterPromise = $animate.enter(block.clone, $element.parent(), $element);
enterPromise.then(function() {
enterPromise = null;
});
} else if (!childScope) {
// New clone to be created + injected
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end animIf: ' + $attr.animIf + ' ');
block = {
clone: clone
};
enterPromise = $animate.enter(clone, $element.parent(), $element);
enterPromise.then(function() {
enterPromise = null;
});
});
}
} else {
if (enterPromise) {
$animate.cancel(enterPromise);
enterPromise = null;
}
if (block) {
leavePromise = $animate.leave(block.clone);
leavePromise.then(function() {
leavePromise = null;
if (!latestValue && childScope) {
// Scope is only destroyed at the end of the animation
// This is different to how ngIf works, where it is destroyed
// at the beginning
if (childScope) {
childScope.$destroy();
childScope = null;
}
block = null;
}
});
}
}
});
}
};
});
为了确保它实际上使一个真正的动画成为可能,我已经集成了GSAP的TweenMax,对于一个动画,在进入到离开时是不同的,但如果它被打断,那么它会逆转到原来的位置。
app.animation('.toggle', function(TweenMax) {
function reverseOrClear(element) {
if (element[0]._toggleTween) {
var tween = element[0]._toggleTween;
tween.reversed(!tween.reversed());
} else {
element[0]._toggleTween = null;
}
}
function onComplete(element, done) {
element[0]._toggleTween = null;
done();
}
return {
enter: function(element, done) {
function enterComplete() {
onComplete(element, done);
}
// Not Using .data since data seems to be removed from element when
// it is removed from the DOM
element[0]._toggleTween = element[0]._toggleTween
|| TweenMax.from(element, 1, {opacity: 0, y: 200, onComplete: enterComplete, onReverseComplete: enterComplete});
return function() {
reverseOrClear(element);
};
},
leave: function(element, done) {
function leaveComplete() {
onComplete(element, done);
}
element[0]._toggleTween = element[0]._toggleTween
|| TweenMax.to(element, 1, {opacity: 0, y: -200, onComplete: leaveComplete, onReverseComplete: leaveComplete});
return function() {
reverseOrClear(element);
};
}
};
});
这可以在http://plnkr.co/edit/ZkylJwkesu6sztin6ZDB?p=preview
看到我怀疑在很多情况下,在进入和离开时使用相同但反向的动画会更好,但这只是上述代码的特殊情况。
一种方法是根本不集成ngAnimate,只使用一个指令来处理ngif风格的添加/删除DOM和动画。代码更少,感觉更灵活,因为你不局限于ngAnimate可以做什么,它更清晰,因为有更少的移动部分。
下面是一个与GSAP集成的示例
app.directive('animIf', function(TweenMax) {
return {
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
link: function($scope, $element, $attr, ctrl, $transclude) {
var latestValue, latestClone, childScope, tween;
var firstTime = true;
function onEnterComplete() {
tween = null;
}
function onLeaveComplete() {
tween = null;
if (!latestValue) {
childScope.$destroy();
childScope = null;
latestClone.remove();
latestClone = null;
}
}
$scope.$watch($attr.animIf, function ngIfWatchAction(value) {
latestValue = value;
if (tween) {
tween.reversed(!tween.reversed());
} else if (value) {
if (!childScope) {
$transclude(function(clone, newScope) {
latestClone = clone;
childScope = newScope;
$element.after(clone);
// Just like ngAnimate, don't animate elements initially.
// Could be configurable if needed
if (!firstTime) {
// Hard coded style for this example, but could get from attributes, style sheets...
tween = TweenMax.from(latestClone, 1, {opacity: 0, y: 200, onComplete: onEnterComplete, onReverseComplete: onLeaveComplete});
}
});
}
} else if (childScope) {
tween = TweenMax.to(latestClone, 1, {opacity: 0, y: -200, onComplete: onLeaveComplete, onReverseComplete: onEnterComplete});
}
firstTime = false;
});
}
};
});
可以在http://plnkr.co/edit/IY2icRQt4dsmVVbsnduj?p=preview