2

I have a flexbox. Its contents are NxN squares. I want the container to fit as many of these squares as possible given the display width. I want the flexbox to be center-aligned on the page.

However the problem is when I use

justify-content: center

image: justify-content: center

then the last row is not aligned to the left. However if I change to

justify-content: left

image: justify-content: left

then the entire container is no longer displayed center-aligned on the page. Can I somehow achieve a mix of two, so that is this example I would have centrally aligned 5 items, but the last row would be aligned to the first item in previous rows?

Minimal repro:

<style>
  div { display: flex; flex-wrap: wrap; justify-content: center; }
  i { display: block; width: 300px; height: 300px; background: black; margin: 10px; }
</style>
<div>
  <i></i> <i></i> <i></i> <i></i> <i></i>
  <i></i> <i></i> <i></i>
</div>
Bob
  • 21
  • 3
  • Hi and Welcome to SO. Please add a minimal reproduicable code snippet (ctrl + m) showing your code and issue (focus on being minimal). – tacoshy Mar 21 '21 at 14:22
  • Thanks. Repro added. – Bob Mar 21 '21 at 14:25
  • I'm sorry but i really don't understand the problem. I mean if you use `justify-content: left;` then it is aligned on the left and if you use `justify-content: center;` it's centered. – GucciBananaKing99 Mar 21 '21 at 14:27
  • I want however many elements fit to be centrally-aligned. If I use `justify-content: left;` then my 5 elements (could be 4, or could be 6 etc depending on the resolution) are not aligned centrally in the parent. And I can't center align the parent element (or at least I don't know how to do it), since the flexbox occupies 100% width. – Bob Mar 21 '21 at 14:29

3 Answers3

4

The layout you're building is a pretty standard grid. CSS grid is going to be a better fit than flexbox.

By using auto-fit for the columns and setting each to a fixed size, it will fit as many columns as it can within the container. justify-content: center will center your columns but content will still move across those columns from left to right.

Example:

div {
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(auto-fit, 160px);
    justify-content: center;
}

span {
    background: red;
    display: block;
    height: 160px;
}
<div>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
</div>
Nathan Dawson
  • 18,138
  • 3
  • 52
  • 58
0

Found the answer here

Adding below CSS works

div:after {
    content: '';
    flex: auto;
    margin: 10px;
    max-width: 300px;
}

But, It breaks the responsiveness for Ipad and mobile screens, You can fix that by adding the above CSS only for big screens using a media query.

I would not recommend this, If possible switch flex-box to grid as stated by @nathan

Sameer
  • 4,758
  • 3
  • 20
  • 41
0

Here is a method of achieving the asked question. I'm using Angular (Typescript) but the principle remains the same.

Firstly you'll need these two methods / functions:

  /** 
   * Returns the width of each item in the flexfox (they need to be equal width!)
   */
  getFlexboxItemWidth(selector: string){
    return document.querySelector(selector)?.getBoundingClientRect().width
  }

  /**
   * Returns the amount of additional, invisible flexbox items to place
   */
  calculateFlexboxFillerItemCount(totalItems: number, selector: string, flexboxSelector: string): number{
    const flexboxWidth: number = document.querySelector(flexboxSelector)?.getBoundingClientRect().width as number
    const itemsPerRow: number = Math.trunc(flexboxWidth / (this.getFlexboxItemWidth(selector) as number))
    const itemsInLastIncompleteRow: number = totalItems % itemsPerRow
    return isNaN(itemsPerRow) 
            || isNaN(itemsInLastIncompleteRow) 
            || itemsPerRow - itemsInLastIncompleteRow === Infinity 
            || itemsInLastIncompleteRow === 0 
              ? 0 
              : itemsPerRow - itemsInLastIncompleteRow + 1
  }

Once you have created those two methods you'll need to implement them:

  <!--The Flexbox-->
  <ion-list id="home-tab--item-list-container" *ngIf="items.length > 0">

    <!--Visible Flex Items-->
    <ion-card class="home-tab--item-card" *ngFor="let item of items" (click)="goToItemDetails(item)">
      <img [src]="item.images[0]">
      <ion-card-header>
        <ion-card-title>{{item.title}}</ion-card-title>
      </ion-card-header>
    
      <ion-card-content>
        {{item.description}}
        <div class="home-tab--item-slide-price">
          ${{item.cost.toFixed(2)}}
          <div class="home-tab--item-rental-period" *ngIf="item.rentalPeriod !== null">
             /{{item.rentalPeriod}}
          </div>
        </div>
      </ion-card-content>
    </ion-card>

    <!--Invisible Flexbox Items responsible for filling remaining space of last row-->
    <ion-card [ngStyle]="{width: getFlexboxItemWidth('.home-tab--item-card')+'px', visibility: 'hidden'}" *ngFor="let _ of [].constructor(calculateFlexboxFillerItemCount(items.length, '.home-tab--item-card', '#home-tab--item-list-container'))">
    </ion-card>

  </ion-list>

And finally, here is the styling for better context:

#home-tab--item-list-container {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    width: 100%;
    overflow: auto;
}

.home-tab--item-card {
    min-width: 170px;
    max-width: 300px;
    cursor: pointer;
}

@media screen and ( max-width: 960px) {
    .home-tab--item-card  { flex: 0 1 30% }
}

End Result:

enter image description here

otboss
  • 621
  • 1
  • 7
  • 16