Stretchy High Performance Parallax Animation Header in Ionic 3

How to build Stretched header effect with high-performance parallax directive for Ionic 3 using the system of cards and dynamic images

In this tutorial, I am going to walk through the process of building a high-performance parallax directive step by step for Ionic 3 that work with the system of Cards. This tutorial is based on the tutorial made by Joshua Morony.

Before We Get Started

We will start from scratch, we will generate the application with Ionic CLI, then we will walk through how to create and add the directive to our page that contains cards.

NOTE: At the time of writing this, Ionic 3.20.0 is used

How will it work?

Before going into how to build this directive, let's discuss first how the parallax header is going to work. Ideally, we want this to be simple, and require the least configuration possible.

Of course, we need some way to define what image is going to be used for the parallax header. In order to do that, this is the syntax we are going to use in our templates when making use of this directive.


<ion-content parallax-header>

<div class="header-image"></div>

<ion-card class="main-content">

<p>Content goes here</p>

</ion-card >

</ion-content>

All we will need to do is adding the parallax-header directive to the <ion-content> element, then we define the parallax header image using div element and the content area using ion-card element.

We will need to define a background image statically or dynamically (we will see both) using CSS on the header-image class, and place any content inside of the ion-card.

Now that we agreed on the way we want it to work, let’s get started.

1. Generate a New Ionic 3 Project

We’re going to start by generating a new blank Ionic project, to do that, run the following command.

$ ionic start ionic-parallax-header blank

Once the project is generated, access to the application directory by running the following command to make it your working directory.

$ cd ionic-parallax-header

Next, we will generate our page that will contain the Parallax Header, to do that, run the following command.

$ ionic g page parallax

2. Create the Parallax Header Directive

We are going to start off by creating a new directive using the Ionic CLI generate command, then we will implement it.

Use the following command to generate the directive.

$ ionic g directive ParallaxHeader

This will generate the directive in the directives folder for us inside src directory, no more configurations is required, we just need to import the directive module inside the page module that will use it, for our case we will import it in parallax.module.ts and add it to the imports array.

Modify src/pages/parallax/parallax.module.ts to reflect the following.


import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { ParallaxPage } from './parallax';
import { DirectivesModule } from '../../directives/directives.module';

@NgModule({
    declarations: [
        ParallaxPage,
    ],
    imports: [
        IonicPageModule.forChild(ParallaxPage),
        DirectivesModule,
    ],
})
export class ParallaxPageModule {}

Now we are ready to move to the next step.

3. Implement the Parallax Header Directive

Now let’s implement the logic of our directive. We are going to set it up in its entirety first, and then explain how it works.

Modify src/directives/parallax-header/parallax-header.ts.


import { Directive, ElementRef, Renderer } from '@angular/core';

@Directive({
    selector: '[parallax-header]',
    host: {
    '(ionScroll)': 'onContentScroll($event)',
    '(window:resize)': 'onWindowResize($event)'
    }
})
export class ParallaxHeaderDirective {

    header: any;
    headerHeight: any;
    translateAmt: any;
    scaleAmt: any;

    constructor(public element: ElementRef, public renderer: Renderer){

    }

    ngOnInit(){

        let content = this.element.nativeElement.getElementsByClassName('scroll-content')[0];
        console.log("scroll-content: ", content)

        this.header = content.getElementsByClassName('header-image')[0];
        console.log("header-image: ", this.header);

        let mainContent = content.getElementsByClassName('main-content')[0];
        console.log("main-content: ", mainContent);

        this.headerHeight = this.header.clientHeight;

        this.renderer.setElementStyle(this.header, 'webkitTransformOrigin', 'center bottom');
        this.renderer.setElementStyle(this.header, 'background-size', '100% 100%');

    }

    onWindowResize(ev){
        console.log("resize");
        this.headerHeight = this.header.clientHeight;
    }

    onContentScroll(ev){

        console.log("scroll");
        ev.domWrite(() => {
        this.updateParallaxHeader(ev);
        });

    }

    updateParallaxHeader(ev){

        if(ev.scrollTop >= 0){
            this.translateAmt = ev.scrollTop / 4;
            this.scaleAmt = 1;
            } else {
            this.translateAmt = 0;
            this.scaleAmt = -ev.scrollTop / this.headerHeight + 1;
        }

        this.renderer.setElementStyle(this.header, 'webkitTransform', 'translate3d(0,'+this.translateAmt+'px,0) scale('+this.scaleAmt+','+this.scaleAmt+')');

    }

}

Let’s first take a look at our decorator definition.


@Directive({
    selector: '[parallax-header]',
    host: {
    '(ionScroll)': 'onContentScroll($event)',
    '(window:resize)': 'onWindowResize($event)'
    }
})

We define a selector for the parallax-header attribute, which allows us to attach the directive to an element like we did with <ion-content>, and then we set up the host property with an object.

The host property will listen for output from the parent component, that is the component that the directive is attached to. So, if we are adding this directive to <ion-content> then this will allow us to listen for output coming from <ion-content>. In our case, we are listening for the ionScroll event, and when it gets fired it will trigger the onContentScroll function in our directive. We also listen for the resize event and trigger the onWindowResize event the same way.

Now let’s take a look at the ngOnInit function:


ngOnInit(){

    let content = this.element.nativeElement.getElementsByClassName('scroll-content')[0];
    console.log("scroll-content: ", content)

    this.header = content.getElementsByClassName('header-image')[0];
    console.log("header-image: ", this.header);

    let mainContent = content.getElementsByClassName('main-content')[0];
    console.log("main-content: ", mainContent);

    this.headerHeight = this.header.clientHeight;

    this.renderer.setElementStyle(this.header, 'webkitTransformOrigin', 'center bottom');
    this.renderer.setElementStyle(this.header, 'background-size', '100% 100%');

}

This function is automatically executed when the DOM is ready, and we use it to define some variables, and also to set up some initial styling on certain elements. We set up a reference to the content area, and then we use that to get references to the header-image and main-content that we will define in the template.

We also set some styling we need for the parallax effect to work correctly by the use of the Renderer.

Let’s move on.


onWindowResize(ev){
    console.log("resize");
    this.headerHeight = this.header.clientHeight;
}

onContentScroll(ev){

    console.log("scroll");
    ev.domWrite(() => {
        this.updateParallaxHeader(ev);
    });
}

These are the two functions that we set up the listeners for in host. The first is quite simple; to have the parallax effect work correctly we need to know the height of the header area. When the screen size changes, this will affect the headers size, so we update the height properly when we detect a resize event.

The onContentScroll function is triggered whenever we receive a scroll event, and this function contains the most important logic in the entire directive. We must call the updateParallaxHeader() function to apply the parallax effect as the content area scrolls, but instead of calling it directly, we use domWrite function to call it

.

This domWrite function is one of the major improvements that the Ionic team has implemented, which (along with the updated ionScroll event) allows us to achieve such smooth animation performance even on an old Android device.

Using the ionScroll event, rather than setting up a normal scroll listener, ensures that the values will be read from the DOM at the correct time such that they don’t cause jankiness in the scrolling. When attempting to read values like this you can trigger a layout, which is a process the browser performs to calculate the size and location of elements on the page, which can be a costly operation. Using ionScroll ensures that unnecessary layouts aren’t triggered.

Similarly, domWrite ensures that anything that needs to be written to the DOM is done at the most opportune time. This is similar in theory to using requestAnimationFrame for animations, which essentially allows you to tell the browser “I want to make this update, do it at the best time” rather than “I want to make this update, DO IT NOW!”. However, the domWrite function allows all the components from the app to queue up writes to the DOM, and it will perform those writes in the most efficient way possible. It will handle performing all DOM reads from across the app first, and then all of the DOM writes.

This organisation is what allows for the smooth performance. So, if you are writing to the DOM, make sure to do it inside of domWrite. Not doing so would be like slamming your brakes on the freeway, you’ll disturb the nice harmonious flow of traffic.

Now, let’s examine the final function.


updateParallaxHeader(ev){

    if(ev.scrollTop >= 0){
    this.translateAmt = ev.scrollTop / 4;
    this.scaleAmt = 1;
    } else {
    this.translateAmt = 0;
    this.scaleAmt = -ev.scrollTop / this.headerHeight + 1;
    }

    this.renderer.setElementStyle(this.header, 'webkitTransform', 'translate3d(0,'+this.translateAmt+'px,0) scale('+this.scaleAmt+','+this.scaleAmt+')');

}

This is the function we are calling inside of domWrite, and as you can see it aims to update the header element in the DOM. This function calculates the new values required for the parallax effect (based on the user scroll) and then applies them.

4. Add the Correct CSS

Add the following CSS to the template that is making use of the directive.


.header-image {
    height: 60vh;
    z-index: -5;
    background-repeat: no-repeat;
}

.main-content {
    padding: 0px;
}

