I'm working on an Angular 18 project where I want to animate a button when it comes into view using the IntersectionObserver API and it works.
However, if only half of the top button is in view, it starts flickering, which is the problem I am trying to solve.
Here's my component code:
export class FooterComponent implements AfterViewInit {
@ViewChild('invite_btn') protected invite_btn!: ElementRef<HTMLAnchorElement>;
ngAfterViewInit() {
const observer: IntersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.invite_btn.nativeElement.classList.add('animate__animated', 'animate__fadeInUp');
} else {
this.invite_btn.nativeElement.classList.remove('animate__animated', 'animate__fadeInUp');
}
});
});
observer.observe(this.invite_btn.nativeElement);
}
}
I tried to use an entry.IntersectionRatio
check but that didn't help, because the ratio is always below 1.0
.
How can I fix that? Video example:
Based on the animation CSS classes I assume that you are using Animate.css or something similar.
The flickering appears to be caused by the translate transform applied by the fadeInOut animation.
IntersectionObserver
detects that the button has entered the viewporttransform: translate3d(0px, 100%, 0px);
which moves the element out of the viewport.IntersectionObserver
detects that the button has exited the viewportWe now have an infinite loop.
To circumvent the issue, you can wrap the button in a container element and observe it with IntersectObserver
instead of the button itself.
<!--
Template of the FooterComponent
The fadeInUp animation applies a translate transform on the button which causes isIntersecting to rapidly switch between true/false.
To workaround that we introduce a container element.
We'll use it to test for intersection instead of the button.
-->
<div #invite_btn_container>
@if(isIntersecting()) {
<!--
Remove the button completely when it is not intersecting.
Otherwise the button jumps down at the start of the animation.
-->
<a #invite_btn class="animate__animated animate__fadeInUp">Invite</a>
}
</div>
export class FooterComponent implements AfterViewInit {
@ViewChild('invite_btn_container')
protected invite_btn_container!: ElementRef<HTMLElement>;
isIntersecting = signal(false);
ngAfterViewInit() {
const observer: IntersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
// Use a signal to make sure that Change Detection is triggered.
// Alternatively, use a regular boolean property and call ChangeDetectorRef.markForCheck()
this.isIntersecting.set(entry.isIntersecting);
});
}
);
// Observe the button container for intersection, instead of the button itself
observer.observe(this.invite_btn_container.nativeElement);
}
}
Hope that helps :)