The perspective property creates discrepancies in transform behavior between Firefox and Chrome, resulting in unexpected outcomes

While I found a similar question here, the solutions provided are outdated and do not help in my case.

My issue revolves around a turning page effect that utilizes the CSS properties of transform-style:preserve-3d and perspective to create a 3D page. The turning page effect is animated using keyframes, where buttons trigger the application and removal of styles with each keyframe.

The problem mainly lies with the perspective property – when removed, the 3D effect is lost but it functions properly on both Firefox and Chrome. However, with the perspective set to 1000px, the page turning effect appears distorted in Firefox only. In contrast, Chrome displays the page turning as expected (with the illusion of the page going up while turning). Essentially, the front of the page is red while the back is green.

My goal is to maintain the 3D effect with the perspective set at 1000px and for the page to turn in Firefox resembling how it operates in Chrome.

If anybody has a suitable solution or workaround, I would greatly appreciate it.

function turnLeft() {
  page = document.getElementById('page');
  page.classList.remove('turnRight')
  page.classList.add('turnLeft')
}

function turnRight() {
  page = document.getElementById('page');
  page.classList.remove('turnLeft')
  page.classList.add('turnRight')
}
body {
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  height: 100vh;
  width: 100vw;
  background-color: aliceblue;
  perspective: 1000px;
}

.page {
  position: absolute;
  left: 50%;
  top: 50%;
  height: 50%;
  width: 25%;
  transform: translateY(-50%) rotateX(25deg);
  -moz-transform: translateY(-50%) rotateX(25deg);
  border: solid 1px black;
  transform-origin: 0% 45%;
  transform-style: preserve-3d;
}

.front {
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  background-color: red;
  transform-style: preserve-3d;
}

.back {
  position: absolute;
  box-sizing: content-box;
  border: solid 2px #000;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  transform: rotate3d(0, 1, 0, 180deg);
  -moz-transform: rotate3d(0, 1, 0, 180deg);
  background-color: green;
  transform-style: preserve-3d;
}

.turnLeft {
  -moz-animation: turnPageToLeft 0.4s ease-in 0s 1 normal forwards;
  animation: turnPageToLeft 0.4s ease-in 0s 1 normal forwards;
}

.turnRight {
  -moz-animation: turnPageToRight 0.4s ease-in 0s 1 normal forwards;
  animation: turnPageToRight 0.4s ease-in 0s 1 normal forwards;
}

@keyframes turnPageToLeft {
  0% {
    transform: translateY(-50%) rotateX(25deg);
  }
  100% {
    transform: rotate3d(0, 1, 0, -180deg) translateY(-51%) rotateX(-25deg) scale(1, 0.99);
  }
}

@-moz-keyframes turnPageToLeft {
  0% {
    transform: translateY(-50%) rotateX(25deg);
  }
  100% {
    transform: rotate3d(0, 1, 0, -180deg) translateY(-51%) rotateX(-25deg) scale(1, 0.99);
  }
}

@keyframes turnPageToRight {
  0% {
    transform: rotate3d(0, 1, 0, -180deg) translateY(-51%) rotateX(-25deg)scale(1, 0.99);
  }
  100% {
    transform: translateY(-50%) rotateX(25deg);
  }
}

@-moz-keyframes turnPageToRight {
  0% {
    transform: rotate3d(0, 1, 0, -180deg) translateY(-51%) rotateX(-25deg)scale(1, 0.99);
  }
  100% {
    transform: translateY(-50%) rotateX(25deg);
  }
}
<body>
  <div id="page" class="page">
    <div class="front"></div>
    <div class="back"></div>
  </div>
  <button onclick="turnLeft()">Turn Left</button>
  <button onclick="turnRight()">Turn Right</button>
</body>

Answer №1

Modify this part:

transform: rotate3d(0, 1, 0, -180deg)

to:

transform: rotate3d(0, 1, 0, -179deg)

There are technically two correct ways to animate from 180 to -180; however, Firefox tends to choose the "downward and away" path which does not fit this specific effect. By adjusting the animation from 180 to -179, it ensures that the "up and over" path is always selected by both browsers.

function turnLeft() {
  page = document.getElementById('page');
  page.classList.remove('turnRight')
  page.classList.add('turnLeft')
}

function turnRight() {
  page = document.getElementById('page');
  page.classList.remove('turnLeft')
  page.classList.add('turnRight')
}
body {
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  height: 100vh;
  width: 100vw;
  background-color: aliceblue;
  perspective: 1000px;
}

.page {
  position: absolute;
  left: 50%;
  top: 50%;
  height: 50%;
  width: 25%;
  transform: translateY(-50%) rotateX(25deg);
  border: solid 1px black;
  transform-origin: 0% 45%;
  transform-style: preserve-3d;
}

.front {
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  background-color: red;
  transform-style: preserve-3d;
}

.back {
  position: absolute;
  box-sizing: content-box;
  border: solid 2px #000;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  transform: rotate3d(0, 1, 0, 180deg);
  background-color: green;
  transform-style: preserve-3d;
}

.turnLeft {
  animation: turnPageToLeft 0.4s ease-in 0s 1 normal forwards;
}

.turnRight {
  animation: turnPageToRight 0.4s ease-in 0s 1 normal forwards;
}

@keyframes turnPageToLeft {
  0% {
    transform: translateY(-50%) rotateX(25deg);
  }
  100% {
    transform: rotate3d(0, 1, 0, -179deg) translateY(-51%) rotateX(-25deg);
  }
}


