4

Making a card game web app, I am trying to put as much rendering logic as possible in CSS (possibly just as a challenge to myself). One of the harder challenges is the following: as in solitaire, I need to display vertical stacks of cards (having a fixed size and aspect ratio), as in the image below. Stacks 1 and 2 show the principle. However, if a stack gets very large, the vertical spacing between the cards might need to be shrunk, for the entire stack to fit within the parent's bounds (stack 3 in the image). To achieve this shrinking, solely in the case that the stack would overflow the parent's bounds, is the content of this question.

An 'intermediate' result is that I'm able to make the stacks behave like 3 and 4 in the image, by putting each card within a 0-height container, vertically evenly spaced within the stack with flex, and then linearly interpolating the margin-top of the cards from -0% to -100%. This setting of the margin-top has to be done with JS, outside of the CSS, which is a concession -- but it's not the worst solution, because it doesn't require the JS to respond to the rendering itself. This intermediate result obviously only works well for the overflow-case (stack 3), and not for the ordinary case (stacks 1 and 2 in the image), in which case it is ugly (stack 4).

The question is, then, whether it is possible to achieve this automatic shrinking only after having passed a certain critical break-off point, with CSS only, and/or possibly some 'before-render' attributes/styles set with JS.

Problem sketch (a working version of which can be found at http://jsfiddle.net/kelleyvanevert/5fkac3s7/1/):

Problem sketch

Flex-box 'intermediate result' of stacks 3 and 4:

CSS

.stack {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.surr {
  height: 0;
}

HTML

<div class="stack">
  <div class="surr"><div class="item" style="margin-top: -0%;"></div></div>
  <div class="surr"><div class="item" style="margin-top: -25%;"></div></div>
  <div class="surr"><div class="item" style="margin-top: -50%;"></div></div>
  <div class="surr"><div class="item" style="margin-top: -75%;"></div></div>
  <div class="surr"><div class="item" style="margin-top: -100%;"></div></div>
</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Kelley van Evert
  • 1,063
  • 2
  • 9
  • 17

1 Answers1

1

(See comments below for some additional results)

You can use a pseudo-element (:after) that will help you to keep your stack as you want when having more cards. Then you can simply control the flex value of this element to control how much the cards should shrink as it will control how much space this element should take:

.box {
  margin-top: 25px;
  border: 2px solid #000;
  height: 300px;
  display: flex;
  flex-direction: row;
}

.stack {
  position: relative;
  box-sizing: border-box;
  width: 15%;
  height: 100%;
  padding: 5px 5px 0 5px;
  margin: 0 10px;
  background: #fff;
  border-left: 1px solid #ddd;
  border-right: 1px solid #ddd;
    display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.label {
  position: absolute;
  top: -20px;
  left: 50%;
  width: 20px;
  height: 20px;
  margin-left: -10px;
  text-align: center;
}

.item {
  box-sizing: border-box;
  height: 0;
  padding-top: 80%;
  /* aspect ratio */
  margin-bottom: 5px;
  background: #fff;
  border: 1px solid #333;
}

.stack:after {
  content: "";
  flex: 5;
}

.surr {
  height: 0;
  flex: 1
}
<div class="box">
  <div class="stack">
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="label">D</div>
  </div>
  <div class="stack">
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="label">E</div>
  </div>
  <div class="stack">
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>    
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="label">E</div>
  </div>
  <div class="stack">
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>    
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="surr"><div class="item" ></div></div>
    <div class="label">E</div>
  </div>
  
</div>
Kelley van Evert
  • 1,063
  • 2
  • 9
  • 17
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • I like this but I am concerned that the items.and wrappers have zero height and are only given dimension via padding. I think you are on to something though. – Paulie_D Dec 21 '17 at 16:27
  • @Paulie_D i totally agree. Visually it's fine but when it will come to some interaction we will not be able to select the whole element as `item` is overflowing from `surr` and specifying a height will screw the thing (unless we re-consider the negative margin). So am waiting for any comments from the OP to see how is going to continue his coding :) – Temani Afif Dec 21 '17 at 19:23
  • 1
    This is interesting thinking! :) I figured out you can even put this into the CSS directly, by counting the number of `.surr` siblings that come before an added `.pad` div, that plays the role that you assigned to the `:after` pseudo-element: http://jsfiddle.net/1nosmLda/5/. However, there is still one problem here: just as I wanted the cards to have an aspect ratio, so that changing the parent (= viewport in the app) size is the same as 'zooming', so too, I want to change the vertical spacing according to this principle. The only way to do that now, that I can think of, is with media queries. – Kelley van Evert Dec 21 '17 at 21:37
  • @KelleyvanEvert great idea the use of nth-child() to make the flex grow depeding on the number of cards :) ... concerning Vertical spacing, it is actually depending on the size of container which is fixed, so maybe you can make this height dynamic (or change it using media query) and then the spacing will change too. – Temani Afif Dec 21 '17 at 21:50
  • 1
    So true :) Just apply the same aspect ratio thing to the entire box, and then let the rest scale accordingly. Case solved, thanks. (Or, well, almost. I still have to figure out how to not make that last stick out to a varying degree. But that should be less complicated.) http://jsfiddle.net/pv6r5fw5/1/ – Kelley van Evert Dec 21 '17 at 21:57
  • A fully completed version of the challenge: http://jsfiddle.net/pv6r5fw5/2/ (Works in Google Chrome, but for some reason not in Firefox as of yet though). – Kelley van Evert Dec 22 '17 at 14:58
  • @KelleyvanEvert good ;) for firefox add all the vendor prefix for flex to be sure all the property works – Temani Afif Dec 22 '17 at 14:59
  • I have the idea that it's not a vendor prefix thing, but rather a distinct implementation of flexbox, and in particular `flex-basis`, though I'm not entirely sure. Here's a version that works in Chrome & Firefox, with only a very small 'hack' (setting `.stack .pad` height to 15%): http://jsfiddle.net/pv6r5fw5/3/ – Kelley van Evert Dec 22 '17 at 15:23
  • @KelleyvanEvert yes the flex-basis is a know issue :) https://github.com/philipwalton/flexbugs/issues/41 – Temani Afif Dec 22 '17 at 15:25