We will set up a background image for the header directly on the template so we can have a dynamic and a static image, so if we are using a REST API we can change the image based on the request response, and then also give it a height of 60 of the viewport height. You can use your own values as well. Using the parallax header means we can’t use Ionic’s built in padding utility attribute because it would also add padding to the header image, which we don’t want, so you may also want to add your own padding to the new main-content area.

5. Add the Directive to the Template

Finally, all you have to do is adding the directive to <ion-content> and create the appropriate div and ion-card elements.


<ion-header>
    <ion-navbar>
      <ion-title></ion-title>
    </ion-navbar>
</ion-header>

<ion-content parallax-header>
  
    <div class="header-image" [ngStyle]="{ 'background-image': 'url('+backgroundImg()+')'}"> </div>

    <ion-card class="main-content">
    <ion-card-content>
      <ion-card-title>
        <button ion-button clear large color ="secondary" icon-start>
          <ion-icon class="icon-36" name='home' item-start></ion-icon>
            <b>Home</b>
        </button>
      </ion-card-title>
      <p>
        Parallax Card Title
      </p>
      <p> Some Dummy Text</p>
      <p>
        Very long text
      </p>   
    </ion-card-content>
    <ion-row no-padding>
      <ion-col>
        <button ion-button clear small color="danger" icon-start>
          <ion-icon name='clock'></ion-icon>
          Clock
        </button>
      </ion-col>
      
      <ion-col text-right>
        <button ion-button clear small color="danger" icon-start>
          <ion-icon name='stats'></ion-icon>
          Statistics
        </button>
      </ion-col>
    </ion-row>

  </ion-card>
</ion-content>

As we have pointed before, we are setting up dynamically the background-image using [ngStyle] Angular built-in directive, where backgroundImg() is a function that return a dynamic image from an API if it exist or a static one in assets folder.

This is the content of backgroundImg() function that need to be added to src/pages/parallax/parallax.ts.


backgroundImg() {

    try{
        this.bgImg = this.resource.dynamicImage;
    }
    catch(e) {
        if(e.name === 'ReferenceError') {
        console.log('var is undeclared')
        }
        this.bgImg = "../../assets/imgs/defaultImg.jpg";
    }

    return this.bgImg;
}

You have to add a bunch of dummy content if you want to scroll.

That’s it; this is the way if you want to implement parallax Header within an Ionic Page that contains ion-card elements.

IMPORTANT: If you want to run this on iOS you should install the WKWebView plugin otherwise scroll events won’t be received whilst the list is scrolling (resulting in a wonky header).

Summary

I hope that this tutorial helped you to understand how important it is to consider performance when building your applications. You could simply build this directive without using ionScroll and domWrite, and it would work, but you will lose on performance. It would be obvious, and understandable, to come to the conclusion that Ionic is slow based on that poor performance, when in fact the poor performance is due to poor design.

Besides it was a good opportunity to talk about performance, and some of the concepts that surround that, the parallax directive is also a cool and nice to have feature that you can implement in your apps.

Name

Angular,7,Angular 8,1,Best Practices,1,Design,1,Firebase,1,Ionic,1,Java,5,Nodejs,2,Python,1,Restful API,1,Software Development,1,Spring,3,Spring Batch,1,Spring Boot 2,1,Web Development,1,
ltr
item
Programming Tutorials, News and Reviews: Stretchy High Performance Parallax Animation Header in Ionic 3
Stretchy High Performance Parallax Animation Header in Ionic 3
How to build Stretched header effect with high-performance parallax directive for Ionic 3 using the system of cards and dynamic images
Programming Tutorials, News and Reviews
https://www.ninjadevcorner.com/2018/09/stretchy-header-high-performance-parallax-animation-in-ionic-3.html
https://www.ninjadevcorner.com/
https://www.ninjadevcorner.com/
https://www.ninjadevcorner.com/2018/09/stretchy-header-high-performance-parallax-animation-in-ionic-3.html
true
493653397416713395
UTF-8
Loaded All Posts Not found any posts VIEW ALL Readmore Reply Cancel reply Delete By Home PAGES POSTS View All RECOMMENDED FOR YOU LABEL ARCHIVE SEARCH ALL POSTS Not found any post match with your request Back Home Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sun Mon Tue Wed Thu Fri Sat January February March April May June July August September October November December Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec just now 1 minute ago $$1$$ minutes ago 1 hour ago $$1$$ hours ago Yesterday $$1$$ days ago $$1$$ weeks ago more than 5 weeks ago Followers Follow THIS CONTENT IS PREMIUM Please share to unlock Copy All Code Select All Code All codes were copied to your clipboard Can not copy the codes / texts, please press [CTRL]+[C] (or CMD+C with Mac) to copy