@keyframes turnPageToRight {
  0% {
    transform: rotate3d(0, 1, 0, -179deg) translateY(-51%) rotateX(-25deg);
  }
  100% {
    transform: translateY(-50%) rotateX(25deg);
  }
}
<body>
  <div id="page" class="page">
    <div class="front"></div>
    <div class="back"></div>
  </div>
  <button onclick="turnLeft()">Turn Left</button>
  <button onclick="turnRight()">Turn Right</button>
</body>

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

Send image data in base64 format to server using AJAX to save

My goal is to store a base64 image on a php server using the webcam-easy library (https://github.com/bensonruan/webcam-easy). I added a button to the index.html file of the demo: <button id="upload" onClick="postData()" style=" ...

Implementing CSS counter-increment with jQuery

Is there a way to use jQuery to set the CSS counter-increment attribute on ".demo:before" even though jQuery cannot access pseudo elements directly? I recall seeing a suggestion on Stack Overflow about using a data attribute and then referencing that value ...

Changing the navigation bar's position from fixed to static while scrolling using Bootstrap

I am attempting to keep the navbar at the top by setting a fixed position with 'top: 0' on scroll for navbar-default, and return the navbar to its original position when scrolling up (to top 300). Below is my code: var height = jQuery('.n ...

What is the most effective method for preserving RichText (WYSIWYG output)?

I am currently using a JavaScript-based rich text editor in my application. Could you suggest the most secure method to store the generated tags? My database is MySQL, and I have concerns about the safety of using mysql_real_escape_string($text);. ...

Navigating to a randomly generated ID in Firestore using Vue - A Step-by-Step Guide

My app uses Firestore as a backend for posts. On the screen displaying all categories, when a new category is created it's added to the list. When a category is clicked, I use its id as a parameter to navigate to that specific category and show the po ...

What is the best method to choose a random color for the background when a button is pressed?

Does anyone know how to create a button that changes the background color of a webpage each time it is clicked? I've been having trouble with the javascript function in my code. I've tried multiple solutions but nothing seems to work. Could someo ...

Modernizing web design with Bootstrap

Attempting to create a unique column layout for different device sizes. For medium (md) and larger devices, 2 columns are used: md-8 and md-4. However, on smaller devices, the intention is to display half of the content in the md-8 column, followed by half ...

Having trouble getting THREE.Raycaster to intersect with THREE.PointCloud

Currently, I am trying to implement click events on my WebGL based 3D graph library called Graphosaurus. You can take a look at what I have done so far here. I have used this example as a reference. I am wondering if the reason it is not functioning corr ...

Using AJAX to submit a single form

Within my HTML view, I have multiple forms that are displayed using a PHP foreach loop. One of the form examples is as follows: <form method="POST" class="like-form-js"> <input type="hidden" name="post_id" value="<?= $post['i ...

To view the contents of the dropdown list on an iPhone, simply tap the arrow key next to the mobile dropdown list and the contents will be

I am attempting to trigger the dropdown list contents to open/show by tapping on the arrow keys near AutoFill on the iPhone mobile keyboard. This action should mimic clicking on the dropdown itself. Currently, I am working on implementing this feature for ...

Switching to AngularJS for an upgrade

I am working with an AngularJS code and HTML. <input type="text" ng-model="selected" uib-typeahead="demos for demos in demo | filter:$viewValue | limitTo:8"> $scope.demo = ['demo-NV-enable','demo-NV-disable','demo-NV-shutdo ...

What is the process for incorporating dynamic templates in AngularJS?

I have created 3 unique templates (DetailView, CardView, Column) for displaying content on a single page. Users can easily switch between these views. My goal is to display only one view at a time on the page. When the user switches views, I want to remov ...

Determining the pixel padding of an element that was initially set with a percentage value

I am working with a div element that has left padding assigned as a percentage, like so: padding-left: 1%; However, I need to obtain the value of this padding in pixels for some calculations after the window has been resized. When using JavaScript to chec ...

Create a dynamic menu dropdown with absolute positioning using React

I recently made the transition to React and now I am in the process of converting my old vanillaJS website into ReactJS. One issue I am facing is with a button that is supposed to trigger the opening of a dropdown menu. <button type="button&qu ...

The most recent update of Blink does not support the complex selector :nth-child(2):nth-last-child(2){}

There is an unusual issue: after a recent update, the selector .groups .group:nth-child(2):nth-last-child(2){} suddenly stopped working. However, it still works perfectly in Webkit and Gecko browsers. Any thoughts on how to resolve this? Snippet of HTML ...

Tips for inserting active class on the main menu and adding an active class along with an icon on the sidebar menu

I am working on a website where I want to add the active class only when a user clicks on the top menu. However, if the user chooses something from the sidebar menu, I also want to add an icon arrow next to it. Any ideas on how I can achieve this? Check o ...

Expanding to a full width of 100%, images on desktop screens are displayed larger than their original

I am facing a challenge with my three images lined up in a row, each with a width of 323px. In order to make my website responsive, I decided to switch from using width: 323px; to width: 100%;. While this adjustment worked well on mobile devices, I encoun ...

The accuracy of getBoundingClientRect in calculating the width of table cells (td)

Currently, I am tackling a feature that necessitates me to specify the CSS width in pixels for each td element of a table upon clicking a button. My approach involves using getBoundingClientRect to compute the td width and retrieving the value in pixels (e ...

Angucomplete Alternative solves the challenge of accessing remote URLs

I have been using the Angucomplete Alt directive for creating an autocomplete feature. It has been working well so far, but now I want to customize a specific request to be sent to my server. <div angucomplete-alt id="input-name" ...

Why Electron is refusing to open a JSON file

Currently, I am attempting to populate filenames using JSON for the onclick='shell.openItem('filename') function. While the console.log(data[i].url) correctly displays the kmz files for each button, I encounter an error when clicking a butto ...