Angular Scroll-Animation Directive

Federicorudolf
4 min readJul 7, 2020

A couple of weeks ago, I was struggling with a project that required to animate a component for a website I was developing, which was instanciated several times.

After doing some research (with questionable results), I decided to give it a try ‘without any help’ which lead me to come up with the solution I’m about to explain.

What I was looking for was something that would sort of make a parallax effect while scrolling, but that could also be used to apply any other effect.

My setup was an Angular 9 app, with lazy loaded modules, and really (I mean, really) long components. They had to mostly show text (a report-like site), and some images/graphs.

So with that in mind, I wanted a solution that wouldn’t overload components, as they were already packed with data so I decided to create a new directive.

Let’s get directly to the code and its explanation.

First of all, you need to create a new directive, either running

ng g d yourDirectiveName

in the terminal (if you have Angular CLI installed), or else create a new file yourDirectiveName.directive.ts.

Once the file is created, we need some imports to make this work

import { Directive, ElementRef, NgModule, Renderer2, OnInit } from '@angular/core';

Before moving along, let me mention something about this Renderer2 class that we import from ‘@angular/core’. This is an abstraction provided by Angular which brings methods to style our component instance (among other things), without having to deal with the DOM.
We’ll be using the setStyle() method provided by it, but there’s a hole pack of alternatives you could use, depending on what you want to achieve here.

Once we have those imports you need to declare some properties within your directive (or create the whole directive if you’re not using the cli).

@Directive({selector: '[appTranslateObject]'})export class TranslateObjectDirective implements OnInit {elementPosition: number;scrollHeight: number;distance: number;constructor(private element: ElementRef, private renderer: Renderer2) {
}
}

We’re going to have those five properties:

  • elementPosition: This will store the position of our element within our file (explanation later on).
  • scrollHeight: This will store the window’s full height.
  • distance: This one will store distance between the other two.
  • element: This is the instance of the element we want to style
  • renderer: This is an instance of the class provided by Angular

After this, we will declare two functions, as follows

ngOnInit(): void {document.addEventListener('scroll', () => {this.scrollHeight = window.innerHeight;this.elementPosition = this.element.nativeElement.getBoundingClientRect().top;this.distance = this.elementPosition - this.scrollHeight;if (this.distance < 0) {this.setStyle();}});}setStyle() {this.renderer.setStyle(this.element.nativeElement,'transform',`translateY(${-this.distance / 10}px)`);}

We need to declare the ngOnInit() method, and we’ll attach a scroll listener inside it, which will update the element’s position as the user scrolls through the site. To do that, we need to call getBoundingClientRect() which returns an object with the following properties:

  1. bottom: number
  2. height: number
  3. left: number
  4. right: number
  5. top: number
  6. width: number
  7. x: number
  8. y: number

I won’t be explaining all of them, for this example we’ll just use the top property that will give us the element’s distance to the top of the page.

We’ll calculate the difference between the element’s distance to top and the window height, and when that returns a negative number, we can assure that the element has entered the viewport.

When difference is negative, we can call the setStyle() method provided by the renderer class to apply styles to our element.

This method takes a couple of arguments

abstract setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void
  • el: Here we need to pass our rendered element (in my case this.element.nativeElement).
  • style: This one needs to be the CSS style property (I went for transform)
  • value: Here, as the name says, we need to provide the value for the style we want to change (as you saw before, I passed another calculation translateY(${-this.distance / 10}px))

Lastly, what you need to do is call that directive from the element you want to style, like this:

<div appTranslateObject> Translate me! </div>

And import / export the directive in the component’s module:

// This is the module that exports the directive (TranslateDirectiveModule)@NgModule({declarations: [ TranslateObjectDirective ],exports: [ TranslateObjectDirective ]})
// This is the module that will use that directive@NgModule({imports: [TranslateDirectiveModule,],})

Basically, what my code does is, when the element appears on screen and the user is scrolling, it will have a sort of parallax effect to it, and will stay longer in screen.

Here’s the full directive code:

import { Directive, ElementRef, NgModule, Renderer2, OnInit } from '@angular/core';@Directive({selector: '[appTranslateObject]'})export class TranslateObjectDirective implements OnInit {elementPosition: number;scrollHeight: number;distance: number;constructor(private element: ElementRef, private renderer: Renderer2) {}ngOnInit(): void {document.addEventListener('scroll', () => {this.scrollHeight = window.innerHeight;this.elementPosition = this.element.nativeElement.getBoundingClientRect().top;this.distance = this.elementPosition - this.scrollHeight;if (this.distance < 0) {this.setStyle();}});}setStyle() {this.renderer.setStyle(this.element.nativeElement,'transform',`translateY(${-this.distance / 10}px)`);}}

Hope it works!

Thanks for reading!

Fede

--

--

Federicorudolf

Web developer @ LaunchpadLab (https://launchpadlab.com/) . Tech passionate. Economy enthusiast