Shrinking Header in Ionic

Creating a Shrinking Header for Segments in Ionic



·

I was browsing the Ionic Forums earlier this week, and came across a post asking if it was possible to create a layout in Ionic where a header would be initially displayed above a segment, that would shrink away as the user scrolled (leaving the segment selections now sitting at the top of the screen). This gif was given as an example.

I’ve already built a component to create similar functionality recently when I wrote Creating a Custom Expandable Header Component for Ionic. That component was to be added to the header area of the application and it would start out expanded initially and display a couple of inputs, as the user scrolled the header area would shrink and the inputs would disappear.

That component will just about already do exactly what we need for this functionality, we will just be making a couple of tweaks to that and applying it in a different context. As such, I won’t spend too much time in this tutorial talking about some of the specifics of the way the component works, I’ll let you read the previous tutorial if you are interested in that, we will just focus on building out the solution.

Here’s what it will look like when we are done:

Shrinking Header Segment

Before We Get Started

Last updated for Ionic 3.1.0

Before you go through this tutorial, you should have at least a basic understanding of Ionic concepts. You must also already have Ionic set up on your machine.

If you’re not familiar with Ionic already, I’d recommend reading my Ionic Beginners Guide or watching my beginners series first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic, then take a look at Building Mobile Apps with Ionic.

1. Generate a New Ionic Application

We’re going to start by generating a new blank Ionic application, to do that just run the following command:

ionic start ionic-shrinking-segment-header blank

Once that has finished generating, you should make it your working directory by running the following command:

cd ionic-shrinking-segment-header blank

We will just need to generate a single component for this application, so let’s do that now by running the following command:

ionic g component ShrinkingSegmentHeader

In order to be able to use that component throughout our application, we will need to add it to our app.module.ts file.

Modify src/app/app.module.ts to reflect the following:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

import { ShrinkingSegmentHeader } from '../components/shrinking-segment-header/shrinking-segment-header';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    ShrinkingSegmentHeader
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

2. Implement the Shrinking Header Component

This component is actually really quite simple, and it’s even simpler than the component we used in the previous tutorial because we don’t need logic to handle hiding inputs at certain thresholds.

We will start off with the template, which will just contain a simple content projection.

Modify src/components/shrinking-segment-header/shrinking-segment-header.html to reflect the following:

<ng-content></ng-content>

All this will do is project whatever we add inside of the component into the component’s template. Again, if you’re interested in learning more about the details of this, I’d recommend reading the previous tutorial.

In order for the header to display properly, we will just need to add a block display style to the .scss file for the component.

Modify src/components/shrinking-segment-header/shrinking-segment-header.scss to reflect the following:

shrinking-segment-header {

    display: block;

}

Now let’s move on to the main part of the component.

Modify src/components/shrinking-segment-header/shrinking-segment-header.ts to reflect the following:

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

@Component({
  selector: 'shrinking-segment-header',
  templateUrl: 'shrinking-segment-header.html'
})
export class ShrinkingSegmentHeader {

  @Input('scrollArea') scrollArea: any;
  @Input('headerHeight') headerHeight: number;

  newHeaderHeight: any;

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

  }

  ngAfterViewInit(){

    this.renderer.setElementStyle(this.element.nativeElement, 'height', this.headerHeight + 'px');

    this.scrollArea.ionScroll.subscribe((ev) => {
      this.resizeHeader(ev);
    });

  }

  resizeHeader(ev){

    ev.domWrite(() => {

      this.newHeaderHeight = this.headerHeight - ev.scrollTop;

      if(this.newHeaderHeight < 0){
        this.newHeaderHeight = 0;
      }   

      this.renderer.setElementStyle(this.element.nativeElement, 'height', this.newHeaderHeight + 'px');

    });

  }

}

Although this uses the same logic as the previous tutorial, I will recap quickly. This component accepts two inputs: a headerHeight that will define how big the header area should be, and a scrollArea that the component will listen to for scroll events and resize accordingly.

In the ngAfterViewInit function we set up the component by setting the initial height, as supplied by the input, and we set up a listener for the scroll events that will call resizeHeader every time a scroll event occurs. That’s all there is to it!

3. Use the Shrinking Header Component

Now that we have the component defined, we just need to use it in conjunction with a segment. We will also add a bit of styling to make things look a little nicer.

Modify src/pages/home/home.html to reflect the following:

