Search code examples
javascripthtmlcss

How to make a flip page effect in book without using absolute position, or make a relatively positioned div adapt to content size?


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>


Solution

  • 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 thickness 0.4 or the pages might transpare */
      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:
    For a pure HTML/CSS example see this similar answer of mine: Pure HTML/CSS3 animated flip-book

    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.


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