1

I have a component that I want to edit some style dynamically. When I enter the component I want it to smoothly grow and when I leave I want it to smoothly shorten.

This is my code:

  const easing = 0.1;
  const outScale = 0.6;
  const inScale = 1;
  let targetScale = outScale;
  let elementScale = targetScale;
  let innerScale = 1 / elementScale;

  const [parentTransformStyle, setParentTransformStyle] = useState(
    `scale(${elementScale})`
  );
  const [innerTransformStyle, setInnerTransformStyle] = useState(
    `scale(${innerScale})`
  );
  const requestRef = useRef();

  const onPointOverEvent = () => {
    targetScale = inScale;
    console.log("over", inScale, targetScale);
  };

  const onPointerOutEvent = () => {
    targetScale = outScale;
    console.log("out", outScale, targetScale);
  };

  const animate = () => {
    console.log("animate", targetScale);

    elementScale += (targetScale - elementScale) * easing;
    innerScale = 1 / elementScale;

    setParentTransformStyle(`scale(${elementScale})`);
    setInnerTransformStyle(`scale(${innerScale})`);

    requestAnimationFrame(animate);
  };

  useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  }, []);

The variable targetScale is supposed to be updated when the mouse hovers in the element and when the mouse leaves the element.

If I print the value inside the event listeners, the value is correct. However inside the animate function, it has the initial value. Can someone point me to what I'm missing?

1 Answer 1

2

Is there any reason you can't use CSS transformations instead of doing the animation yourself?

Example:

function MyComponent() {
  const [innerTransformStyle, setInnerTransformStyle] = React.useState(
    `scale(${0.6})`
  );

  const onPointOverEvent = () => {
    setInnerTransformStyle(`scale(${1})`);
  };

  const onPointerOutEvent = () => {
    setInnerTransformStyle(`scale(${0.6})`);
  };
  
  return (
    <div className="parent">
      <div className="element" style={{transform: innerTransformStyle}} onPointerOut={onPointerOutEvent} onPointerOver={onPointOverEvent}></div>
    </div>
  );
}

ReactDOM.render(<MyComponent/>, document.getElementById("app"));
.parent {
  width: 100px;
  height: 100px;
  background-color: #f00;
}

.element {
  width: 100px;
  height: 100px;
  background-color: #0f0;
  transition: transform 1s;
}
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<div id="app"></div>

Sign up to request clarification or add additional context in comments.

2 Comments

I've implemented this solution and it indeed does work. However because I have two animations that need to work in inverse scale (hence the bit elementScale += (targetScale - elementScale) * easing; innerScale = 1 / elementScale;), a more css oriented solution gives a bit of a wobbly effect (even though I prefere it, as no infinite loops are required). The example I'm trying to convert to react from vanilla was smooth all the way.
Can you include an executable demo of the wobbly animation?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.