<ion-header>
    <ion-navbar color="dark">
        <ion-title>
          Profile
        </ion-title>
        <ion-buttons right>
            <button ion-button icon-only><ion-icon name="beer"></ion-icon></button>
            <button ion-button icon-only><ion-icon name="bicycle"></ion-icon></button>
            <button ion-button icon-only><ion-icon name="boat"></ion-icon></button>
        </ion-buttons>
    </ion-navbar>

    <shrinking-segment-header [scrollArea]="myContent" headerHeight="150">
        <img src="https://avatars.io/instagram/pathmoretravelled" />
        <h2>Josh</h2>
    </shrinking-segment-header>

    <ion-toolbar color="light" mode="md">
      <ion-segment color="dark" mode="md" [(ngModel)]="section">
        <ion-segment-button value="one">
          <ion-icon name="home"></ion-icon>
        </ion-segment-button>
        <ion-segment-button value="two">
          <ion-icon name="cog"></ion-icon>
        </ion-segment-button>
        <ion-segment-button value="three">
          <ion-icon name="chatbubbles"></ion-icon>
        </ion-segment-button>
      </ion-segment>
    </ion-toolbar>

</ion-header>

<ion-content fullscreen #myContent>

    <div [ngSwitch]="section">

      <ion-list *ngSwitchCase="'one'">
        <ion-item *ngFor="let something of somethings">
          <ion-thumbnail item-left>
            <img src="https://avatars.io/instagram/unknown">
          </ion-thumbnail>
          <h2>Something</h2>
        </ion-item>
      </ion-list>

      <ion-list *ngSwitchCase="'two'">
        <ion-item *ngFor="let something of somethings">
          <ion-thumbnail item-left>
            <img src="https://avatars.io/instagram/unknown">
          </ion-thumbnail>
          <h2>Something</h2>
        </ion-item>
      </ion-list>

      <ion-list *ngSwitchCase="'three'">
        <ion-item *ngFor="let something of somethings">
          <ion-thumbnail item-left>
            <img src="https://avatars.io/instagram/unknown">
          </ion-thumbnail>
          <h2>Something</h2>
        </ion-item>
      </ion-list>

    </div>

</ion-content>

For the most part, this template is just a normal segment with three different cases and a bunch of dummy content. However, we have added the <shrinking-segment-header> component above the segment in the header, and we have supplied that with an input of the headerHeight we want to use and the element that it should listen to for scroll events (which is the <ion-content> area that we added the #myContent template variable to).

We will need to add a couple of styles to this to get it looking how we want.

Modify src/pages/home/home.scss to reflect the following:

page-home {

    shrinking-segment-header {

        background-color: #363636;
        color: #fff;
        display: flex;
        align-items: center;
        justify-content: center;

        img {
            border-radius: 50%;
            width: 75px;
            height: auto;
            margin-right: 15px;
        }

    }



}

Finally, we just need to implement the TypeScript file for the Home Page.

Modify src/pages/home/home.ts to reflect the following:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

    section: string = 'two';
    somethings: any = new Array(20);

    constructor(public navCtrl: NavController) {

    }

}

This file is quite simple as well. We just set up the variable being used to switch between our various segments and a somethings array that we are just using to create a bunch of dummy data inside an *ngFor loop in the template.

If you run the code in your browser now, you should have a fully functional shrinking header component above your segments.

Summary

If you’ve read both of the tutorials related to this component, then you would likely notice that there are very few differences between the two implementations. That’s the benefit of a well-designed component, it can just about be dragged and dropped into any application and used in a variety of different circumstances.

