onMouseMove causes component to completely re-render

I’m trying to build a section like in the screen capture below (it’s the intro block of our current website). For the movement of the individual images, I’m using translate(X, Y).

Screen capture:
https://drive.google.com/file/d/1S1WKFlw5Q9k6QtTBYJwEfoI1kgTY4mT-/view?usp=sharing

I managed to re-create it with Frontity/React, but I the animation seems way less smooth than what we did on our current site. I noticed that the component completely re-renders when applying the new CSS values. Isn’t that more heavy than it should be?

Can I change something, so only the transformation will be applied, instead of a complete re-rendering? Or is this something that is caused by the core of React?

This is my code for the images:

import React, { useEffect, useState } from "react";
import { connect, styled, css } from "frontity";
import FeaturedMedia from "./featured-media";

const FeaturedProjectImages = ({ state, actions, imgs, imagesKeys, item }) => {
   useEffect(() => {
      actions.source.fetch(`/media/${imgs}`);
   }, []);

   const img = state.source.get(`/media/${imgs}`);

   // setting up an array with multipliers
   const imageProperties = {
      'back_parallax_layer': {
         'multiplier': 0.025,
         'size': '110%',
      },
      'middle_parallax_layer': {
         'multiplier': 0.05,
         'size': '110%',
      },
      'front_parallax_layer': {
         'multiplier': 0.075,
         'size': '110%',
      },
      'static_layer': {
         'multiplier': 0.0025,
         'size': '75%',
      },
      'top_static_layer': {
         'multiplier': 0.1,
         'size': '80%',
      },
   };

   const { mouseMove } = actions.theme;

   return (img.isReady ? (
      <FeaturedProjectImagesWrapper onMouseMove={(event) => { mouseMove({ event: event }) }} onMouseLeave={resetMove} className="mouseMoveTarget">
         {
            imagesKeys.map(key => {

               return (
                  <FeaturedMedia
                     id={item.acf.intro_block_images[key].id}
                     css={css`
                        position: absolute;
                        height: ${imageProperties[key].size};
                        width: ${imageProperties[key].size};
                        transform: translate(${state.theme.introBlockPosition.x * imageProperties[key].multiplier}px, ${state.theme.introBlockPosition.y * imageProperties[key].multiplier}px);
                        will-change: transform;
                        transition: .05s ease-in-out;
                        transition-property: all;
                        transition-property: transform;
                        margin-top: auto;
                        margin-bottom: auto;
                        margin-left: auto;
                        margin-right: auto;
                        top: 0;
                        bottom: 0;
                        left: 0;
                        right: 0;
                     `}
                  />
               )
            })
         }
      </FeaturedProjectImagesWrapper>
   ) : null);
}

export default connect(FeaturedProjectImages);

const FeaturedProjectImagesWrapper = styled.div`
   width: 100%;
   height: 100%;
`;

And this is my action in the index.js:

const marsTheme = {
  ...
  state: {
    theme: {
      ...
      introBlockPosition: {
        x: 0,
        y: 0,
        wait: false
      },
    },
  },
  /**
   * Actions are functions that modify the state or deal with other parts of
   * Frontity like libraries.
   */
  actions: {
    theme: {
      ...
      mouseMove: ({ state }) => ({ event }) => {
        event.preventDefault();
        event.stopPropagation();

        if (state.theme.introBlockPosition.wait == false) {
          const mouseMoveTarget = document.querySelector('.mouseMoveTarget');

          state.theme.introBlockPosition.x = (mouseMoveTarget.offsetWidth / 2 + (event.clientX - mouseMoveTarget.offsetWidth));
          state.theme.introBlockPosition.y = (mouseMoveTarget.offsetHeight / 2 + (event.clientY - mouseMoveTarget.offsetHeight));

          state.theme.introBlockPosition.wait = true;

          // setting a timeout to throttle the animation somewhat
          setTimeout(function () {
              state.theme.introBlockPosition.wait = false;
              // console.log('waiting no more');
          }, 25);
        }
      }
    },
  },
};

Hi @dominique

I admit that I’ve not done much with CSS transforms, but rather than doing the translate inside the <FeaturedMedia> component with css would it not make more sense to do it in a styled component, then it’s just the CSS that’s being updated rather than the component itself.

I’m not sure, it’s just a theory, but it might mean that the component doesn’t re-render on every mouse move.

I think it might be worth a try. Let me know how you get on if you decide to try this.

Hi @mburridge, I thought so as well, so I tried it already and it does seem to make things better, although the component still gets re-rendered completely.