Make flex items overlap
Asked Answered
B

11

50

I'd like to show a horizontal series of a unknown number of playing cards. To do this, they will have to overlap if there are too many. I'm having trouble convincing a flex box to overlap the cards without shrinking them. The example below shrinks the cards. I tried flex-shrink: 0, but then max-width wasn't respected.

.cards {
  display: flex;
  max-width: 300px;
}

.card {
  width: 50px;
  height: 90px;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
}
<div class='cards'>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Baillieu answered 11/5, 2017 at 14:59 Comment(5)
Why use flex if you want them to overlap, isn't the point of flex that it fills out the space?Ornithology
Can you just use negative margin for the overlap?Glindaglinka
A negative margin would work for a given number of cards. But it would have to change for each card and for each number of total cards. I'm looking for something more flexible.Baillieu
Can you be more specific with what you're trying to achieve? What's "too many"? How do you want them to overlap exactly. What do you mean in reply to @Glindaglinka with "it would have to change for each card and for each number of total cards. I'm looking for something more flexible."? it would have to change how? And why? What's the logic there? What do you mean "more flexible"?Filth
@MichaelCoker I'm trying to get the cards to fan in a way similar to this picture: shpgames.com/zero-mod/fan.jpg (but without the curve). The cards should never resize - they should always remain 50 x 90px. So, if the max-width of the container is 300px wide, you can fit up to 6 cards with no overlap. Once a seventh is added, there will be a slight overlap. And if there were 50 cards, you would only see 6 pixels of each due to the overlap. So I'm hoping the flex container could take care of that calculation.Baillieu
T
54

Here's how I'd do this using flexbox.

.cards {
  display: flex;
  align-content: center;
  max-width: 35em;
}

.cardWrapper {
  overflow: hidden;
}

.cardWrapper:last-child, .cardWrapper:hover {
    overflow: visible;
}

.card {
    width: 10em;
    min-width: 10em;
    height: 6em;
    border-radius: 0.5em;
    border: solid #666 1px;
    background-color: #ccc;
    padding: 0.25em;
  
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
<div class="cards">
  <div class="cardWrapper">
    <div class="card">card 1 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 2 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 3 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 4 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 5 blah blah blah</div>
  </div>
</div>

Note that technically speaking, the cards aren't overlapping, they're just being clipped. But they look like they're overlapping. The trick is to wrap each card in another element with overflow: hidden.

The wrapping elements are shrunk to fit the available space, and as much of the cards as is possible is displayed in that space.

I include the :hover rule just to show how you could fully display a card from the middle of the "stack", but in a real project I'd probably do this for selected cards instead of hovered ones.

Tollmann answered 30/4, 2018 at 23:44 Comment(2)
You can eliminate overflow: hidden if you set the size on the wrapper and position the card to take it out of the flow, then the cards actually would overlap visually.Naphthalene
min-width on the wrappers instead of the hidden overflow will also let it visually stack, just add a bit of margin/padding on the right of .cards if neededFabiano
M
36

You can do it using only css by using grid instead of flex.

.hand{
  width: 50%;
  margin-left: auto;
  margin-right: auto;
  justify-content: center;
  display: grid;
  grid-template-columns: repeat(auto-fit,  minmax(10px, max-content)) ;
}
.card{
  width: 3em;
  height: 2.4em;
  padding: 3px;
  margin: 2px;
  background-color: lightgreen;
  border-style: solid;
  transform: rotate(3deg);  /*makes it easier to see the overlap*/
}
<div class="hand"> 
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
</div>
Misspend answered 29/10, 2018 at 3:23 Comment(1)
This solution is quite elegant; however, it overflows the .hand container. Is there a way to accomplish this such that the cards are evenly spaced and overlapping in a css grid, but without the overflow?Playful
F
34

You can make elements in a flex layout overlap using transform: translateX(-10px), but that won't address the layout you're trying to get. I don't think you can do that in flexbox. But you could easily do this with JS.

var parentEl = document.getElementById("cards");

function sortCards() {
  var cards = document.getElementsByClassName("card"),
      cw = parentEl.clientWidth,
      sw = parentEl.scrollWidth,
      diff = sw - cw,
      offset = diff / (cards.length - 1);

  for (var i = 1; i < cards.length; i++) {
    cards[i].style.transform = "translateX(-" + offset * i + "px)";
  }
}

sortCards();

var b = document.getElementById("button");
b.addEventListener("click", function() {
  var div = document.createElement("div");
  div.classList.add("card");
  parentEl.appendChild(div);
  sortCards();
});
.cards {
  display: flex;
  max-width: 300px;
}

.card {
  height: 90px;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
  flex: 0 0 50px;
  background: red;
  transition: transform .25s;
}
<div><button id="button">addcards</button></div>
<div class='cards' id="cards">
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Filth answered 11/5, 2017 at 15:39 Comment(4)
Although not flex, this is a great solution. It fulfills all the requirements of a variable number of cards in a given space. If anyone still has a flex solution, I'd love to hear it, but as of now, this is a good-enough accepted answer.Baillieu
@MichaelArrison someone else answered saying you can make the elements overlap with margin - you can do that with transform: translateX() too, as I did here, but that wouldn't pull off your layout, so I didn't use it. You'll need some math (I think) to do that. But I updated my answer to use flex and transform since you had that in your original question.Filth
Why doesn't the for cycle start at i=1 in your solution?Domain
@Domain 'cause I mostly write CSS and that's just how I default to a JS loop :) Updated the answer with your suggestion, thanks!Filth
M
9

