Skip to content

Instantly share code, notes, and snippets.

@uxder
Last active April 7, 2017 20:47
Show Gist options
  • Save uxder/8464331241ab43bf2cd879693b47a56c to your computer and use it in GitHub Desktop.
Save uxder/8464331241ab43bf2cd879693b47a56c to your computer and use it in GitHub Desktop.
Sticky Component using RAF (works better than window scroll on specific mobile devices) -WIP
import {ViewChild, ViewChildren, Component, AfterViewChecked, OnDestroy, OnInit, ElementRef, Inject, Output, EventEmitter, HostListener, HostBinding} from "@angular/core";
import { DOCUMENT } from '@angular/platform-browser';
import { RafManager} from '../lib/animations/raf-manager';
declare var cordova: any;
/**
* A very simple sticky component. Supports stacking multiple.
*
* Usage:
* <h1> The sticky component will stick to the top, when the top of the window
* scrolls by it</h1>
* <sticky-component id="my-sticky-1">
* Some stuff
* </sticky-componet>
*
* <h2> You can also add an offset. This allows you to 'stack' multiple stickiers.
* In this case, you would want the offset to be the height of #my-sticky-1. </h2>
*
* <sticky-component offsetAmount="60">
* Some stuff
* </sticky-componet>
*
*/
@Component({
selector: 'sticky-component',
template: '<div #content class="sticky-component__content"><ng-content></ng-content></div>'
})
export class StickyComponent implements OnInit, OnDestroy {
@Output() change = new EventEmitter();
/**
* Binds a css class to the host.
*/
@HostBinding('class') cssClass = 'sticky-component';
/**
* View child reference to content element.
*/
@ViewChild('content') contentElement;
/**
* Style host bindings.
*/
@HostBinding('style.width') elWidth;
@HostBinding('style.height') elHeight;
private nativeEl: Element;
private hostCoordinates:any;
private offset:number;
private rafManager: RafManager;
private skipIos:boolean;
constructor(
public el: ElementRef,
@Inject(DOCUMENT) private document: Document
) {
this.nativeEl = el.nativeElement;
this.offset = +this.nativeEl.getAttribute('offsetAmount') || 0;
this.skipIos = !!this.nativeEl.getAttribute('skipIos');
}
ngOnInit() {
this.hostCoordinates = this.nativeEl.getBoundingClientRect();
// Set the parent to have the height and width of the contents.
// This way, when the inner contents become position:fixed and are
// removed from the document flow, the page still takes up the
// same amoutn of space.
this.elWidth = this.hostCoordinates.width + 'px';
this.elHeight = this.hostCoordinates.height + 'px';
this.contentElement.nativeElement.style.width = this.hostCoordinates.width + 'px';
if(cordova.platformId == 'ios') {
if(this.skipIos) {
return;
} else {
// let contentEl = this.contentElement.nativeElement;
// contentEl.style.position = 'fixed';
return;
}
}
this.rafManager = new RafManager(()=> {
this.refresh();
})
this.rafManager.start();
}
/*
@HostListener("window:scroll", [])
onWindowScroll() {
this.refresh();
}
*/
private refresh() {
this.hostCoordinates = this.nativeEl.getBoundingClientRect();
let scrollTop = this.document.body.scrollTop;
let contentEl = this.contentElement.nativeElement;
let shouldFix = this.hostCoordinates.top <= this.offset;
contentEl.style.position = (shouldFix) ? 'fixed' : 'relative';
contentEl.style.top = (shouldFix) ? this.offset + 'px' : '0px';
}
ngOnDestroy() {
if(this.rafManager) {
this.rafManager.stop();
this.rafManager = null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment