Skip to content

stiubhart/sd-masonry

Repository files navigation

SdMasonry

This library was generated with Angular CLI version 10.0.14.

Check out an example here

Angular Component for organising child components in a masonry layout

The masonry grid is hastily calculated using sdMasonryWidth and sdMasonryHeight on each item on an array that you provide. Much faster than working this out on the fly after render that you see on other masonry layout packages. From there, you may add as much or as little data you want to each item which will be passed to one of your own component to do with what you will.

Did I mention sd-masonry also flawlessly handles pagination. Simply add more items to your data object

Install

npm i sd-masonry

Usage

Import SdMasonryModule into your app's modules:

import { SdMasonryModule } from 'sd-masonry';

@NgModule({
  imports: [SdMasonryModule],
  ...
})

@Params

  • component: Component Class (required)
  • data: array (required)
  • maxGridGap: number (optional - default 10px)
  • maxTotalColCount: number (optional - default 10px)

<sd-masonry [component]="component" [data]="data" maxGridGap="0" maxTotalColCount="15"></sd-masonry>

maxGrigGap and maxTotalColCount will automatically reduce in size depending on the container size

Component Class

Your component will be passed each item in your data array above in the form of an item object. Your component will need to be ready to grab it - @Input() item

@Input() item: {sdMasonryWidth: number, sdMasonryHeight: number, ... };

Getting Started - A Walk-through

Start a new project ng new my-sd-masonry

Install sd-masonry

cd my-sd-masonry
npm i sd-masonry

replace app.component.ts with the following

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

@Component({  
    selector: 'app-root',  
    template: `<div style="margin: 40px 100px;"><sd-masonry [component]="component" [data]="data"></sd-masonry></div>`,  
})  
export class AppComponent {  
  
    /** Required by sd-masonry */  
    public component = ItemComponent; /** ItemComponent declared below */  
  
    /** Required by sd-masonry (id and url properties are only required by ItemComponent in this example) */
    public data: {sdMasonryWidth: number, sdMasonryHeight: number, id?: number, url?: string}[];  
  
    private page1 = [  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 1, url: 'https://via.placeholder.com/1000x1500/947/000/?text=1+-+1000x1500' },  
        { sdMasonryWidth: 1512, sdMasonryHeight: 1006, id: 2, url: 'https://via.placeholder.com/1512x1006/69c/000/?text=2+-+1512x1006' },  
        { sdMasonryWidth: 1507, sdMasonryHeight: 1004, id: 3, url: 'https://via.placeholder.com/1507x1004/a5b/000/?text=3+-+1507x1004' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 4, url: 'https://via.placeholder.com/1000x1500/3c2/000/?text=4+-+1000x1500' },  
        { sdMasonryWidth: 1086, sdMasonryHeight: 1629, id: 5, url: 'https://via.placeholder.com/1086x1629/ae4/000/?text=5+-+1086x1629' },  
        { sdMasonryWidth: 1376, sdMasonryHeight: 1720, id: 6, url: 'https://via.placeholder.com/1376x1720/5c7/000/?text=6+-+1376x1720' },  
        { sdMasonryWidth: 1488, sdMasonryHeight: 992, id: 7, url: 'https://via.placeholder.com/1488x992/dbd/000/?text=7+-+1488x992' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1351, id: 8, url: 'https://via.placeholder.com/1000x1351/c8b/000/?text=8+-+1000x1351' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 9, url: 'https://via.placeholder.com/1000x1500/2b3/000/?text=9+-+1000x1500' },  
        { sdMasonryWidth: 1374, sdMasonryHeight: 917, id: 10, url: 'https://via.placeholder.com/1374x917/468/000/?text=10+-+1374x917' },  
        { sdMasonryWidth: 708, sdMasonryHeight: 1060, id: 11, url: 'https://via.placeholder.com/708x1060/2c7/000/?text=11+-+708x1060' },  
        { sdMasonryWidth: 1368, sdMasonryHeight: 912, id: 12, url: 'https://via.placeholder.com/1368x912/a3a/000/?text=12+-+1368x912' },  
        { sdMasonryWidth: 888, sdMasonryHeight: 1291, id: 13, url: 'https://via.placeholder.com/888x1291/7ec/000/?text=13+-+888x1291' },  
        { sdMasonryWidth: 912, sdMasonryHeight: 1368, id: 14, url: 'https://via.placeholder.com/912x1368/1e3/000/?text=14+-+912x1368' },  
        { sdMasonryWidth: 1693, sdMasonryHeight: 1123, id: 15, url: 'https://via.placeholder.com/1693x1123/989/000/?text=15+-+1693x1123' },  
        { sdMasonryWidth: 1326, sdMasonryHeight: 1988, id: 16, url: 'https://via.placeholder.com/1326x1988/583/000/?text=16+-+1326x1988' },  
        { sdMasonryWidth: 1200, sdMasonryHeight: 1500, id: 17, url: 'https://via.placeholder.com/1200x1500/b9e/000/?text=17+-+1200x1500' },  
        { sdMasonryWidth: 1734, sdMasonryHeight: 1771, id: 18, url: 'https://via.placeholder.com/1734x1771/889/000/?text=18+-+1734x1771' },  
        { sdMasonryWidth: 817, sdMasonryHeight: 561, id: 19, url: 'https://via.placeholder.com/817x561/a5a/000/?text=19+-+817x561' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 20, url: 'https://via.placeholder.com/1000x1500/5bd/000/?text=20+-+1000x1500' },  
        { sdMasonryWidth: 931, sdMasonryHeight: 1396, id: 21, url: 'https://via.placeholder.com/931x1396/d69/000/?text=21+-+931x1396' },  
        { sdMasonryWidth: 1006, sdMasonryHeight: 1512, id: 22, url: 'https://via.placeholder.com/1006x1512/2bb/000/?text=22+-+1006x1512' },  
        { sdMasonryWidth: 1146, sdMasonryHeight: 764, id: 23, url: 'https://via.placeholder.com/1146x764/68e/000/?text=23+-+1146x764' },  
        { sdMasonryWidth: 1840, sdMasonryHeight: 1228, id: 24, url: 'https://via.placeholder.com/1840x1228/8bd/000/?text=24+-+1840x1228' },  
        { sdMasonryWidth: 750, sdMasonryHeight: 500, id: 25, url: 'https://via.placeholder.com/750x500/891/000/?text=25+-+750x500' },  
        { sdMasonryWidth: 1182, sdMasonryHeight: 1000, id: 26, url: 'https://via.placeholder.com/1182x1000/464/000/?text=26+-+1182x1000' },  
        { sdMasonryWidth: 1396, sdMasonryHeight: 2092, id: 27, url: 'https://via.placeholder.com/1396x2092/d5d/000/?text=27+-+1396x2092' },  
        { sdMasonryWidth: 666, sdMasonryHeight: 1000, id: 28, url: 'https://via.placeholder.com/666x1000/989/000/?text=28+-+666x1000' },  
        { sdMasonryWidth: 931, sdMasonryHeight: 698, id: 29, url: 'https://via.placeholder.com/931x698/6c6/000/?text=29+-+931x698' },  
        { sdMasonryWidth: 1023, sdMasonryHeight: 1535, id: 30, url: 'https://via.placeholder.com/1023x1535/122/000/?text=30+-+1023x1535' },  
    ];  
  
    private page2 = [  
        { sdMasonryWidth: 855, sdMasonryHeight: 1282, id: 31, url: 'https://via.placeholder.com/855x1282/89e/000/?text=31+-+855x1282' },  
        { sdMasonryWidth: 967, sdMasonryHeight: 1450, id: 32, url: 'https://via.placeholder.com/967x1450/752/000/?text=32+-+967x1450' },  
        { sdMasonryWidth: 1680, sdMasonryHeight: 1120, id: 33, url: 'https://via.placeholder.com/1680x1120/871/000/?text=33+-+1680x1120' },  
        { sdMasonryWidth: 1060, sdMasonryHeight: 708, id: 34, url: 'https://via.placeholder.com/1060x708/211/000/?text=34+-+1060x708' },  
        { sdMasonryWidth: 1500, sdMasonryHeight: 1000, id: 35, url: 'https://via.placeholder.com/1500x1000/6a8/000/?text=35+-+1500x1000' },  
        { sdMasonryWidth: 1015, sdMasonryHeight: 1522, id: 36, url: 'https://via.placeholder.com/1015x1522/137/000/?text=36+-+1015x1522' },  
        { sdMasonryWidth: 1252, sdMasonryHeight: 765, id: 37, url: 'https://via.placeholder.com/1252x765/1db/000/?text=37+-+1252x765' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 38, url: 'https://via.placeholder.com/1000x1500/ec6/000/?text=38+-+1000x1500' },  
        { sdMasonryWidth: 864, sdMasonryHeight: 1296, id: 39, url: 'https://via.placeholder.com/864x1296/a9b/000/?text=39+-+864x1296' },  
        { sdMasonryWidth: 760, sdMasonryHeight: 760, id: 40, url: 'https://via.placeholder.com/760x760/77e/000/?text=40+-+760x760' },  
        { sdMasonryWidth: 1500, sdMasonryHeight: 1000, id: 41, url: 'https://via.placeholder.com/1500x1000/686/000/?text=41+-+1500x1000' },  
        { sdMasonryWidth: 966, sdMasonryHeight: 1296, id: 42, url: 'https://via.placeholder.com/966x1296/987/000/?text=42+-+966x1296' },  
        { sdMasonryWidth: 1152, sdMasonryHeight: 768, id: 43, url: 'https://via.placeholder.com/1152x768/214/000/?text=43+-+1152x768' },  
        { sdMasonryWidth: 1004, sdMasonryHeight: 1385, id: 44, url: 'https://via.placeholder.com/1004x1385/c6c/000/?text=44+-+1004x1385' },  
        { sdMasonryWidth: 1016, sdMasonryHeight: 677, id: 45, url: 'https://via.placeholder.com/1016x677/776/000/?text=45+-+1016x677' },  
        { sdMasonryWidth: 864, sdMasonryHeight: 1296, id: 46, url: 'https://via.placeholder.com/864x1296/2ec/000/?text=46+-+864x1296' },  
        { sdMasonryWidth: 526, sdMasonryHeight: 750, id: 47, url: 'https://via.placeholder.com/526x750/565/000/?text=47+-+526x750' },  
        { sdMasonryWidth: 600, sdMasonryHeight: 750, id: 48, url: 'https://via.placeholder.com/600x750/62e/000/?text=48+-+600x750' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 49, url: 'https://via.placeholder.com/1000x1500/7a7/000/?text=49+-+1000x1500' },  
        { sdMasonryWidth: 1040, sdMasonryHeight: 1560, id: 50, url: 'https://via.placeholder.com/1040x1560/d16/000/?text=50+-+1040x1560' },  
        { sdMasonryWidth: 1040, sdMasonryHeight: 1560, id: 51, url: 'https://via.placeholder.com/1040x1560/e4a/000/?text=51+-+1040x1560' },  
        { sdMasonryWidth: 1040, sdMasonryHeight: 1560, id: 52, url: 'https://via.placeholder.com/1040x1560/6b4/000/?text=52+-+1040x1560' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1500, id: 53, url: 'https://via.placeholder.com/1000x1500/81e/000/?text=53+-+1000x1500' },  
        { sdMasonryWidth: 1120, sdMasonryHeight: 1680, id: 54, url: 'https://via.placeholder.com/1120x1680/7de/000/?text=54+-+1120x1680' },  
        { sdMasonryWidth: 722, sdMasonryHeight: 1084, id: 55, url: 'https://via.placeholder.com/722x1084/d96/000/?text=55+-+722x1084' },  
        { sdMasonryWidth: 1000, sdMasonryHeight: 1250, id: 56, url: 'https://via.placeholder.com/1000x1250/e5a/000/?text=56+-+1000x1250' },  
        { sdMasonryWidth: 945, sdMasonryHeight: 1417, id: 57, url: 'https://via.placeholder.com/945x1417/92b/000/?text=57+-+945x1417' },  
        { sdMasonryWidth: 912, sdMasonryHeight: 1283, id: 58, url: 'https://via.placeholder.com/912x1283/63c/000/?text=58+-+912x1283' },  
        { sdMasonryWidth: 1197, sdMasonryHeight: 1820, id: 59, url: 'https://via.placeholder.com/1197x1820/e6a/000/?text=59+-+1197x1820' },  
        { sdMasonryWidth: 987, sdMasonryHeight: 1480, id: 60, url: 'https://via.placeholder.com/987x1480/6de/000/?text=60+-+987x1480' },  
    ];  
  
    /**  
     * Populate this.data 
     */  
    constructor() {  
        this.data = this.page1; /** Loading in Page 1 */  
  
        setTimeout(() => {  
            this.data = this.data.concat(this.page2); /** Loading in Page 2 */  
        }, 5000);  
    }  
}  
  
  
/**  
 * Your own child component for each sd-masonry cell 
 */