What to watch next...

  • Paulo Ruca

    Awesome man
    Menu header like whatsapp

  • Andrés Felipe Chaparro

    Hi Josh, thanks for this great tutorial, it’s really amazing, can you please guide me how to solve it when I have a list of ions ion inputs, what happens is when I select an ion input scroll up to much to show the keyboard, so the input is hidden and does’nt scroll down when you start typing in the input. How can i handle this?… and again thanks. https://uploads.disquscdn.com/images/a124e9e22c6d9140e6247cfb85c7d80a4e0af8714ce19bcf0a5219eed5540cf6.png https://uploads.disquscdn.com/images/f6375c419fa335aa86f859e7325f555ce10cc0534f21eb4ab739829075e05746.png https://uploads.disquscdn.com/images/b166bb785ac40b964f58e77c03be8915d2830ac208642c11e6bd6e875946d578.png

    • Chanoch

      Andres, if you want someone to look at this, you might need to share your code. It’s not obvious what you are trying to achieve and might be solved by trying a different way of editing the entry. Is there any reason why you need to edit in the same page as it appears you are doing above?

      Chanoch

      • Andrés Felipe Chaparro

        Hi @chanoch:disqus , Thank you for your response, so the code it’s the same, the only differences is that insteand of using ion-thumbnail on the ‘three’ segment I use a ion-list of ion-inputs. Here is where I have in that segment:

        Email

        Name1

        Name2

        Name3

        Name4

        Name5

        Name6

        Name7

        Name8

        (Sorry I do’nt know how to create a codepen whit the shrinking-header explained here).
        You can see that is nothing rare on this segments, but the problem comes when I tapped on a ion-input they scroll up to much that is hidding behaind the io-header and segments.

        This is have to see whit the variation of the shrinking-header, or any div whit position relative I think.

  • Kausttubh

    Hi josh, thank for the tutorial, It fulfilled my 70% requirement. In remaining 30% i want to show the name ‘Josh’ on the navbar replacing profile when user scrolls up and the header is hidden. I tried output and event emitter it dint work. How can i achieve this ? thanks in advace

  • Saud

    Thank you for the great tutorial as always. I followed your steps and everything seems to work great except when switching between segments it doesn’t maintain the space. There will always be space on underneath segments buttons. I tried debugging the issue but I couldn’t really figure out what was causing this to happen.

    • Saud

      I think my issue is scroll-content padding-top, although it works fine when scrolling within the same segment but when switching segment there is a gap between tool bar and content. I’m thinking about setting padding-top whenever segmentChanged is fires but not sure if I’m doing too much here.

      • Kévin

        Same problem for me. What can we do ?

      • Kévin

        Simply add a click event in your template

        declare a Viewchild in your page and thats it
        @ViewChild(Content) content: Content;

      • Jonas Bakker

        Is there another way, where you don’t scroll to top. I find the sudden scroll change to be quite disruptive in the user experience.

  • Reda Makhchan

    Thanks for this Tutorial =) worked for me.

  • Salvatore Alessi

    I get this error:
    “Cannot read property ‘ionScroll’ of undefined”

  • Matheus Pereira

    I’m really thankful for this tutorial and all content you make available on ionic.
    Maybe you can add the style ‘overflow: hidden;’ to the ‘shrinking-segment-header’. This ensures that the content does not exceed the component limit.

  • Hi Josh, thanks for the useful tutorial. I thought I copied the code line for line however I get a blank space where the header user to be – see : https://www.screencast.com/t/5DLraYPzj (Any ideas why?)

    • Hmm it seems to be the “fullscreen” attribute that fixes it. I can’t find much documentation on it.

  • Luca Palezza

    Hi! Great component! How can I apply the same concept to a tabs view?
    The tabs are included in the “content” area, but they don’t pass the right reference of scrolling to the component.
    Any suggestion?

  • Aaron camacho leandro

    hi, why in ios made an extra scroll ?

  • Kévin

    I like it but it stutters a lot even on powerful computers… is there a way to make it slide better, in my opinion it’s not production ready.
    I’ve found another bug when switching tabs a lot and fast with selected tab content is not bigger than the scrollable view.

    Thanks a lot anyway. but in my opinon, not Josh Morony’s valid performance wise..

    • Kévin

      Another issue is when a user is trying to scroll while his cursor is on top of a tab, it doesn’t work, one has to leave the tabs to scroll the view.

      🙂

  • naidu

    i am getting errors

    Runtime Error
    Uncaught (in promise): Error: Template parse errors: Can’t bind to ‘scrollArea’ since it isn’t a known property of ‘shrinking-segment-header’. 1. If ‘shrinking-segment-header’ is an Angular component and it has ‘scrollArea’ input, then verify that it is part of this module. 2. If ‘shrinking-segment-header’ is a Web Component then add ‘CUSTOM_ELEMENTS_SCHEMA’ to the ‘@NgModule.schemas’ of this component to suppress this message. 3. To allow any property add ‘NO_ERRORS_SCHEMA’ to the ‘@NgModule.schemas’ of this component. (” ][scrollArea]=”myContent” headerHeight=”150″> <img src="https://avatars.io/instagram/pathmoret&quot;): ng:///ApphomePageModule/[email protected]:28 'shrinking-segment-header' is not a known element: 1. If 'shrinking-segment-header' is an Angular component, then verify that it is part of this module. 2. If 'shrinking-segment-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. (" [ERROR ->] <img src="https://avat&quot;): ng:///ApphomePageModule/[email protected]:2 Error: Template parse errors: Can't bind to 'scrollArea' since it isn't a known property of 'shrinking-segment-header'. 1. If 'shrinking-segment-header' is an Angular component and it has 'scrollArea' input, then verify that it is part of this module. 2. If 'shrinking-segment-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. 3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. (" ][scrollArea]=”myContent” headerHeight=”150″> <img src="https://avatars.io/instagram/pathmoret&quot;): ng:///ApphomePageModule/[email protected]:28 'shrinking-segment-header' is not a known element: 1. If 'shrinking-segment-header' is an Angular component, then verify that it is part of this module. 2. If 'shrinking-segment-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. (" [ERROR ->] <img src="https://avat&quot;): ng:///ApphomePageModule/[email protected]:2 at syntaxError (http://localhost:8100/build/vendor.js:82589:34) at TemplateParser.parse (http://localhost:8100/build/vendor.js:93827:19) at JitCompiler._compileTemplate (http://localhost:8100/build/vendor.js:108021:39) at http://localhost:8100/build/vendor.js:107940:62 at Set.forEach () at JitCompiler._compileComponents (http://localhost:8100/build/vendor.js:107940:19) at http://localhost:8100/build/vendor.js:107827:19 at Object.then (http://localhost:8100/build/vendor.js:82578:143) at JitCompiler._compileModuleAndComponents (http://localhost:8100/build/vendor.js:107826:26) at JitCompiler.compileModuleAsync (http://localhost:8100/build/vendor.js:107755:37)

    Stack
    Error: Uncaught (in promise): Error: Template parse errors:
    Can’t bind to ‘scrollArea’ since it isn’t a known property of ‘shrinking-segment-header’.
    1. If ‘shrinking-segment-header’ is an Angular component and it has ‘scrollArea’ input, then verify that it is part of this module.
    2. If ‘shrinking-segment-header’ is a Web Component then add ‘CUSTOM_ELEMENTS_SCHEMA’ to the ‘@NgModule.schemas’ of this component to suppress this message.
    3. To allow any property add ‘NO_ERRORS_SCHEMA’ to the ‘@NgModule.schemas’ of this component. (”

    ][scrollArea]=”myContent” headerHeight=”150″>
    <img src="https://avatars.io/instagram/pathmoret&quot;): ng:///ApphomePageModule/[email protected]:28
    'shrinking-segment-header' is not a known element:
    1. If 'shrinking-segment-header' is an Angular component, then verify that it is part of this module.
    2. If 'shrinking-segment-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("

    [ERROR ->]
    <img src="https://avat&quot;): ng:///ApphomePageModule/[email protected]:2
    Error: Template parse errors:
    Can't bind to 'scrollArea' since it isn't a known property of 'shrinking-segment-header'.
    1. If 'shrinking-segment-header' is an Angular component and it has 'scrollArea' input, then verify that it is part of this module.
    2. If 'shrinking-segment-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
    3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("

    ][scrollArea]=”myContent” headerHeight=”150″>
    <img src="https://avatars.io/instagram/pathmoret&quot;): ng:///ApphomePageModule/[email protected]:28
    'shrinking-segment-header' is not a known element:
    1. If 'shrinking-segment-header' is an Angular component, then verify that it is part of this module.
    2. If 'shrinking-segment-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("

    [ERROR ->]
    <img src="https://avat&quot;): ng:///ApphomePageModule/[email protected]:2
    at syntaxError (http://localhost:8100/build/vendor.js:82589:34)
    at TemplateParser.parse (http://localhost:8100/build/vendor.js:93827:19)
    at JitCompiler._compileTemplate (http://localhost:8100/build/vendor.js:108021:39)
    at http://localhost:8100/build/vendor.js:107940:62
    at Set.forEach ()
    at JitCompiler._compileComponents (http://localhost:8100/build/vendor.js:107940:19)
    at http://localhost:8100/build/vendor.js:107827:19
    at Object.then (http://localhost:8100/build/vendor.js:82578:143)
    at JitCompiler._compileModuleAndComponents (http://localhost:8100/build/vendor.js:107826:26)
    at JitCompiler.compileModuleAsync (http://localhost:8100/build/vendor.js:107755:37)
    at c (http://localhost:8100/build/polyfills.js:3:19132)
    at c (http://localhost:8100/build/polyfills.js:3:18841)
    at http://localhost:8100/build/polyfills.js:3:19613
    at t.invokeTask (http://localhost:8100/build/polyfills.js:3:15040)
    at Object.onInvokeTask (http://localhost:8100/build/vendor.js:4238:33)
    at t.invokeTask (http://localhost:8100/build/polyfills.js:3:14961)
    at r.runTask (http://localhost:8100/build/polyfills.js:3:10214)
    at o (http://localhost:8100/build/polyfills.js:3:7274)
    at

    • Chris Cardinal

      Make sure you declare your component in src/app/app.module.ts