A flex container is designed to align items along the X and Y axes.

You're asking about alignment along the Z axis.

Flexbox is not designed for z-axis alignment (overlapping).

Any overlapping would have to come from negative margins, absolute positioning, CSS Grid Layout, JavaScript or something else. The z-index property may also need to play a role.

Here's a basic example using CSS Grid:

.cards {
  display: grid;
  grid-template-columns: repeat(30, 10px);
  grid-template-rows: 90px;
  max-width: 300px;
}

.card {
  grid-row-start: 1;
  background-color: lightgreen; 
  border: 1px solid black;
}

.card:nth-child(1) { grid-column: 1 / 6; }
.card:nth-child(2) { grid-column: 4 / 9; }
.card:nth-child(3) { grid-column: 7 / 12; }
.card:nth-child(4) { grid-column: 10 / 15; }
.card:nth-child(5) { grid-column: 13 / 18; }
.card:nth-child(6) { grid-column: 16 / 21; }
.card:nth-child(7) { grid-column: 19 / 24; }
.card:nth-child(8) { grid-column: 22 / 27; }
.card:nth-child(9) { grid-column: 25 / 30; }
<div class='cards'>
  <div class='card'>1</div>
  <div class='card'>2</div>
  <div class='card'>3</div>
  <div class='card'>4</div>
  <div class='card'>5</div>
  <div class='card'>6</div>
  <div class='card'>7</div>
  <div class='card'>8</div>
  <div class='card'>9</div>
</div>

Cards are made to overlap using line-based placement. In this case, the grid-column property is used to make column tracks overlap.

Still, some scripting would be necessary if the number of cards varies dynamically, as the overlap amount would need to vary in order for all cards to fit precisely in the fixed-width container.

Magnesia answered 11/5, 2017 at 15:20 Comment(3)
Although one card should be on top of the next, that is not the important issue here. The important issue is having them overlap on the x axis. Flex containers have many options for alignment along the x axis. But I'm just not sure if it has any for overlapping.Baillieu
I think with use of jquery you can put a formula on the margin-left from the 2nd card onwards that will dynamically calculate it and overlap the cards. If you want i can make you an example.French
@NasirT brings up a good point, although it can be simplified to pure css. A rule like .card:nth-child(n+2) { margin-left: -30px; } would be quite effective if the number of cards is known ahead of time.Baillieu
P
9

Late to the party, but this was my fix for a similar situation:

