Hero Header Component (ala Plex on iOS)

I’ve been working on a “Hero” header component that gets bigger when you scroll. Similar to the Plex app - when you are on the movie detail screen and you pull down from the top, the hero background gets bigger instead of scrolling the content. Scrolling up scrolls the content normally.

image

Here is the code, in case anyone has any feedback or a better way to do it…

example page.ts:

import { ViewChild, Component } from '@angular/core';

import { Content } from 'ionic-angular';

import { Subject } from 'rxjs/Rx';

@Component({
  selector: 'page',
  template: '<ion-content><hero [photo]="assets/img/photo.png" [background]="assets/img/background.png" [title]="Title" [scroll]="scroll"><br /><h1>Test Page</h1></ion-content>'
})
export class JarsPage {

  @ViewChild(Content) content: Content;

  scroll = new Subject();

  ionViewDidEnter() {

    this.content.ionScroll.subscribe(data => this.scroll.next(data.scrollTop));
  }
}

hero.ts:

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

import { Subject } from 'rxjs/Rx';
@Component({
  selector: 'hero',
  templateUrl: 'hero.html',
})
export class HeroComponent {

  @Input() title: string;
  @Input() background: string;
  @Input() photo: string;
  @Input() scroll: Subject<{}>;

  photo_style = { transform: 'scale(1,1)' };
  bg_style = { transform: 'scale(1.1,1.1)' };

  constructor(private ref: ChangeDetectorRef) { }

  ngOnInit() {

    this.scroll.subscribe((height: number) => {

      if (height === 0) return; // Sometimes scroll returns 0 on iOS, ignore it

	  if (height > 0) height = 0;
	  if (height < -50) height = -50;
	  height = height / -100;
	  this.photo_style.transform = 'scale('+(1+height)+','+(1+height)+')';
	  this.bg_style.transform = 'scale('+(1.1+(height/3))+','+(1.1+(height/3))+')';

      this.ref.detectChanges();
    }, err => {});
  }
}

hero.html:

<div>
	<img class="hero_bg" [src]="background" [ngStyle]="bg_style">
	<img class="hero_photo" [src]="photo" [ngStyle]="photo_style">
	<span class="hero-title" *ngIf="title">{{ title }}</span>
</div>

hero.scss:

hero {
  background-color: #f0f;
  overflow: visible;

  // Hack for Safari overflow clipping
  div::before {
    content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=);
    display: block;
    z-index: -1;
    position: fixed;
    height: 300px;
    width: 100%;
  }
}

hero > div {
  position: relative;
  height: 350px;
  margin-top: -200px;
  overflow: hidden;

  img.hero_bg {
    position: absolute;
    top: 180px;
    width: 120%;
    height: auto;
  	transform-origin: center 240px;
  }

  img.hero_photo {
    position: absolute;
    bottom: 10px;
    left: 50%;
    margin-left: -64px;
  	width: 128px;
  	height: 128px;
  	border-radius: 50%;
  	border: 3px solid #000;
  	transform-origin: bottom center;
  }

  span {
  	background-color: #000
  	color: white;
  	font-weight: bold;
  	text-transform: uppercase;
  	padding: 3px 10px 0 10px;
  	border-radius: 10px;
  	position: absolute;
  	bottom: 10px;
  	left: 50%;
  	margin-left: -56px;
  	width: 112px;
  	text-align: center;
  }
}

Also in app.module.ts you have to import the HeroComponent and add it to the declarations.

Issues

  • On iOS Safari, for some reason scrolling doesn’t work at all sometimes, like the page is locked from scrolling… more often it happens only for a few seconds right after the page loads. Not sure why this is happening, it’s almost like preventDefault() in an onscroll, but after a while it goes away.

  • Would be nice if scrolling stopped once the image got to a certain size. I couldn’t figure out how to send a preventDefault to the browser onscroll from within this module… The ion-refresher kind of locks once you scroll a certain amount, but it doesn’t very fluid.

  • Maybe there is a better way of updating the transform css on the images in a way that doesn’t require ChangeDetectorRef.detectChanges() on every update?

  • There is probably a better way of passing the scroll events from the page to the hero controller?