What is causing my CSS transition to break when I remove the 0ms sleep delay?

Recently, I decided to test my understanding of the FLIP animation technique by trying to implement it in a simple codepen experiment.

In the codepen (apologies for the messy code, I was just experimenting), I noticed an interesting behavior. When I removed the sleep function call that lasted for 0 milliseconds, the smooth transition stopped working and the div abruptly changed its position. This puzzled me as the sleep was set for such a short duration.

import React, { useRef, useState } from "https://esm.sh/react@18";
import ReactDOM from "https://esm.sh/react-dom@18";

let first = {}
let second =  {}

const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const App = () => {
  const [start, setStart] = useState(true);
  
  const boxRefCb = async el => {
    if (!el) return;

    el.style.transition = "";
    const x = parseInt(el?.getBoundingClientRect().x, 10);
    const y = parseInt(el?.getBoundingClientRect().y, 10);
    first = { x: second.x, y: second.y };
    second = { x, y };
    
    const dx = first.x - second.x;
    const dy = first.y - second.y;

    const transStr = `translate(${dx}px, ${dy}px)`;
    el.style.transform = transStr;
    await sleep(0); // comment me out
    el.style.transition = "transform .5s";
    el.style.transform = "";
  }
  
  return (
    <>
    <div style={{ display: "flex", gap: "1rem", padding: "3rem"}}>
      <div ref={ start ? boxRefCb : null } style={{ visibility: start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div>
      <div  ref={ !start ? boxRefCb : null } style={{ visibility: !start ? "" : "hidden", width: 100, height: 100, border: "solid 1px grey" }}></div>
    </div>
      
    <button style={{ marginLeft: "3rem"}} onClick={() => setStart(start => !start)}>start | {start.toString()}</button>
    </>
  );
}

ReactDOM.render(<App />,
document.getElementById("root"))

I have a feeling that there might be some event loop intricacies at play here which I am not fully grasping. Can someone provide insights on this phenomenon?

Answer №1

When the browser has time to recalculate the CSSOM boxes, also known as "performing a reflow," during a sleep, the transform rule may not be applied. This is because browsers typically wait until the changes are absolutely necessary before updating the entire page box model, as it can be quite resource-intensive.
For example, if you set the color of an element to red, then yellow, and finally green within a short period of time:

element.style.color = "red";
element.style.color = "yellow";
element.style.color = "green";

The CSSOM will only register the final state, which in this case is "green." The previous two changes are essentially ignored.

In your code, if you do not allow the event loop to actually loop, the value of transStr may never be recognized either.

Using a 0ms setTimeout is not a reliable solution, as there is no guarantee that the styles will be recalculated at that exact moment. It's better to manually force a recalculation using certain DOM methods/properties synchronously when needed. However, keep in mind that reflows can be costly operations, so use them sparingly and consolidate multiple instances where they are required to minimize performance impact.

Here is an example code snippet demonstrating how to handle this situation:

[Code snippet example]

To see the code in action, you can check out the demo here.

Answer №2

Your approach to this issue is currently using vanilla JavaScript, but React utilizes a virtual DOM and requires that DOM elements are re-rendered when the state changes. It would be beneficial to utilize React's state to update the XY position of the element within the virtual DOM while still incorporating CSS.

View the live demonstration here or access the code directly here:

https://codesandbox.io/s/react-dom-animations-s14n10?fontsize=14&hidenavigation=1&theme=dark


import { useState, useRef, useLayoutEffect } from "react";
import "./styles.css";

type BoxXYPosition = { x: number; y: number };

export default function App() {
  const startBox = useRef<HTMLDivElement | null>(null);
  const startBoxPosition = useRef<BoxXYPosition>({ x: 0, y: 0 });

  const endBox = useRef<HTMLDivElement | null>(null);

  const [boxPosition, setBoxPosition] = useState<BoxXYPosition>({
    x: 0,
    y: 0
  });
  const { x, y } = boxPosition;
  const hasMoved = Boolean(x || y);

  const updatePosition = () => {
    if (!endBox.current) return;

    const { x: endX, y: endY } = endBox.current.getBoundingClientRect();
    const { x: startX, y: startY } = startBoxPosition.current;

    // "LAST" - calculate end position
    const moveXPosition = endX - startX;
    const moveYPosition = endY - startY;

    // "INVERT" - recalculate position based upon current x,y coords
    setBoxPosition((prevState) => ({
      x: prevState.x !== moveXPosition ? moveXPosition : 0,
      y: prevState.y !== moveYPosition ? moveYPosition : 0
    }));
  };

  useLayoutEffect(() => {
    // "FIRST" - save starting position
    if (startBox.current) {
      const { x, y } = startBox.current.getBoundingClientRect();
      startBoxPosition.current = { x, y };
    }
  }, []);

  // "PLAY" - switch between start and end animation via the x,y state and a style property
  return (
    <main className="app">
      <h1>Transition Between Points</h1>
      <div className="container">
        <div
          ref={startBox}
          className="box start-point"
          style={{
            transform: hasMoved
              ? `translate(${x}px, ${y}px) rotateZ(360deg)`
              : ""
          }}
        >
          {hasMoved ? "End" : "Start"}
        </div>
        <div className="end-container">
          <div ref={endBox} className="box end-point" />
        </div>
      </div>
      <button
        type="button"
        onClick={updatePosition}
      >
        Move to {hasMoved ? "Start" : "End"}
      </button>
    </main>
  );
}

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

To navigate to an id or name in HTML, you must click on the anchor element twice

When using Firefox, I encountered a small issue with my buttons that have anchor links to different in-page destinations. The buttons work fine on Chrome, but on Firefox, the anchor only works on the second click. The first click just changes the URL. < ...

Error 403 CSRF forbidden encountered while utilizing the request.post.get() function within the views.py file linked to the HTML page

Here is the views.py code snippet: from django.shortcuts import render from django.http import HttpResponse from django.http import HttpRequest from django.db import models from datetime import datetime def postdata(request): if request.method == "PO ...

Using jQuery to assign each name the value of the group of radio buttons

For instance: In the database, there are currently five names available (possibly more): ANGGIE, BOB, CHUCK, DEAN, EAST. Here is the HTML code snippet: <form name = "test"> <?php $num++; ?> <td> [each name in the database] </td> ...

Having trouble with jQuery UI draggable when using jQueryUI version 1.12.1?

Currently, I am diving into the world of jQuery UI. However, I am facing an issue with dragging the boxes that I have created using a combination of HTML and CSS. My setup includes HTML5 and CSS3 alongside jQuery version 1.12.1. Any suggestions or help wou ...

Show the entered user data in a separate Div when the button is clicked

Whenever a button is clicked, I am currently able to show the user input from the textbox. However, it always displays in the same Div. The HTML file with the textbox and button- <input type="text" name="inputText"><br> <tr> &l ...

Error: When executing the npm run build command, I encountered a TypeError stating that Ajv is not a

I keep encountering an issue whenever I try to execute npm run build error: /node_modules/mini-css-extract-plugin/node_modules/schema-utils/dist/validate.js:66 const ajv = new Ajv({ ^ TypeError: Ajv is not a constructor at Object.<anon ...

Creating a personalized MUI color scheme that seamlessly flows throughout your entire app

I am eager to establish a unified color scheme for my entire project. To achieve this, I have devised a color palette within my App.js file. Here is the code snippet: import { Navigate, Route, Routes } from 'react-router-dom'; import Home from &a ...

Reactive JS memory leakage occurring from recurring calculations utilizing setInterval

I'm currently working on a task where I need to run a calculation every 5 seconds and update a component's state with the result using a setInterval timer. It seems that the updateCalculation() function is being called every 5 seconds, but I&apos ...

Selecting the next element in the DOM using Javascript

Here is the structure of my current setup: <div class="wrapper"> <div class="first"> <a class="button" href="">click</a> </div> <div class="second"> <div class="third"> S ...

Clicking outside the navigation container will not cause it to disappear completely

When the width of my navigation bar reaches a maximum of 560px, a hamburger menu appears for mobile devices. I want to implement a functionality where clicking on a navigation item (e.g., About) will close the nav-container instead of making it disappear c ...

The dialog box in CSS is extending too far down past the bottom of the screen, making it impossible to scroll and click on the buttons located

I am currently working on creating a dialog box with a text area using material UI. Unfortunately, when I input a significant amount of text, the dialog box ends up extending beyond the screen, making it impossible to scroll down to access the buttons. &l ...

Creating a Confirmation Page following Stripe Checkout

I recently learned about stripe and APIs, following a tutorial to successfully implement stripe checkout. However, I am now looking to display a receipt after completing the stripe checkout process. Does anyone know of a tutorial or can offer assistance w ...

Display the menu on the left side for mobile devices

Looking to switch up the mobile menu on my website built with Bootstrap. Rather than it dropping down from the top, I want it to slide in from left to right. Check out my markup here. https://i.sstatic.net/DAfhl.png Trying to achieve a menu layout simila ...

Adjusting the positioning of links with bootstrap scrollyspy

Having trouble getting the scrollyspy links to align properly in the center of the navbar. Can someone offer some assistance? Much appreciated! <ul class="nav nav-pills"> <li class="nav-item"> < ...

Tips for ensuring the CSRF token functions properly on the browser when utilizing Django and React

Apologies in advance if this question seems beginner-friendly, but I have developed an application with Django backend and React frontend. I am currently working on implementing the CSRF token for the post request on the create endpoint using the code snip ...

I'm struggling to comprehend why this code isn't functioning as expected

There is a need to update the displayed image. The two images are stacked on top of each other, with one having an opacity of 100 (active) and should be positioned above the inactive one based on z-index. The inactive image has an opacity of 0. When one im ...

Is it possible to retrieve information from a json file?

I am looking to extract specific elements from a JSON response fetched using the YouTube API. Here is an example of the response I receive in my script: { "version": "1.0", "encoding": "UTF-8", "feed": { // Details of the feed... } } My goal ...

Building a drop-down menu with descriptions in React is a simple process that involves using

I am currently working on implementing a dropdown list with descriptions in React. Here is an image for reference: https://i.sstatic.net/R8CYN.png Is there a different approach using bootstrap or Material-UI that I could use to achieve this? Currently, I ...

Should I use CSS, Bootstrap, or a combination of both for a complicated design in a React app

I am currently developing a react app and utilizing react-bootstrap to incorporate various components. As someone who is new to javascript, react, and web development as a whole, I find it challenging at times. My goal was to create a layout that seemed q ...

Problem with padding in Firefox when using jQuery's css() methodInconsistent behavior with padding property in

It has come to my attention that Firefox (specifically v19.0.2) is encountering an issue with the jQuery css() function when attempting to retrieve an element's padding. While using .css('padding-left') seems to be a workaround, it would be ...