There's a suggestion mentioning negative margins and it's true that when no other adaptions are done, cards will always overlap with: margin-left: -30px; (Overlap width chosen somewhat arbitrary. You could choose the card's width as a maximum.)

What I've changed here is adding justify-content: space-evenly; to the mix. This spreads out the cards over the available space in the flex container and only lets the cards overlap when it gets too crowded. This could be a solution if you're fine with the cards spreading out like that when not overlapping.

NB. Since the negative margin pulls the cards to the left, I also hacked in a padding-left with the same width as the negative margin into the flex container.

NB2. I changed the opacity of the original example because otherwise the cards become see-through.

    .cards {
      display: flex;
      max-width: 300px;
      padding-left: 30px;
      justify-content: space-evenly;
    }

    .card {
      width: 50px;
      height: 90px;
      border: 1px solid black;
      border-radius: 8px;
      background-color: rgb(255, 100, 100);
      margin-left: -30px;
    }
    <div class='cards'>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
    </div>
Petrify answered 3/1, 2021 at 21:40 Comment(1)
Though simple, it reduces the width of the .card elems when the width of the .cards container is reduced. (tested in FF and Chrome)Lefkowitz
R
2

I came up with a generic, CSS-based solution. However, with a few caveats:

  1. The last card overflows the .cards container.
  2. A child element of .card is needed to produce the overlapping effect.

.cards {
  display: flex;
  max-width: 300px;
}

.card {
  position: relative;
  flex-basis: 50px;
  height: 90px;
}

.card::before {
  content: '';
  display: block;
  position: absolute;
  width: 50px;
  height: 100%;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
  box-sizing: border-box;
}
<div class='cards'>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Retha answered 28/3, 2018 at 13:52 Comment(0)
Q
1

None of the current answers resolve the original question very well, because it stated: they will have to overlap if there are too many. So the cards should use their normal space by default (and no more), and then only overlap when necessary.

The solution to this is using an absolute positioned element inside a relative positioned flex item, set to grow, and with a max-width.

            .cards {
                display: flex; /* Main element is a flex container */
            }
            .card_container {
                position: relative; /* Flex items have relative positioning */
                flex: 1 1 auto; /* Flex items will grow to use up space */
                max-width: 100px; /* BUT they will only grow up to this size (the size of a card) */
                height: 150px; /* Necessary to match height of cards as they are absolute positioned */
                margin: 5px; /* Optional spacing between the cards */
            }
            .card {
                position: absolute; /* The card itself is absolute positioned within the  */
                top: 0;
                left: 0; /* Position card at top left of flex item */
                box-sizing: border-box; /* Ensure card size matches flex item size set above */
                width: 100px; /* Card width */
                height: 150px; /* Card height */
            }
            .card_container:last-child {
                flex: 1 0 100px; /* Ensure last card doesn't get cut off */
            }
            /* Design fluff */
            .card {
                border: 1px solid black;
                background: linear-gradient(145deg, rgba(255,255,255,1) 0%, rgba(235,235,235,1) 100%);
                border-radius: 20px;
                padding: 10px 0 0 15px;
                font-family: sans-serif;
                font-size: 30px;
                color: black;
                transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out, border-width 0.3s ease-in-out, rotate 0.3s ease-in-out;
            }
            .card_container:nth-child(even) .card {
                top: 10px;
                color: red;
            }
            .card:hover {
                z-index: 1;
                cursor: pointer;
                transform: scale(1.1);
                box-shadow: 0 5px 15px rgba(0,0,0,0.3), inset 0 0 0 1px black;
                rotate: 5deg;
            }
            .card_container:nth-child(even) .card:hover {
                rotate: -5deg;
            }
        <div class="cards">
            <div class="card_container"><div class="card">10♣</div></div>
            <div class="card_container"><div class="card">A♥</div></div>
            <div class="card_container"><div class="card">A♠</div></div>
            <div class="card_container"><div class="card">4♥</div></div>
            <div class="card_container"><div class="card">8♠</div></div>
            <div class="card_container"><div class="card">J♦</div></div>
            <div class="card_container"><div class="card">2♠</div></div>
            <div class="card_container"><div class="card">2♦</div></div>
            <div class="card_container"><div class="card">6♣</div></div>
        </div>

I've commented the important bits of the CSS, and the design fluff is all separated at the end!

With this method you will always see your cards fully when there is space, and have them overlap just as much as is necessary if not. And note the extra part to ensure the last card always stays on the screen.

Quenna answered 27/4 at 12:52 Comment(0)
K
0

You can do it using margin property and apply to the items you desire by manual or JS.

.cards {
  display: flex;
  max-width: 300px;
}

.overl {
margin-left:-10px;
}

.card {
  width: 50px;
  height: 90px;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
}
<div class='cards'>
  <div class='card'></div>
  <div class='card overl'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Komsa answered 11/5, 2017 at 15:42 Comment(0)
M
0

div.card-area
{
    height: 16vh; /* whatever */
    display: flex;
}
div.card-area > div
{
    flex: 1 0 0;
    overflow-x: hidden;
}
div.card-area > div:last-of-type
{
    flex: 0 0 auto;
}
div.card-area img
{
    height: 100%;
}
<div class="card-area N">
    <div><img src="img/cards/AH.png"></div>
    <div><img src="img/cards/KH.png"></div>
    <div><img src="img/cards/QH.png"></div>
    <div><img src="img/cards/JH.png"></div>
    <div><img src="img/cards/10H.png"></div>
    <div><img src="img/cards/9H.png"></div>
    <div><img src="img/cards/8H.png"></div>
    <div><img src="img/cards/7H.png"></div>
    <div><img src="img/cards/6H.png"></div>
    <div><img src="img/cards/5H.png"></div>
    <div><img src="img/cards/4H.png"></div>
    <div><img src="img/cards/3H.png"></div>
    <div><img src="img/cards/2H.png"></div>
</div>
Movable answered 17/1, 2019 at 23:1 Comment(0)
F
0

This overlaps a right child over a left child, excluding the first.

.overlapped {
  > * + * {
    postion: absolute;
    margin-left: -7px;
  }
}

Your container should also have flex and flex-direction row.

Fusible answered 24/12, 2019 at 19:37 Comment(0)
P
0

One more option for overlapping N cards with CSS flex is to combine negative margin and transform translate:

.cards {
  width: 100px;
  display: flex;
  flex-wrap: wrap;
}

.card {
  width: 20px;
  height: 30px;
  box-shadow: 1px 1px 3px 1px black;
  background: #BADA55;


  margin-top: -10px;
  margin-left: -4px;
  transform: translate(4px, 10px);
}
<div class='cards'>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Phonetic answered 20/12, 2023 at 9:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.