skip to Main Content

I built a landing site using Angular. To make it SEO friendly, I had to do prerender it.

The landing page starts with an intro animation (using CSS’ animation).

When I first load the page/full reload, the animation starts, and in the middle the app is being bootstrapped so ot restarts the animation again.

I was wondering if there’s a way to prevent the animations to reoccur. I know that there are few questions that might be similar to mine, but none of them helped me.

I have tried to solve this issue by:

  1. Adding initialNavigation in AppRoutingModule:
@NgModule({
  imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabled'})],
  exports: [RouterModule]
})
export class AppRoutingModule {
}
  1. AddingTransferHttpCacheModule and BrowserTransferStateModule to AppModule, and ServerTransferStateModule to AppServerModule.

4

Answers


  1. You could do it, however it might not be the answer you’re looking for.

    First let’s see what happens when a SSR Angular app starts in the browser:

    1. The browser receives the server-side rendered page. The HTML is rendered, including your animation, and it starts playing. Then at the bottom of the page, the angular script tags are parsed, and the JS for the app starts downloading.

    2. While the app is downloading, the animation is playing.

    3. When the app is downloaded, it bootstraps, and when it’s bootstrapped, it renders the whole DOM again, including your animation. During this process, the old animated elements are removed from the DOM, and then new animated elements are added. The animation starts playing the second time.

    You can go around this in 2 ways:

    • either don’t start the animation until the app is loaded on the client (add the animation with a custom class when the app runs in a browser)

    • or move the animation outside of your app, embed it manually to the index.html – this way when the SSR re-render happens it will not be affected. (this way you can’t have the element inside your <app-root></app-root> elements)

    Hope this helps!

    Login or Signup to reply.
  2. Have you tried to save any indication of initial loading, for example with cookies as suggested.
    And then wrap the animated element with [@.disabled]="indication", it should look like this:

    <div [@.disabled]="isInitialized">
        <div @someAnimation>
            ...
        </div>
    </div>
    
    Login or Signup to reply.
  3. It’s actually fairly simple – all you need to do is:

    • get your initial (server side generated) page url
    • store it in the TransferState
    • when client page is reloaded, check if the url is the same
    • if so, fast forward animation immediately right to the end
    • ensure it’s only done for the initial navigation (as the server- to client-rendered transition only occurs once)

    I’ve created a simple directive for this purpose. Just put it on any element that has a css animation e.g.

    <div class="slideInAnimation" ssrPreventAnimationReplay></div>
    

    Here it is:

    @Directive({
        selector: '[ssrPreventAnimationReplay]'
    })
    export class ServerSidePreventAnimationReplayDirective { 
    
        static key = makeStateKey('ssrPreventAnimationReplay');
        static subscription: Subscription;
    
        constructor(
            private el: ElementRef,
            private transferState: TransferState,
            private router: Router,
            private renderer: Renderer2,
            @Inject(PLATFORM_ID) private platformId) {
        }
    
        ngOnInit() {
            if (isPlatformServer(this.platformId))
                this.transferState.set(ServerSidePreventAnimationReplayDirective.key, { url: this.router.url });
    
            else {
                let value = this.transferState.get(ServerSidePreventAnimationReplayDirective.key, null);
                if (value && value.url == this.router.url) {
                    this.preventAnimation();
                    this.cancelOnNavigation();
                }
            }
        }
    
        ngOnDestroy() {
            if (ServerSidePreventAnimationReplayDirective.subscription)
                ServerSidePreventAnimationReplayDirective.subscription.unsubscribe();
        }
    
        private preventAnimation() {
            this.renderer.setStyle(this.el.nativeElement, 'animation-duration', '0ms');
            this.renderer.setStyle(this.el.nativeElement, 'animation-delay', '0ms');
        } 
    
        private cancelOnNavigation() {
            ServerSidePreventAnimationReplayDirective.subscription = this.router.events.subscribe(async event => {
                if (event instanceof NavigationStart)
                    this.transferState.remove(ServerSidePreventAnimationReplayDirective.key);
            });
        }
    }
    

    You can disable angular animations in a similar fashion.

    Hope it’s of help to someone.

    Login or Signup to reply.
  4. Import NoopAnimationsModule to the app.server.module.ts

    @NgModule({
      imports: [
        NoopAnimationsModule,
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search