How to prevent css animation to reoccur when angular SSR bootstraps the app - css

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:
Adding initialNavigation in AppRoutingModule:
#NgModule({
imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabled'})],
exports: [RouterModule]
})
export class AppRoutingModule {
}
AddingTransferHttpCacheModule and BrowserTransferStateModule to AppModule, and ServerTransferStateModule to AppServerModule.

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:
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.
While the app is downloading, the animation is playing.
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!

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>

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.

Import NoopAnimationsModule to the app.server.module.ts
#NgModule({
imports: [
NoopAnimationsModule,

Related

change global css element

i've made this banner like screen that appears when my site is loaded, but here's the thing, i don't want no scrollbar while this opening animation it's happening, i only want to show the other components (the scrollbar and the whole site) once the gsap animation finishes, how could i proceed? thanks! (i tried to create a function to control those global elements, is it a way?)
So if I understand correctly you need the Banner to be displayed until the site is loaded. Maybe you are making some API calls or in general, you are planning to show the banner for let's say 3 sec and post that you want your actual components to be displayed.
You can try below approch:
export const APP = (): JSX.Element => {
const [isAnimationInProgress, SetAnimationState] = React.useState(true);
React.useEffect(() => {
// You can have your page load API calls done here
// Or wait for 'X' seconds
// Post that set the AnimationState to false to render actual components
setAnimationState(false);
})
return (
{
isAnimationInProgress && <Banner />
}
{
!isAnimationInProgress && <ActualComponent />
}
)
}
Regarding scrollbars, including overflow: hidden; in style for the banner should do the work if you are getting scrollbars for the Banner component.

Avatar animation sync issue in NAF (Network a-frame)

I have avatar with animations, like flying, floating, eyes, etc. and perform specific animation based on keycode mapping with specific animation.
to perform specific animation on specific key, i have created custom animation component as follow schema:
schema: {
keyCode: { default: stopAllAnimation }
}
and put that element and component in network schema:
{
selector: ".AvatarRoot",
component: "own-animation"
}
Locally/Individually it is working fine but in NAF I am facing the syncing issue like play specific animation not sync in other tab/screen.
Any one know how avatar animation works in network a-frame? and cloud be possible to sync perfectly.
You need to make things with a-frame schema and register component with a-frame extras included to make animation from gtlf/glb models.
The flow is like you need to set the register component with clip name in local JS file like this
let currentKeyDown = null;
document.addEventListener("keydown", (e) => {
currentKeyDown = e.which;
const avatar = document.querySelector("#local-avatar");
switch (currentKeyDown) {
case 37: // left arrow key
avatar.setAttribute("player-info", "clip:Walk");
break;
}
});
and in register component if previous clip and new clip are different set a new clip. like this
if (oldData.clip !== this.data.clip) {
this.character.setAttribute('animation-mixer', {clip: this.data.clip, crossFadeDuration: 1});
}
Maybe you can take this code for reference and I have added to glitch. Glitch walking demo

Lottie player do not update the underlying animation when src attribute is changed at run time?

I was using lottie player for animated sticker on my PWA app.
Now at run time when I change the src attribute of lottie player, it do not reloads a new animation. <lottie-player src=${state.url} background="transparent" speed="1" style="width: 100%; height: 100%;" loop autoplay></lottie-player>
is there a way to force this animation to reload by changing the attribute ?
I am using lit-html as my templating library and custom framework for creating web components.
The lottie web player requires a programmatic load method call to change the animation. Their api doesn't update the animation based on a runtime src property change.
Lit-html and web components are not required to answer the question, but I can provide a code sample using lit to show a working example.
Example changing animation of Lottie web player: https://lit.dev/playground/#gist=4569b4b023db7014d45112679059296b
See below for a minimal answer.
class ExampleChangingAnimation extends LitElement {
/**
* Get the rendered Lottie player element
*/
get lottiePlayer() {
return this.shadowRoot.querySelector('lottie-player');
}
/**
* changeAnimation gets the new animation src string and loads
* it onto the Lottie web player.
*/
changeAnimation () {
// Get new animation to load.
const newAnimation = "...animation url...";
// Load the new animation on the lottie player element.
this.lottiePlayer.load(newAnimation)
}
render () {
return html`
<lottie-player></lottie-player>
<button #click=${this.changeAnimation}>Change Animation</button>
`;
}
}

Dynamically switch global CSS for Angular8 app (client branding)

I want to dynamically switch Angulars global CSS files based on which client is connecting. This will be used for client-branding purposes, including fonts, colors, photos, headers, footers, button-styles, etc.
Each client has provided us with a CSS file, which we need to integrate into our app. We have hundreds of clients.
Current solution is to try and override the CSS of individual components at load. This is bad because it adds a lot of boilerplate:
Html:
<link id="theme" rel="stylesheet" href="./assets/stylesheets/{{cclientCode}}.css">
ts:
ngOnInit() {
this.service.clientCode.subscribe(clientCode => this.clientCode = clientCode);
}
My workaround isn't working because the link html is called before the {{}} has a chance to load in the value.
I'm also not motivated to fix my workaround because its just that -a workaround. Instead, I want to implement something that works globally, without any per-component boilerplate.
What I want is the ability to dynamically switch the global Angular style for each client. So something like:
"styles": [
"src/assets/stylesheets/angular_style.css",
"src/assets/stylesheets/client_style.css"
]
Where client_style.css is served differently to each client.
I've found a solution that I think is workable. It definitely has issues though, so if anyone has their own answer, please still share!
First, I added a clientCode String field to SessionDataService, a global service I use to move component-agnostic data around my app:
export class SessionDataService {
clientCode: BehaviorSubject<String>;
constructor(){
this.clientCode = new BehaviorSubject('client_default');
}
setClientCode(value: String) {
this.clientCode.next(value);
}
}
Then, inside app.component.ts, I added a BehaviorSubject listener to bring in the value of clientCode dynamically:
public clientCode: String;
constructor(private service : SessionDataService) {
this.service.clientCode.subscribe(clientCode => this.clientCode = clientCode);
}
Next, I added a wrapper around my entire app.component.html:
<div [ngClass]="clientCode">
--> ALL app components go here (including <router-outlet>)
</div>
So at this point, I've created a system that dynamically adds client-code CSS classes to my components, including all children :)
Finally, I just have to write CSS rules:
.ClientOne p {
color: red;
}
.ClientOne .btn {
background-color: red;
}
.ClientTwo.dashboard {
height: 15%;
}
I hope this helps somebody! Essentially the "trick" here is to add a ngClass that wraps the entire app, and then justify all client-specific CSS rules with their client code.

CSS Styling Angular 2+ (pain in the butt)

Ok, i am new to Angular 4. I am having issues with styling the application properly. I went ahead and added a background image to the body, this worked as expected, then added a component that would display content and it looked as expected.
I added a second component different route but for this one I do not want the body to have the background image at this point i am not sure what is the best practice.
I read a few articles and some say that you can have a different style for the body at the component level by adding a body style override in the styleUrls. I did this but everytime i went from say /myroute/page2 to myroute/page1 the background sticked to what i set for myroute/page2 while it should show the body image for /myroute/page1. I am also using ViewEncapsulation set to None (maybe this is the issue)?.
Also this is my setup and may be wrong too
index.html has something like this:
</head>
<body>
<app-root></app-root>
</body>
</html>
then my app.component.html has this:
<app-navigation></app-navigation>
<router-outlet></router-outlet>
<app-footer></app-footer>
and then i have my welcome.component.html that has a section
<section id="hero">
<div>
</div>
</section>
Now the component above shows the background set correctly, but if I navigate using something like
this.router.navigate(['/page2']);
the background stays the same as in my welcome component. If i refresh on /page2 the right background now shows up.
UPDATE:
Ok i gave up on showing the background on the second component, i wanted without background image, but want to keep the one in the first component. So i removed the ViewEncapsulation from all of them but now if I move the body {} to the first component it does not show it for some reason (I made sure it has the right path). Is there a better fix than removing it by using the DOM?. What is the best practice?.
You need to set style class on ngOnInit and remove it on ngOnDestroy.
constructor(private renderer: Renderer2) {
}
ngOnInit() {
this.renderer.addClass(document.body, 'your_class');
}
ngOnDestroy() {
this.renderer.removeClass(document.body, 'your_class');
}
This worked for me, pls try. In your component1,
ngOnInit() {
document.body.style.background="url(https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSu5rIAFQkkswZLuwAUCXZqc_8bBROGkGgmZP5bmGk57sRKXWJMEg)";
}
ngOnDestroy() {
document.body.style.background="";
}
In your component2,
ngOnInit() {
document.body.style.background="url(http://gkreading.com/wp-content/uploads/2017/03/awesome-kid-in-the-grass.jpg)";
}
ngOnDestroy() {
document.body.style.background="";
}
Hope this helps.

Resources