Creating a card flipper with front/back faces WITHOUT absolut positioning
Asked Answered
T

6

10

I want to add a new feature to my site that uses the card-flipper schema from David Walsh's site: https://davidwalsh.name/css-flip. The problem is, the content in my cards is variable so the height is unknown (and it's a responsive site as well). I have a footer at the bottom of my page which needs to be below this area, but since this approach relies on absolute positioning, the footer will be at the top like in this codepen: https://codepen.io/anon/pen/ALJmGZ

Can anyone think of any css hacks or alterations to this approach that would make it so the cards don't have to be absolutely positioned?

HTML:

<div class="flip-container" ontouchstart="this.classList.toggle('hover');">
  <div class="flipper">
    <div class="front">
      <span class="name">David Walsh</span>
    </div>
    <div class="back">
      <div class="back-logo"></div>
      <div class="back-title">@davidwalshblog</div>
      <p>Mozilla Web Developer, MooTools & jQuery Consultant, MooTools Core Developer, Javascript Fanatic, CSS Tinkerer, PHP Hacker, and web lover.</p>
    </div>
  </div>
  <footer>Here is my footer</footer>
</div>

CSS:

.flip-container {
  -webkit-perspective: 1000;
  -moz-perspective: 1000;
  -o-perspective: 1000;
  perspective: 1000;

    border: 1px solid #ccc;
}

    .flip-container:hover .flipper,  
  .flip-container.hover .flipper {
        -webkit-transform: rotateY(180deg);
        -moz-transform: rotateY(180deg);
    -o-transform: rotateY(180deg);
        transform: rotateY(180deg);
    }

.flip-container, .front, .back {
    width: 320px;
    height: 427px;
}

.flipper {
    -webkit-transition: 0.6s;
    -webkit-transform-style: preserve-3d;

    -moz-transition: 0.6s;
    -moz-transform-style: preserve-3d;

  -o-transition: 0.6s;
    -o-transform-style: preserve-3d;

    transition: 0.6s;
    transform-style: preserve-3d;

    position: relative;
}

.front, .back {
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
  -o-backface-visibility: hidden;
    backface-visibility: hidden;

    position: absolute;
    top: 0;
    left: 0;
}

