css transform: translate transition showing unexpected behavior

Check out this sandbox where I've recreated the classic sliding puzzle game.

In my component called GameBlock, I'm using a mix of css properties like transform: translate(x,y) and transition: transform to create animations for the sliding game pieces:

const StyledGameBlock = styled.div<{
  index: number;
  isNextToSpace: boolean;
  backgroundColor: string;
}>`
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  width: ${BLOCK_SIZE}px;
  height: ${BLOCK_SIZE}px;
  background-color: ${({ backgroundColor }) => backgroundColor};
  ${({ isNextToSpace }) => isNextToSpace && "cursor: pointer"};

  ${({ index }) => css`
    transform: translate(
      ${getX(index) * BLOCK_SIZE}px,
      ${getY(index) * BLOCK_SIZE}px
    );
  `}

  transition: transform 400ms;
`;

Essentially, I calculate the block's current index on the board to determine its x and y values which in turn affect the transform: translate property when it's being moved.

Although this approach smoothly transitions the block when moving it upwards, to the right, and to the left, there seems to be an issue with the smoothness of the transition when sliding the block from top to bottom.

Any thoughts on what might be causing this particular problem?

Answer №1

React Components and Key Prop Usage

The current issue stems from the mount/unmount process of <GameBlock /> components in React.
Although a key prop is being passed to the component, React is uncertain if it's rendering the same element.

If I were to pinpoint the cause of React's uncertainty, it would likely be attributed to:

  • Altering the array sort using:
 const previousSpace = gameBlocks[spaceIndex];
     gameBlocks[spaceIndex] = gameBlocks[index];
     gameBlocks[index] = previousSpace;
  • Generating different virtual DOM outcomes based on the condition used for isSpace:
 ({ correctIndex, currentIndex, isSpace, isNextToSpace }) => isSpace ? null : ( <GameBlock             ....    

In most cases, remounting isn't a concern for applications as it occurs quickly. However, when incorporating animations, avoid remounts to prevent interference with CSS transitions.
To ensure React recognizes the same node without requiring a remount, maintain consistency in the virtual DOM between renders by preserving identical keys. Achieving this involves straightforward list rendering without complex alterations, ensuring consistent key passages across renders.

Passing 'isSpace' Down

Instead of modifying rendered DOM nodes, prioritize retaining an equal count of nodes in list rendering with matching keys in the same sequence.

Simply pass down 'isSpace' and apply styling like display:none; to achieve this goal.

 <GameBlock
      ...
      isSpace={isSpace}
      ...
  >

const StyledGameBlock = styled.div<{ ....}>`
   ...
  display: ${({isSpace})=> isSpace? 'none':'flex'};
   ...  
`;

Maintaining Array Sort Consistency

React detects modifications to the gameBlocks array due to keys being rearranged, triggering unmount/remount cycles of rendered <GameBlock/> components. To affirm that React views the array as unaltered, solely modify item properties within the list without changing the sorting order itself.

In your scenario, preserve all properties unchanged, only updating the currentIndex for blocks subject to movement or swapping.

 const onMove = useCallback(
    (index) => {
      const newSpaceIndex = gameBlocks[index].currentIndex; // space adopts clicked block's index.
      const movedBlockNewIndex = gameBlocks[spaceIndex].currentIndex; // clicked block assumes space's index.

      setState({
        spaceIndex: spaceIndex, // space maintains constant position in array.
        gameBlocks: gameBlocks.map((block) => {
          const isMovingBlock = index === block.correctIndex; // identifies clicked block
          const isSpaceBlock =
            gameBlocks[spaceIndex].currentIndex === block.currentIndex;  // identifies space block 
          let newCurrentIndex = block.currentIndex; // most blocks remain stationary
          if (isMovingBlock) {
            newCurrentIndex = movedBlockNewIndex; // moving block swaps with space
          }
          if (isSpaceBlock) {
            newCurrentIndex = newSpaceIndex; // space swaps with moving block
          }
          return {
            ...block,
            currentIndex: newCurrentIndex,
            isNextToSpace: getIsNextToSpace(newCurrentIndex, newSpaceIndex)
          };
        })
      });
    },
    [gameBlocks, spaceIndex]
  );


...
// Ensure onMove is called with index of clicked block
() => onMove(correctIndex) 

Changes are limited to altering the currentIndex for clicked block and space.

Code Sandbox Examples:

Access sandbox example inspired by the provided sandbox link.

Closing Remarks: Your code readability and comprehension are commendable. Well done!

Answer №2

In addition to the fantastic response and clarifications from @Lars, I wanted to provide visual evidence that specific <GameBlock /> elements are indeed being unmounted or changed in sequence, which is causing a disruption in the CSS animation.

If you take a look, when focusing on one of the blocks and scrolling down, you can see the element shifting its position in the DOM.

https://i.sstatic.net/Gb1OP.gif

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

Guide on positioning the Bootstrap accordion button ahead of text and modifying its appearance

Check out this link for a bootstrap accordion with plus and minus icons: I'm looking to put buttons similar to the ones shown in this image: https://ibb.co/GJRGKnn Additionally, I would like to change the style of the buttons to match what's di ...

How to Alter the Color of a Hyperlink in HTML

I've been working on an angular2 application and I'm using a hyperlink to trigger a method that opens a small popup window. I've tried different methods to change the color of the link when it's clicked, but so far none have worked. Th ...

Issue with CSS background color not extending to full screen in webkit browsers when using 960.gs grid system

When using 960.gs, I have created a login screen with a div for the login form that has a background image. This div is positioned below a header, which centers it in the browser window. In my CSS, I am setting the body color as follows: body { backgr ...

Maximizing the efficiency of critical rendering path while utilizing bootstrap

Is it feasible to enhance the critical rendering path (like Google and Facebook) while utilizing Bootstrap 3? Facebook opted for inlining styles connected to the header and sidebars. Meanwhile, Google inlined all styles since they have minimal styles for ...

Exploring Mui theme direction within the latest version of next.js - version

I currently have a theme provider set up like this: const CustomProvider = ({ children }: { children: React.ReactNode }) => { return ( <AppThemeCacheProvider> <RtlConfig> <ThemeProvider theme={theme ...

JavaScript text spacing

I'm currently utilizing the JavaScript code snippet below to clear out the existing content within a div element in order to insert new elements. However, I've encountered an issue where once the div is cleared, its CSS styling is lost. I am atte ...

React will only render components that correspond to existing properties in the array

I'm looking to show the following components only if their href property is present: <ExternalLinks href={source}>Code</ExternalLinks> <ExternalLinks href={download}>Download</ExternalLinks> <ExternalLinks href={visit}> ...

Having trouble getting padding to work inside a pre block, even when using a wrapper div?

Snippet of HTML Code: <div class="prewrap"> <pre> stepsize = .01 samplestimes = 30 universex = seq(-1, 1, stepsize) universey = sin(pi * universex) </pre> </div> Sample CSS Styles: #prewrap { background-color: #e3e3e3; pa ...

What is the best way to add a line next to a specific word in a

For my report, I need to create multiple lines with automated dashes without having to use the SHIFT plus underscore keyboard shortcut. I searched for a solution but couldn't find anything that addressed my specific issue. I envision something like t ...

What is the best way to automatically adjust the size of this HTML Menu to match the width of its

I am in the process of converting a Drupal 6 theme to Drupal 7 and I'm encountering some difficulties with this particular section. Below is the HTML code snippet: <ul id="nav" class=" scaling-active scaling-ready"> <li><a href="/demos ...

Optimizing Image Size According to the Container, Not the Screen Size: A Guide

After exploring various resources on responsive images, I have come across a challenge that has me stumped. It seems like it should be simple, but I can't quite figure it out: How can I serve the appropriate image size based on container width rather ...

Migrating from Styled Components to Material-UI: Is it possible for Material-UI to apply `withStyles()` to a div element

My app's logic and operation aspects are solid, so I can't risk altering components that switch from <FriendlyComponentName /> to <div css={{styleObject}} /> using Material makeStyles() or similar methods. An example of existing Styl ...

Implementing Cross-Origin Resource Sharing (CORS) in Express.js to facilitate communication between various ports

Struggling to make API calls using React and Axios from my front end (localhost:3000) to my back end (localhost:4567) is proving challenging. The consistent error message I encounter is: The CORS policy is blocking access to XMLHttpRequest at 'localh ...

Adding a loader to the specific button that has been clicked can be achieved by following these steps:

I am currently in the process of building an e-commerce platform website and I'm looking to implement a feature where users can add products to their cart with just a click of a button. However, before the product is added to the cart, I want to disp ...

Issue with mouseMove function not aligning correctly with object-fit:contain CSS property

My current code allows users to select a color from an image when hovering over the pixel with the mouse. However, I am encountering an issue where the colors do not map correctly when using object-fit: contain for the image. The script seems to be treatin ...

Do LI elements need to be floated left for right alignment within a container?

I have a code where the LI elements are floated left and aligned to the left of the container. I want to change their alignment to the right using CSS. How can I achieve this? For example: [Item1..................................Item2] Below is the HTML ...

Use CSS to position three buttons in the center of a row by aligning them horizontally

I need help aligning three buttons in the same row and centered vertically. I want them to be aligned in the same way as the text "Select your attack:" Please advise on how to achieve this alignment. .prompt_user_to_choose { margin: 0; display: flex; ...

Implementing a feature in React.js to pass parameters to another component and display list details from an API upon button click

I am a beginner in reactJS and I have created a Listview that displays a list of posts. There is a button on the page that should take me to another component or page where I can view the comments for that post. How do I pass the postId to the other compon ...

Issue with Font Awesome (6.2) Duotone not displaying correctly in Bootstrap 5 breadcrumb divider

I attempted to modify the Bootstrap 5.2 breadcrumb divider and include a Font Awesome Duotone icon, but it is not displaying. I am unable to figure out what the issue might be. Any suggestions would be greatly appreciated. Thank you. One interesting disco ...

Is there a way to have the collapsible content automatically expanded upon loading?

I came across a tutorial on w3school (https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_collapsible_symbol), which demonstrates collapsible content hidden by default. The code snippet from the link is provided below. Can someone assist me in cha ...