What is the best way to create a flip page effect in a book without relying on absolute positioning? Alternatively, how can a relatively positioned div adjust to the size

Check out this Codepen

I have created a div that simulates the pages of a book, with front and back elements. When you click on the page, it flips to reveal the reverse side.

To style the book properly, I set the parent div (which houses the page divs) to have position: relative, while the page itself has position: absolute.

The issue arises when the content of the page exceeds the dimensions set for the book, causing overflow:

https://i.sstatic.net/VP2a8.png

However, when the content fits within the dimensions, it looks fine:

https://i.sstatic.net/T8fhl.png

I am looking for a solution to make the paper elements adjust dynamically based on the content size. So if there are multiple pages, each page should match the height of the page with the most 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 styling */

.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 styling */

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


/* Controller Buttons styling */

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 styling */

#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>

Answer №1

CSS Animated Flip-Book Tutorial

https://i.sstatic.net/8jF04.gif

When creating a flip-book using 3D CSS, avoid using Z-index as it can lead to bugs and complications. Instead, consider using the translate property on the Z axis.

  • To ensure that the tallest page determines the height of the entire book, utilize CSS flex.
  • Translate each .page's .back to negative 100% X.
  • Adjust (translate) each .book's .page to negative pageIndex * 100% X.
  • Overlap all pages by stacking each one on the Z-axis with a negative value proportional to its index times the specified "thickness" in pixels (min 0.4px).
  • Use JavaScript solely for assigning page indexes and updating the CSS variable for the current page index. All transformation and animation calculations will be handled through CSS.

In the following example, the .book is tilted slightly on the X-axis to showcase the page animation more clearly. Remove the rotate rule for a purely top-down effect.
PS: Clicking any page edge allows you to turn multiple pages simultaneously.

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

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

body {
  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;
  pointer-events: none;
  transform-style: preserve-3d;
  transition: translate 1s;
  translate: calc(min(var(--current), 1) * 50%) 0%;
  rotate: 1 0 0 30deg;
}