.front {
    background: url(http://davidwalsh.name/demo/dwflip.jpg) 0 0 no-repeat;
    z-index: 2;
}

.back {
    -webkit-transform: rotateY(180deg);
    -moz-transform: rotateY(180deg);
  -o-transform: rotateY(180deg);
    transform: rotateY(180deg);

    background: #f8f8f8;
}

.front .name {
    font-size: 2em;
    display: inline-block;
    background: rgba(33, 33, 33, 0.9);
    color: #f8f8f8;
    font-family: Courier;
    padding: 5px 10px;
    border-radius: 5px;
    bottom: 60px;
    left: 25%;
    position: absolute;
    text-shadow: 0.1em 0.1em 0.05em #333;

    -webkit-transform: rotate(-20deg);
    -moz-transform: rotate(-20deg);
  -o-transform: rotate(-20deg);
    transform: rotate(-20deg);
}

.back-logo {
    position: absolute;
    top: 40px;
    left: 90px;
    width: 160px;
    height: 117px;
    background: url(http://davidwalsh.name/demo/logo.png) 0 0 no-repeat;
}

.back-title {
    font-weight: bold;
    color: #00304a;
    position: absolute;
    top: 180px;
    left: 0;
    right: 0;
    text-align: center;
    text-shadow: 0.1em 0.1em 0.05em #acd7e5;
    font-family: Courier;
    font-size: 2em;
}

.back p {
    position: absolute;
    bottom: 40px;
    left: 0;
    right: 0;
    text-align: center;
    padding: 0 20px;
  font-family: arial;
  line-height: 2em;
}

footer {
  background:red;
}
Teleutospore answered 18/10, 2016 at 22:51 Comment(2)
You can keep "footer" out of "flip-container".Cyprinodont
That would only work because flip-container from that example has an explicit height set.Teleutospore
T
5

I found a way to accomplish this using pure css and no position absolute. I used css animations instead of transitions and you can set the height/width of the elements to 0 at the 50% keyframe. Check out the fiddle: https://codepen.io/anon/pen/bwmkAo

Sass:

@keyframes no-show {
    0% {
        transform: rotateY(0deg);
    height: auto;
    width: 100%;
    }

    49% {
        height: auto;
        width: 100%;
    }

    50% {
        height: 0;
        width: 0;
    }

    100% {
        transform: rotateY(180deg);
        height: 0;
        width: 0;
    }
}

@keyframes show {
    0% {
        transform: rotateY(-180deg);
        height: 0;
        width: 0;
    }

    49% {
        height: 0;
        width: 0;
    }

    50% {
        height: auto;
        width: 100%;
    }

    100% {
        transform: rotateY(0deg);
        height: auto;
        width: 100%;
    }
}

.flip-container {
    border: 1px solid #ccc;
}

.flip-container, .front, .back {
    width: 320px;
}

.flipper {
    position: relative;
}

.front, .back {
  position: relative;
    perspective: 1000px;

    transform-style: preserve-3d;
    perspective-origin: top center;
    animation-duration: 2s;
    animation-timing-function: linear;
    transition-property: transform;
    animation-fill-mode: forwards;
    -webkit-animation-fill-mode: forwards;
    overflow: hidden;
}

.front {
    z-index: 2;

    transform: rotateY(0deg);
    animation-name: show;
    .flipper.active & {
        animation-name: no-show;
    }

  .inner {
    height: 300px;
    background: green;
  }
}

.back {
    transform: rotateY(-180deg);
    animation-name: no-show;

    .flipper.active & {
        animation-name: show;
    }

  .inner {
    height: 400px;
    background: blue;
  }
}

footer {
    background: red;
}
Teleutospore answered 19/10, 2016 at 2:39 Comment(0)
U
3

I was playing with this today, only stumbling upon this question because my solution comes with a tiny caveat.

.flip-container {
  perspective: 1000;
}
.flipper {
  transform-style: preserve-3d;
  display: flex;
  align-items: stretch;
  width: 100%;
  transition: transform 0.6s;
  will-change: transform;
}
.flip-container:hover .flipper {
  transform: rotateY(180deg);
}
.front, .back {
  width: 100%;
  flex: 0 0 auto;
  backface-visibility: hidden;
}
.back {
  transform: rotateY(180deg);
  margin-left: -100%;
}

This works because flex items in a row can take the height of the tallest row item. Style .front and .back as needed, just don't set their height.

The caveat is that the shorter side seems to glitch out its rendered height if the other side grows after render (say, because an image has loaded). It's fine if heights change due to window resizing, just seemingly not if content suddenly loads. Of course this might not wind up being the case for you, but I wound up having to give images a container with set height. If you don't need a pure CSS solution, what might also work is waiting for the image to load before showing the whole flip card, but this is a bit of a digression since you don't seem to be using an img.

Unofficial answered 12/9, 2019 at 14:20 Comment(2)
You saved my life. This works like a charm ! Combining absolute + flex + overflow didn't work on iOS but this method does ! Thank youLeena
This is great! Absolute positioning doesn't work with a responsive CSS grid, but this flex approach did!Cudweed
P
3

Nowadays you can create Flip Cards with CSS Grid and without absolute positioning at all, here is an example:

body {
  background: #1339ac;
  padding: 50px;
}

.flipCard {
  perspective: 1000px;
}
.flipCard .boxes {
  display: grid;
  transition-duration: 0.6s;
  transition-timing-function: ease-in-out;
  transform-style: preserve-3d;
}
.flipCard .boxes .box {
  background: gold;
  padding: 1rem;
  border: 2px solid #000;
  /*---------*/
  grid-row: 1/2;
  grid-column: 1/2;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}
.flipCard .boxes .box.front {
  transform: rotateX(0deg);
}
.flipCard .boxes .box.back {
  transform: rotateX(-180deg);
}
.flipCard:hover .boxes {
  transform: rotateX(-180deg);
}
<div class="flipCard">
  <div class="boxes">
    <div class="box front">
      <h2>Box 1</h2>
      <ul>
        <li>List Item 01</li>
        <li>List Item 02</li>
        <li>List Item 03</li>
      </ul>
    </div>
    <div class="box back">
      <h2>Box 2</h2>
      <ul>
        <li>List Item 01</li>
        <li>List Item 02</li>
        <li>List Item 03</li>
        <li>List Item 04</li>
        <li>List Item 05</li>
        <li>List Item 06</li>
      </ul>
    </div>
  </div>
</div>
Phlebitis answered 26/6, 2022 at 6:5 Comment(0)
C
2

I don't think it's possible to make an effect similar to this without using absolute positioning.

The best way to do this would be in JS to grab the heights.

Here's a CodePen with the basics: https://codepen.io/CourtDemone/pen/vXVbGQ

Basically just move the footer out of the flip container and add some JS like this.

var heightFront, heightBack;

heightFront = $('.front').height();
heightBack = $('.back').height();

if(heightFront > heightBack){
  $('.flip-container').height(heightFront);
}else{
  $('.flip-container').height(heightBack);
}
Carioca answered 19/10, 2016 at 0:28 Comment(0)
H
1

I also followed the David Walsh blog example, and needed to do this within a flex context, so, I found out that absolute positioning of flipping panels would not work properly.

I solved this using

  • relative position in the .flipper class
  • fixed height also for the block with .flipper class, but separately in order to use different height schemas, let's say height: 15em
  • top: 0 for the front block
  • top: negative for the back block

I had to adjust values in order to give some margin and spaces between elements. I used a onclick event instead of hovering, my choice for UX. But both can be done.

<!-- HTML -->
<p>three boxes on a flex basis with a onclick flipper effect</p>
<div class='flexinl'>
    <div class='flip-container fronted hand' 
      onclick="this.swapClasses('fronted,backed');">
        <div class="flipper fli300">
            <div class='front c1'>
                <h1>This is front 1</h1>
            </div>
            <div class='back c2'>
                <h1>This is back 1</h1>
            </div>
        </div>
    </div>
    <div class='flip-container fronted hand' 
      onclick="this.swapClasses('fronted,backed');">
        <div class="flipper fli300">
            <div class='front c1'>
                <h1>This is front 2</h1>
            </div>
            <div class='back c2'>
                <h1>This is back 2</h1>
            </div>
        </div>
    </div>
    <div class='flip-container fronted hand' 
      onclick="this.swapClasses('fronted,backed');">
        <div class="flipper fli300">
            <div class='front c1'>
                <h1>This is front 3</h1>
            </div>
            <div class='back c2'>
                <h1>This is back 3</h1>
            </div>
        </div>
    </div>
</div>  

/* CSS */
.c1 {color:white; background-color:green}
.c2 {color:white; background-color:orange}
.hand { cursor: pointer; }

/* based on David Walsh Flipper */
.flip-container {
    perspective: 1000px;
    border: 1px dashed #a9a9a9;
    margin-bottom:1.5em;    
}
  /* flip the pane when has the backed */
.flip-container.backed .flipper {
    transform: rotateY(180deg);
}
.fli300 {height: 15em;}
.fli300 .front {
    height: 12em !important;
}
.fli300 .back {
    height: 12em !important;
    top: -15em;
}

/* flip speed goes here */
.flipper {
    transition: .4s;
    transform-style: preserve-3d;
    position: relative;    
}

/* hide back of pane during swap */
.flipper .front, .flipper .back {
    backface-visibility: hidden;
    position: relative;
    left: 0;
    height: 100%;
    padding: 1.5em;    
}

/* front pane, placed above back */
.flipper .front {
    z-index: -1;
    top: 0;
    /* for firefox 31 */
    transform: rotateY(0deg);
}

/* back, initially hidden pane */
.flipper .back {
    transform: rotateY(180deg);
}

/* some flex stuff */
.flexinl {  
    display: inline-flex; 
    flex-wrap: wrap;  
    justify-content: flex-start;
}
.flexinl > div {padding:0.2em}

/* some javascript for swapping two classes */

HTMLElement.prototype.swapClasses = function (dosclasses) {
    var clases = dosclasses.split(/\s*\,\s*/);
    var entra = clases[0];
    var sale = clases[1];
    if (this.classList.contains(sale)) {
        this.classList.remove(sale);
        this.classList.add(entra);
    } else {
        this.classList.remove(entra);
        this.classList.add(sale);
    }
    return this;
};

You can find it in this codepen

Hymenopteran answered 3/12, 2020 at 15:42 Comment(0)
B
0

You can force the container with .front and .back to take the height of its parent.(height:inherit;)
JSfiddle: https://jsfiddle.net/9q7xr4ef/

Snippet:

.flip-container {
  -webkit-perspective: 1000;
  -moz-perspective: 1000;
  -o-perspective: 1000;
  perspective: 1000;
  border: 1px solid #ccc;
}
.flip-container:hover .flipper,
.flip-container.hover .flipper {
  -webkit-transform: rotateY(180deg);
  -moz-transform: rotateY(180deg);
  -o-transform: rotateY(180deg);
  transform: rotateY(180deg);
}
.flip-container,
.front,
.back {
  width: 320px;
  height: 427px;
}
.flipper {
  -webkit-transition: 0.6s;
  -webkit-transform-style: preserve-3d;
  -moz-transition: 0.6s;
  -moz-transform-style: preserve-3d;
  -o-transition: 0.6s;
  -o-transform-style: preserve-3d;
  transition: 0.6s;
  transform-style: preserve-3d;
  position: relative;
  height: inherit;
}
.front,
.back {
  -webkit-backface-visibility: hidden;
  -moz-backface-visibility: hidden;
  -o-backface-visibility: hidden;
  backface-visibility: hidden;
  position: absolute;
  top: 0;
  left: 0;
}
.front {
  background: url(http://davidwalsh.name/demo/dwflip.jpg) 0 0 no-repeat;
  z-index: 2;
}
.back {
  -webkit-transform: rotateY(180deg);
  -moz-transform: rotateY(180deg);
  -o-transform: rotateY(180deg);
  transform: rotateY(180deg);
  background: #f8f8f8;
}
.front .name {
  font-size: 2em;
  display: inline-block;
  background: rgba(33, 33, 33, 0.9);
  color: #f8f8f8;
  font-family: Courier;
  padding: 5px 10px;
  border-radius: 5px;
  bottom: 60px;
  left: 25%;
  position: absolute;
  text-shadow: 0.1em 0.1em 0.05em #333;
  -webkit-transform: rotate(-20deg);
  -moz-transform: rotate(-20deg);
  -o-transform: rotate(-20deg);
  transform: rotate(-20deg);
}
.back-logo {
  position: absolute;
  top: 40px;
  left: 90px;
  width: 160px;
  height: 117px;
  background: url(http://davidwalsh.name/demo/logo.png) 0 0 no-repeat;
}
.back-title {
  font-weight: bold;
  color: #00304a;
  position: absolute;
  top: 180px;
  left: 0;
  right: 0;
  text-align: center;
  text-shadow: 0.1em 0.1em 0.05em #acd7e5;
  font-family: Courier;
  font-size: 2em;
}
.back p {
  position: absolute;
  bottom: 40px;
  left: 0;
  right: 0;
  text-align: center;
  padding: 0 20px;
  font-family: arial;
  line-height: 2em;
}
footer {
  background: red;
}
<div class="flip-container" ontouchstart="this.classList.toggle('hover');">
  <div class="flipper">
    <div class="front">
      <span class="name">David Walsh</span>
    </div>
    <div class="back">
      <div class="back-logo"></div>
      <div class="back-title">@davidwalshblog</div>
      <p>Mozilla Web Developer, MooTools & jQuery Consultant, MooTools Core Developer, Javascript Fanatic, CSS Tinkerer, PHP Hacker, and web lover.</p>
    </div>
  </div>
  <footer>Here is my footer</footer>
</div>

# 2, one child is relative:
JSfiddle: https://jsfiddle.net/9q7xr4ef/2/

Beaton answered 18/10, 2016 at 23:31 Comment(4)
Again, I don't set an explicit height anywhere so this will not work for my case. Without an explicit height set for flip-container, this won't work. My content is dynamic.Teleutospore
This is close, kind of. Problem is depending on which view is showing, the front may be larger or the back may be larger. Without using js, this method wouldn't work either.Teleutospore
In the example you gave, the front and back are the same.Beaton
Read my initial post again. "The problem is, the content in my cards is variable so the height is unknown (and it's a responsive site as well)."Teleutospore

© 2022 - 2024 — McMap. All rights reserved.