1

Codepen

I have a div that represents a book page and contains the front and the back elements. On a click the page flips to show the reverse side.

In order to make the styling work, I made the book div (which contains the page divs) have position: relative, and the paper (which represents a page) div has position: absolute styling.

However, the width and height of the book are set in px so when the paper content doesn't fit the dimensions, the content overflows:

enter image description here

On pages where the content fits, it looks alright:

enter image description here

How do I make the paper elements to adjust to the size of the content? So that if there are several pages, the height of each page would be equal to the height of the page with the most amount of content?

let pages = document.getElementsByClassName("paper");

setup(pages);

function setup(elements) {
  for (let i = 0; i < elements.length; i++) {
    let previous = i === 0 ? null : elements[i - 1];
    let element = elements[i];
    let next = i === elements.length - 1 ? null : elements[i + 1];

    element.addEventListener('click', () => {
      element.classList.toggle('flipped');
      if (element.classList.contains("flipped")) {
        if (next !== null) {
          next.style.zIndex++;
        }
        element.style.zIndex = i + 1;
      } else {
        element.style.zIndex = elements.length - i;
      }
    });
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: sans-serif;
  background-color: #333333;
}


/* Book */

.book {
  position: relative;
  width: 350px;
  height: 500px;
  transition: transform 3.5s;
}

.paper {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  perspective: 1500px;
  cursor: pointer;
}

.front,
.back {
  background-color: white;
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  transform-origin: left;
  transition: transform 3.5s;
  display: flex;
  display: flex;
  justify-content: center;
  align-items: flex-start;
}

.front {
  z-index: 1;
  backface-visibility: hidden;
  border-left: 3px solid powderblue;
  background-color: #b3ffff;
}

.back {
  z-index: 0;
  background-color: #ffd699;
}

.front-content,
.back-content {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: flex-start;
}

.back-content {
  transform: rotateY(180deg)
}


/* Paper flip effect */

.flipped .front,
.flipped .back {
  transform: rotateY(-180deg);
}


/* Controller Buttons */

button {
  border: none;
  background-color: transparent;
  cursor: pointer;
  margin: 10px;
  transition: transform 0.5s;
}

button:focus {
  outline: none;
}

button:hover i {
  color: #636363;
}

i {
  font-size: 50px;
  color: gray;
}


/* Paper stack order */

#p1 {
  z-index: 3;
}

#p2 {
  z-index: 2;
}

#p3 {
  z-index: 1;
}
<div id="book" class="book">
  <div id="p1" class="paper">
    <div class="front">
      <div id="f1" class="front-content">
        <h1>The Roman Empire was the post-Republican state of ancient Rome. It included territory around the Mediterranean in Europe, North Africa, and Western Asia, and was ruled by emperors. The adoption of Christianity as the state church in 380 and the
          fall of the Western Roman Empire conventionally marks the end of classical antiquity and the beginning of the Middle Ages.</h1>
      </div>
    </div>

    <div class="back">
      <div id="b1" class="back-content">
        <h1>The first two centuries of the Empire saw a period of unprecedented stability and prosperity known as the Pax Romana (lit. 'Roman Peace'). </h1>
      </div>
    </div>
  </div>

  <!-- Second page -->
  <div id="p2" class="paper">
    <div class="front">
      <div id="f2" class="front-content">
        <h1>Due to the Empire's extent and endurance, its institutions and culture had a lasting influence on the development of language, religion, art, architecture, literature, philosophy, law, and forms of government in its territories. Latin evolved
          into the Romance languages, while Medieval Greek became the language of the East.</h1>
      </div>
    </div>

    <div class="back">
      <div id="b2" class="back-content">
        <h1>he rediscovery of classical science and technology (which formed the basis for Islamic science) in medieval Europe led to the Scientific Renaissance and Scientific Revolution. Many modern legal systems, such as the Napoleonic Code, descend from
          Roman law, while Rome's republican institutions have influenced the Italian city-state republics of the medieval period, the early United States, and modern democratic republics.
        </h1>
      </div>
    </div>
  </div>