.page {
  --thickness: 5;
  flex: none;
  display: flex;
  width: 100%;
  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(--index), var(--current)) - max(var(--index), var(--current))) * 50ms);
  translate: calc(var(--index) * -100%) 0px 0px;
  transform: translateZ( calc((var(--current) - var(--index) - 0.5) * calc(var(--thickness) * 1px)));
  rotate: 0 1 0 calc(clamp(0, var(--current) - var(--index), 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;
  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...
    </div>
  </div>

  ... additional page content goes here ...

</div>

For a demonstration using only HTML/CSS, refer to my similar answer here: Pure HTML/CSS3 animated flip-book

If you wish to incorporate navigation buttons into your flip-book, simply update the CSS variable for the "current page" using JavaScript:

// On button click:
el.style.setProperty("current", someIndex);

The book will automatically animate to display the desired page-view when this action is taken.


Visit my Git project for more code examples: https://github.com/rokobuljan/flipbook

Answer №2

In my opinion, incorporating JavaScript would have been a better approach for this task. Calculate the height of each page dynamically as content changes (specifically, by obtaining the height of the p1 div) and then adjust the book's height accordingly. To retrieve the content's height, you can utilize the following method:

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

The key point is to ensure that the height of the book div is updated whenever there are modifications in the displayed content.

Answer №3

It seems like you're looking to ensure that the element has a minimum height in pixels while still being able to shrink when necessary due to larger content.

To accomplish this, avoid using height and instead utilize a minimum height.

min-height: 500px;

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to Identify and Print a Specific Property in a JSON Object using Node.js?

Hey there, I'm having trouble extracting the trackName from the JSON object provided here. I've tried accessing it using this code: console.log(res.text.results[0].trackName); but unfortunately, I keep getting this error message: TypeError: Cann ...

Displaying an error message in a spreadsheet cell

Issue The problem arises when viewing the exported excel file where undefined is displayed. However, upon investigation, there are no empty array indexes causing this confusion. Solution Attempt const joinDate = new Date(obj.date); let snap = [ obj. ...

"Utilizing Bootstrap 4: Wide text causes columns to stack on top of

My website has a layout with three columns: col-auto on the left, col in the middle, and col-auto on the right. The col-auto columns display skyscraper ads (600x160) while the col column contains text. However, when the text inside the col column is wide, ...

Changing the boolean value of User.isActive in Node.js: A step-by-step guide

Define a User Model with isActive as a Boolean property. Upon clicking a button, the user is redirected to a route where their information is retrieved based on the id from the parameters. Once the user is found, the script checks the value of isActive. ...

Fixed Header Table

Whenever I attempt to set the table header at a position other than 0, the body ends up scrolling above the header. Below is the code snippet showcasing the issue: .test { padding-top: 50px; } th { background-color: orange; position: sticky; to ...

Return an array that has been filtered to exclude any elements that are also found in a separate array

I have an API that provides a list of cars. const getAsset = async () => dbApi('list_cars', ['', 100]) .then(result => result.map(e => e.symbol)); Here, the function getAsset returns the car list. 0: "BMW" 1: "HONDA" 2: " ...

Implementing the useEffect hook in React to iterate over JSON data and update the state

Having trouble implementing job location filtering ("remote"/"in-person"/"hybrid") for my personal project. As a beginner in programming, I've spent quite some time troubleshooting the fetchLocationData function and passing URLSearchParams. I anticipa ...

Remove specific data from jQuery datatables using data attribute

My jQuery datatable is loaded with data from a database without any filtering by default, providing all the necessary information for users. In addition to the built-in search functionality of jQuery datatables, I have incorporated custom search input fiel ...

Node.js and Express throwing errors when trying to access a certain endpoint with HTTPS and passing parameters

I am experiencing issues with my node.js express webserver that operates on both HTTP and HTTPS, specifically related to express's route parameters and HTTPS. Express allows for parameters in the routing, such as: app.get('/users/:userid', ...

Is there a way to store data in a variable for caching in NextJS?

After receiving JSON data from an online API, I am looking for a way to store and cache the response for the duration of my application running. Is there a method to accomplish this in Next.js without relying on an external library for such a basic task? ...

How can I trigger a function after all nested subscriptions are completed in typescript/rxjs?

So I need to create a new user and then create two different entities upon success. The process looks like this. this.userRepository.saveAsNew(user).subscribe((user: User) => { user.addEntity1(Entity1).subscribe((entity1: EntityClass) => {}, ...

Unable to load templateUrl when constructing a custom directive

Currently, I am delving into the world of directives as they appear to be quite advantageous. My intention was to implement a directive for a top navigation bar. However, I find myself perplexed as the templateUrl fails to load. Despite scouring through va ...

Error: The value property is undefined and cannot be read at the HTMLButtonElement

I'm currently working on a project where I need to display a dropdown menu of buttons using Pug in order to render an HTML page. To achieve this, I am using the getElementsByClassName('dropdown-item') method to retrieve all the buttons in an ...

Boosting your website with a slick Bootstrap 4 responsive menu that easily accommodates additional

I have incorporated a main menu in my website using the Bootstrap 4 navbar. However, I also have an additional div (.social-navbar) containing some extra menu items that I want to RELOCATE and INSERT into the Bootstrap menu only on mobile devices. Essentia ...

Tips on wrapping div elements while maintaining equal width in different screen sizes using @media queries

Does anyone have a solution for wrapping divs using @media screen while maintaining equal width on all divs? I've tried using float and inline-block, but can't seem to achieve consistent justified widths. While table-cells maintain equal field wi ...

Deleting a property once the page has finished loading

My issue is a bit tricky to describe, but essentially I have noticed a CSS attribute being added to my div tag that seems to come from a node module. The problem is, I can't seem to find where this attribute is coming from in my files. This attribute ...

How to reset or clear the RangePicker in Ant Design for React

I am working with a component similar to this one and I am looking for a way to make it automatically reset after the user selects a date. Currently, once a date is selected, it remains as is until manually cleared. Current behavior: https://i.sstatic.ne ...

Make sure to assign v-model as a unique identifier within an array by setting it as a key (v-model.key="someArray")

Currently, I am working on a filter page where I need to save both the filter name and its corresponding value. To achieve this, I have set up a data structure like the following: data: { attributes: { checkboxes: [], strings: { keys: [], values: [] }, ...

Creating a responsive layout with Bootstrap4 to show two rows of three columns on smaller screens

I am looking to design a pagination feature that has the following appearance: On a wide screen: | | | [<<] Page # 1 of 6 [>>] | | ...

Creating a responsive carousel that is not yet designed

As a beginner, I'm facing what seems like a simple problem that I just can't solve. Here is the HTML code I've written: <div class="discs container"> <ul> <li><div class="arrowleft"><a href="#" >< ...