@Component({  
    selector: 'app-item-image',  
    template: `<img [src]="item.url" [alt]="item.id">`,  
    styles: ['img { width: 100%; height: 100%; object-fit: cover; border-radius: 5px;}'],  
})  
export class ItemComponent {  
    @Input() item: {sdMasonryWidth: number, sdMasonryHeight: number, id?: number, url?: string};
}

Update app.module.ts to include SdMasonryModule and ItemComponent

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent, ItemComponent } from './app.component';
import { SdMasonryModule } from 'sd-masonry';

@NgModule({
  declarations: [
    AppComponent,
    ItemComponent
  ],
  entryComponents: [ItemComponent],
  imports: [
    BrowserModule,
    SdMasonryModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Output

Masonry Output Screen-shot

Animation Example

Why not try adding in your own animation to the mix? Just edit your ItemComponent like so :

import {animate, state, style, transition, trigger} from '@angular/animations';

// ...

@Component({
    selector: 'app-item-image',
    template: `<img [@scaleAnimation]="state" [src]="item.url" [alt]="item.masonryStyle">`,
    styles: ['img { width: 100%; height: 100%; object-fit: cover;}'],
    animations: [trigger('scaleAnimation', [
        state('beginning', style({transform: 'scale(0)', opacity: 0})),
            state('end', style({transform: 'scale(1)', opacity: 1})),
            transition('beginning => end', [animate('300ms')]),
        ])]
})
export class ItemComponent implements OnInit{

    @Input() item: {adMasonryWidth: number, sdMasonryHeight: number, id?: number, url?: string};
    public state = 'beginning';

    ngOnInit(): void {
        setTimeout(() => {
            this.state = 'end';
        }, Math.random() * 600);
    }
}

Remember to update app.module.ts to import the BrowserAnimationsModule

imports: [
    BrowserModule,
    SdMasonryModule,
    BrowserAnimationsModule
],

Upping the game

If you chose to include an id property, you'll also be able individually update an item's property, add a new item into the middle of the stack, and delete an item. Try updating your app.component.ts to the following

    private newItem = { sdMasonryWidth: 1368, sdMasonryHeight: 2739, id: 61, url: 'https://via.placeholder.com/966x1296/000/fff/?text=NEW+ITEM' };

    /**
     * Populate this.data
     */
    constructor() {

        const delay = 2000;
        let i = 2;

        /** Loading in Page 1 */
        this.data = this.page1;

        /** Change an item's properties (and dimensions) - changing background to red */
        setTimeout(() => {
            this.data = this.data.map(item => {
                if (item.id === 23) {
                    item.url = 'https://via.placeholder.com/146x430/f00/000/?text=23+-+146x430';
                    item.sdMasonryWidth = 146;
                    item.sdMasonryHeight = 430;
                }
                return item;
            });
        }, delay * i++);

        /** Add a new item randomly into the stack */
        setTimeout(() => {
            this.data.splice(12, 0, this.newItem);
            this.data = [].concat(this.data); /** sd-masonry uses ngOnChange(), so mutated data won't trigger that lifecycle event πŸ˜” */
        }, delay * i++);

        /** Loading in Page 2 */
        setTimeout(() => {
            this.data = this.data.concat(this.page2);
        }, delay * i++);

        /** Now... Let's remove the item we just added */
        setTimeout(() => {
            this.data = this.data.filter((item, index) => index !== 12);
        }, delay * i++);
    }

Troubleshooting

If you're getting Ivy errors, you may need to update tsconfig.json or tsconfig.app.json with

  "angularCompilerOptions": {
    "enableIvy": false
  }

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published