我不知道这是否是一个好的解决方案,但这是我能想象的唯一解决方案。我的想法是拥有绝对位置的 div 的“副本”,并保持不透明度 = 0 或可见性 = 隐藏的“原始”。当更改数组时,包含在 setTimeout 中,制作一个手动动画来更改“副本”的顶部和左侧。
在代码中。假设你有一个数组数据
data:any[]=[1,2,3,4,5]
一个像这样的html
<div >
<ng-container *ngFor="let item of data;let i=index">
<div #origin style="opacity:0" >{{item}}</div>
<div #copy style="position:absolute" [style.z-index]="i" >{{item}}</div>
</ng-container>
</div>
我们在 ViewChildren 中获取“原点”和 ngAfterViewInit 中的“副本”,我们将“副本”放置在他的位置
@ViewChildren("origin") bars: QueryList<ElementRef>;
@ViewChildren("copy") copies: QueryList<ElementRef>;
ngAfterViewInit()
{
const bars:any[]=this.bars.toArray().map(x=>x.nativeElement.getBoundingClientRect());
this.copies.forEach((x,index) => {
x.nativeElement.style.top=bars[index].top+"px"
x.nativeElement.style.left=bars[index].left+"px"
});
}
好吧,只需单击一下,我就可以重新排序数组并制作动画
click() {
this.data=this.data
.map(x=>({item:x,value:Math.random()}))
.sort((a,b)=>a.value-b.value)
.map(x=>x.item)
setTimeout(()=>{
const bars:any[]=this.bars.toArray().map
(x=>x.nativeElement.getBoundingClientRect());
this.copies.forEach((x,index) => {
this.animate(x.nativeElement, bars[index].top+"px",bars[index].left+"px");
});
})
}
animate(element: any, top: string,left:string) {
const myAnimation = this.builder.build([
animate(this.timing, style({ top: top,left:left }))
]);
this.player = myAnimation.create(element);
this.player.play();
}
您可以在堆栈闪电战 https://stackblitz.com/edit/angular-s4nw4h?file=src%2Fapp%2Fapp.component.ts
Update为什么不制定指令?
Update 2我使用 offsetTop 和 offsetLeft 更新指令并添加 window.scrollX 和 window.scrollY。此外,我添加了一个新属性:pos0,如果为 true,则首先在 pos 0,0 中创建“副本”
@Directive({ selector: "[animate]" })
export class AnimateDirective implements OnInit {
original: any;
copy: any;
timing:string;
private player: AnimationPlayer;
@Input() set animate(value: string) {
this.timing = value || "450ms ease-in-out";
}
@Input('animatePos0') pos0:boolean=false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private builder: AnimationBuilder,
private renderer: Renderer2
) {}
ngOnInit() {
this.original = this.viewContainer.createEmbeddedView(
this.templateRef
).rootNodes[0];
setTimeout(() => {
this.copy = this.viewContainer.createEmbeddedView(
this.templateRef
).rootNodes[0];
this.renderer.setStyle(this.original, "visibility","hidden");
const rect = !this.pos0?
{top:this.original.offsetTop,left:this.original.offsetLeft}:
{top:0,left:0};
this.renderer.setStyle(this.copy, "position", "absolute");
this.renderer.setStyle(this.copy, "top", rect.top+ window.scrollY + "px");
this.renderer.setStyle(this.copy, "left", rect.left+ window.scrollX + "px");
});
}
animateGo() {
setTimeout(() => {
const rect = {top:this.original.offsetTop,left:this.original.offsetLeft}
const myAnimation = this.builder.build([
animate(this.timing,
style({ top: rect.top+ window.scrollY,
left: rect.left+ window.scrollX }))
]);
this.player = myAnimation.create(this.copy);
this.player.play();
});
}
}
好吧,我们需要一些类似的东西
<ng-container *ngFor="let item of data;let i=index">
<div *animate="'150ms ease-in-out'"> {{item}}-{{i}}
</div>
</ng-container>
在我们的组件中,有一个 ViewChildren
@ViewChildren(AnimateDirective) items:QueryList<AnimateDirective>
所以,当我们改变数据时,我们需要
this.items.forEach(x=>x.animateGo())
See a 新堆栈闪电战 https://stackblitz.com/edit/angular-xv78gq?file=src%2Fapp%2Fanimate.directive.ts