Skip to content

Instantly share code, notes, and snippets.

@jack-guy
Created May 5, 2016 22:47
Show Gist options
  • Save jack-guy/abb8a30529bc2fd80bb40efeed48f2a7 to your computer and use it in GitHub Desktop.
Save jack-guy/abb8a30529bc2fd80bb40efeed48f2a7 to your computer and use it in GitHub Desktop.
import { Directive, ViewChild, Inject, ContentChildren, QueryList, ContentChild,
ElementRef, Input, forwardRef, AfterViewInit, EventEmitter } from 'angular2/core';
import { Subject } from 'rxjs';
const imagesLoaded = require('imagesloaded');
@Directive({
selector: '[masonry-item]',
host: {
'[style.position]': 'position',
'[style.left]': 'left+"px"',
'[style.top]': 'top+"px"',
'[style.visibility]': '(loading) ? "hidden" : "visible"'
}
})
export class MasonryItemDirective {
public el: HTMLElement;
public position: string;
public left: number;
public top: number;
public loading: boolean = true;
constructor (public ref: ElementRef) {
this.el = this.ref.nativeElement;
}
}
@Directive({
selector: '[masonry-spacer]',
host: {
'[style.height]': 'height+"px"',
}
})
export class MasonrySpacerDirective {
public el: HTMLElement;
public height: number;
constructor (public ref: ElementRef) {
this.el = this.ref.nativeElement;
this.el.style.width = '100%';
this.el.style.display = 'block';
}
}
@Directive({
selector: '[masonry]',
host: {
'(resize)': '_resizeStream.next($event)'
}
})
export class MasonryDirective implements AfterViewInit {
@ContentChildren(MasonryItemDirective) items: QueryList<MasonryItemDirective>;
@ContentChild(MasonrySpacerDirective) spacer: MasonrySpacerDirective;
@Input('masonry-columns') maxColumns: number;
private _el: HTMLElement;
private _resizeStream: Subject<any> = new Subject();
constructor (ref: ElementRef) {
this._el = ref.nativeElement;
}
ngAfterViewInit () {
this.items.changes.debounceTime(100).subscribe((changes) => {
imagesLoaded(this._el, () => {
this.layout();
});
});
this._resizeStream.debounceTime(100).subscribe(() => {
this.layout();
});
}
layout () {
if (this.items.length === 0)
return;
const firstEl = this.items.first.el;
const gutter = this.styleVal(firstEl, 'margin-right');
const gap = this.styleVal(firstEl, 'margin-bottom');
const parentWidth = this._el.clientWidth;
const parentInnerWidth = parentWidth -
this.styleVal(this._el, 'padding-left') -
this.styleVal(this._el, 'padding-right');
const columnWidth = this.items.first.el.clientWidth;
const numColumns = Math.min(
Math.floor(
parentInnerWidth / columnWidth
),
this.maxColumns
);
const parentPadding = (parentWidth - (columnWidth + gutter) * numColumns) / 2;
const cols = [];
const initialTop = this.styleVal(this._el, 'padding-top');
for (let i = 0; i < numColumns; i++) { cols.push(initialTop); }
let count = 0;
this.items.forEach((item) => {
const itemCol = count % numColumns;
Object.assign(item, {
position: 'absolute',
left: parentPadding + itemCol * (columnWidth + gutter),
top: cols[itemCol],
});
cols[itemCol] += item.el.clientHeight + gap;
item.loading = false;
count++;
});
if (this.spacer) {
this.spacer.height = Math.max(...cols);
}
}
styleVal (el, prop) {
return parseInt(
getComputedStyle(el, null)
.getPropertyValue(prop)
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment