I have been experimenting with Angular and SVGs. After much success (SVG has changed my life), I have encountered an interesting head-scratcher: I have an inline SVG in my root app component that has a router outlet embedded in a foreignObject,
<!-- app.component.html -->
<main>
<svg viewBox="0 0 900 1600">
<defs>
<clipPath
id="viewport"
clipPathUnits="userSpaceOnUse">
<rect
width="178.36444"
height="294.26218"
ry="49.872025"/>
</clipPath>
</defs>
<!--other stuff: e.g. circles, paths, etc.-->
<foreignObject
width="178.36444"
height="294.26218"
x="29.946405"
y="24.862501"
clip-path="url(#viewport)">
<router-outlet>
</router-outlet>
</foreignObject>
</svg>
</main>
The router works fine (which is extremely cool), however I am having trouble with animations that are coming from the routed components. The '/home' component, for example, has another inline SVG with an animation attached to it,
<!-- home.component.html -->
<section>
<svg viewBox="0 0 508 285.75"
preserveAspectRatio="xMaxYMax meet">
<!-- other stuff -->
<path
[@cntl_revolve]="revolveCntl.state"
d="...path..."
id="revolver-lower"
class="revolver" />
</svg>
</section>
Where the cntl_revolve animation trigger is defined through a static factory method,
// animations.ts
import {
animate,
animateChild,
AnimationTriggerMetadata,
keyframes,
query,
state,
style,
transition,
trigger,
} from '@angular/animations';
export enum AnimationTriggers {
cntl_revolve = 'cntl_revolve',
// ...
}
export enum AnimationPeriods {
// ..
minute = 60,
five = 300,
// ..
}
function formatTriggerTag(trigger: string, tag: string | null | undefined) {
return tag ? `${trigger}_${tag}` : trigger;
}
// ...
export class Animations{
// ...
public static getManualRevolveTrigger(
reversed: boolean = false,
tag: string | null | undefined = null,
animateLength: number = AnimationPeriods.minute
): AnimationTriggerMetadata {
let triggerTag: string = formatTriggerTag(
AnimationTriggers.cntl_revolve,
tag
);
let reversal: string = reversed ? '-' : '';
return trigger(triggerTag, [
state(
'on',
style({
transform: `rotate(${reversal}360deg)`,
})
),
state(
'off',
style({
transform: 'rotate(0deg)',
})
),
transition('on <=> off', [
animate(`${animateLength}s`),
query('@*', animateChild(), { optional: true }),
]),
]);
}
The animation is kicked off in the HomeComponent's AfterViewInit,
// home.component.ts
import { Component } from '@angular/core';
import {
AnimationControl,
AnimationPeriods,
Animations,
AnimationTriggers
} from 'src/animations';
const REVOLVE_DURATION = AnimationPeriods.five;
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
animations:[
Animations.getManualRevolveTrigger(false, null, REVOLVE_DURATION),
]
})
export class HomeComponent{
public revolveCntl: AnimationControl = new AnimationControl(
AnimationTriggers.cntl_revolve
);
constructor(){}
ngAfterViewInit(){
this.revolve();
}
private revolve(): void {
if(!this.revolveCntl.fired()){
this.revolveCntl.animate();
}
else{
this.revolveCntl.prime();
}
setTimeout(()=>{
this.revolve();
}, REVOLVE_DURATION * 1020);
}
}
Where AnimationControl is a simple wrapper class that has a state that toggles between 'on' and 'off' when animate() and prime() are called, respectively.
When the user is first routed to the component, the animation doesn't fire. However, if I resize the page, or interact with the page in anyway (i.e. clicking on a clickable div, toggling controls, etc), the animation begins. I've tried starting the animation in the OnInit and constructor, but I get the same behavior. I have added console.logs to ngAfterViewInit and the revolve methods, so I know it is definitely being called when the component is created. The accompanying animation just doesn't start for some reason.
I have verified the animation works as intended if it is not rendered through the foreignObject's router-outlet, i.e. if I lay down an <app-home></app-home> then the animation will kick off as soon as the component is created. I assume something funky is happening between Angular's rendering engine and the draw order of the SVG; I have noticed if I put an SVG object on top of the foreignObject it will initially get drawn on top (as expected), but as soon as I jimmy the animation into starting, the nested animation in the HomeComponent gets redrawn on top of everything.
I am not sure I understand enough about SVGs or how Angular actually works to understand this problem currently. I am curious what is going on, though. Can anyone provide an explanation that won't break my brain? How can I get my animation to start when the page loads?