</div>
parsecer
  • 4,758
  • 13
  • 71
  • 140
  • 1
    [Make absolute positioned div expand parent div height](https://stackoverflow.com/questions/12070759/make-absolute-positioned-div-expand-parent-div-height) – isherwood Aug 24 '23 at 21:27
  • 1
    I'd look to create the effect without absolute positioning, though. [Search results](https://www.google.com/search?q=css+flip+pages+site:stackoverflow.com) – isherwood Aug 24 '23 at 21:29
  • 1
    So basically the **book height** should be equal to the page with the max height, right? – Roko C. Buljan Aug 25 '23 at 09:57

3 Answers3

2

Animated flip-book using CSS

enter image description here

Z-index in 3D CSS is a painful, bug-prone approach and should be avoided. While going 3D, account for stacking context but forget about z-index.
Use translate on the Z axis instead.

  • To create a flip-book where the tallest page dictates the entire book height — use CSS flex.
  • Retract (translate) each .page's .back to negative 100% X
  • Retract (translate) each .book's .page to negative pageIndex * 100% X
  • Now that all the pages are overlapping, stack each page in the Z axis by a negative amount of index times "thickness" pixels (not less than 0.4px or the natural stacking order might transpare)
  • Use JavaScript purely to assign pages index var --i, and change CSS var --c for current page index. The math for transformation and animation will be handled entirely by CSS.

Here's a Tl;Dr example, where I've inclined the .book on the X axis just a bit, to make more obvious the way the pages animation actually works. For a pure top-down remove the rotate rule.
PS: you can also click any page edge to flip more pages at once.

const flipBook = (elBook) => {
  elBook.style.setProperty("--c", 0); // Set current page
  elBook.querySelectorAll(".page").forEach((page, idx) => {
    page.style.setProperty("--i", idx);
    page.addEventListener("click", (evt) => {
      const curr = !!evt.target.closest(".back") ? idx : idx + 1;
      elBook.style.setProperty("--c", curr);
    });
  });
};

document.querySelectorAll(".book").forEach(flipBook);
* { box-sizing: border-box; }

body {
  /* or any other parent wrapper */
  margin: 0;
  display: flex;
  height: 100dvh;
  perspective: 1000px;
  font: 16px/1.4 sans-serif;
  overflow: hidden;
  background-color: #232425;
}

.book {
  display: flex;
  margin: auto;
  width: 300px;
  /*1* let pointer event go trough pages of lower Z than .book */
  pointer-events: none;
  transform-style: preserve-3d;
  transition: translate 1s;
  translate: calc(min(var(--c), 1) * 50%) 0%;
  /* DEMO ONLY: incline on the X axis for pages preview */
  rotate: 1 0 0 30deg;
}

.page {
  --thickness: 5;
  /* PS: Don't go below 0.4 */
  flex: none;
  display: flex;
  width: 100%;
  /*1* allow pointer events on pages */
  pointer-events: all;
  user-select: none;
  transform-style: preserve-3d;
  border: 1px solid #0008;
  transform-origin: left center;
  transition:
    transform 1s,
    rotate 1s ease-in calc((min(var(--i), var(--c)) - max(var(--i), var(--c))) * 50ms);
  translate: calc(var(--i) * -100%) 0px 0px;
  transform: translateZ( calc((var(--c) - var(--i) - 0.5) * calc(var(--thickness) * 1px)));
  rotate: 0 1 0 calc(clamp(0, var(--c) - var(--i), 1) * -180deg);
}

.page img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.front,
.back {
  flex: none;
  width: 100%;
  padding: 2rem;
  backface-visibility: hidden;
  background-color: #fff;
  /* Fix backface visibility Firefox: */
  translate: 0px;
}

.back {
  background-image: linear-gradient(to right, #fff 80%, #ddd 100%);
  translate: -100% 0;
  rotate: 0 1 0 180deg;
}
<div class="book">
  <div class="page">
    <div class="front">
      <h1>FlipBook</h1>
      <h3>2023.<br>Second edition</h3>
    </div>
    <div class="back">
      <h2>Lorem Ipsum</h2>
      1. Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, modi, perspiciatis molestias cum neque delectus eum eveniet repellat iusto totam magnam cupiditate quaerat quis.
    </div>
  </div>

  <div class="page">
    <div class="front">
      2. Dolor Molestias aspernatur repudiandae sed quos debitis recusandae consectetur ab facilis voluptates sint vero eos, consequuntur delectus?
    </div>
    <div class="back">
      <img src="https://picsum.photos/500/400" alt="Img 1">
    </div>
  </div>

  <div class="page">
    <div class="front">
      <h2>Sit amet</h2>
      4. Consectetur adipisicing elit. Dignissimos illo voluptate sapiente provident tempore ea voluptates perferendis tenetur eos nulla, doloribus! Distinctio a nostrum ipsum, adipisci at mollitia.
    </div>
    <div class="back">
      5. Debitis recusandae consectetur ab facilis voluptates sint vero eos, consequuntur delectus temporibus harum dolorem provident eaque perferendis.
    </div>
  </div>

  <div class="page">
    <div class="front">
      <h2>Consectetur</h2>
      6. Adipisicing elit. Sed, fuga aspernatur? Numquam, molestias unde! Voluptatibus sint aspernatur qui dolore est itaque ipsum consequuntur neque asperiores non obcaecati harum, perspiciatis voluptate!
    </div>
    <div class="back">
      7. Temporibus, eum nobis? Adipisci, a? Eaque vel amet ut reprehenderit.
    </div>
  </div>

  <div class="page">
    <div class="front">
      <img src="https://picsum.photos/536/354" alt="Img 2">
    </div>
    <div class="back">
      <h3>Finalis</h3>
      9. Lorem ipsum dolor sit, amet consectetur adipisicing elit. code by Roko, eniam vero, magni dignissimos deleniti hic ratione sequi ullam eos.
    </div>
  </div>
</div>

Tip:
If you want to create a navigation with buttons all you need to do is to change the "current page" CSS --c var via JavaScript like:

// On navigation button click:
elBook.style.setProperty("--c", someIndex);

and the book will animate automagically to the desired page-view.


I might write soon some details on how this all works. For the time being ask is something is not clear.

Git project: https://github.com/rokobuljan/flipbook

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 1
    Wow! This is so good! So essentially you used `page.style.translate = ${-100 * pageIndex}% 0;` in Javascript to simulate the `absolute` property? Ingenious! Thank you so much, this is the best example of a book with flipping pages I've seen! – parsecer Aug 25 '23 at 20:47
  • 1
    I struggled with making `zIndex` work right, especially when taking into account the time it takes for a page to flip over. Your `transitionend` event listener works flawlessly! – parsecer Aug 25 '23 at 21:18
  • 1
    This implementation is a bit buggy. Will edit soon with an improvement that does not uses z-index at all. – Roko C. Buljan Aug 27 '23 at 17:20
  • 1
    Wow, thank you! I didn't think it would is possible, as all examples in the Internet use `z-index`! – parsecer Aug 27 '23 at 19:59
  • @parsecer exactly. Z-index is just not the way to approach 3D in HTMl/CSS. After hours of experimenting and head scratching - edited with a rock-solid solution. – Roko C. Buljan Aug 28 '23 at 18:06
0

I think you should have done this using JavaScript. Calc every page height when the content is changed (you should get height of p1 div) and set it to height of book. You can get the height of content using this:

const book = document.getElementById('book');
... ... ...
book.style.height = element.querySelector('h1').offsetHeight;

The main thing is, update the book div's height when showing content changes.

0

If I understand your question correctly, you want the element to have a minimum height in px, but with the possibility to shrink when the content is larger.

To achive that, instead of using height, set a minimum height.

min-height: 500px;