DEV Community

terrierscript
terrierscript

Posted on

Create animating element on CSS Grid with React Hooks

In general, grid-row and grid-columns cannot transition and that is hard to create moving animation element.

I found that enable with use create element for calculate.

For example, I try to reproduce UpLabs SmoothBottomBar.

And It's can succeed

demo & Full Source Code

5

Making Step

1. Create base grid

First, I create bare gird.

Preview

1

Full Code

https://stackblitz.com/edit/react-ts-animation-grid-1

Description

Main grid is here. I use react-icons. It's very useful.

That icons array maybe tricky.

import { FiUser, FiHome, FiInbox } from "react-icons/fi"

export const Menu1 = () => {
  const icons = [FiHome, FiInbox, FiUser]

  return (
    <Container>
      <MenuGrid>
        {icons.map((Icon) => (
          <IconWrap>
            <Icon />
          </IconWrap>
        ))}
      </MenuGrid>
    </Container>
  )
}
Enter fullscreen mode Exit fullscreen mode

2. Create element for calcurate.

Next, append cell for calcurate.

Preview

2

Full Code

https://stackblitz.com/edit/react-ts-animation-grid-2

Description

I need to append icons to grid columns and append get cell position state.


// Item got props that passed to `grid-column`
const Item = styled.div`
  ${({ x }) => css`
    grid-column: ${x};
  `}
  grid-row: 1;
`

export const Menu2 = () => {
  const [gridPosition, setGridPosition] = useState<number>(1)

  // ...
        {icons.map((Icon, i) => (
          <Item x={i + 1} onMouseOver={(e) => setGridPosition(i + 1)}>
            <IconWrap>
              <Icon />
            </IconWrap>
          </Item>
        ))}
  // ...
}
Enter fullscreen mode Exit fullscreen mode

and append cell that named PositionCalcurator.


// PositionCalcurator extends Item. That can set row and cell.
const PositionCalcurator = styled(Item)`
  border: 1px solid red; // for debug
`

export const Menu2 = () => {
  const [gridPosition, setGridPosition] = useState<number>(1)
  const calcuratorRef = useRef<HTMLElement>(null) // calucrator has `ref`

  const icons = [FiHome, FiInbox, FiUser]

  return (
    <Container>
      {/* ... */}
        <PositionCalcurator ref={calcuratorRef} x={gridPosition} />
      {/* ... */}
    </Container>
  )
}
Enter fullscreen mode Exit fullscreen mode

3. Append animating cursor.

Next, append hover cursor.

Preview

3

Full Code

https://stackblitz.com/edit/react-ts-animation-grid-3

Description

I set ref to PositionCalcurator, now we can get calcurated refs rect position that same as grid position.

I create cursorRect state and hook with useEffect. This effect called when change gridPosition.(but it's may be warn when use eslint-plugin-react-hooks)

export const Menu3 = () => {
  const [gridPosition, setGridPosition] = useState<number>(1)
  const [cursorRect, setCursor] = useState<null | Rect>(null) // append
  const calcuratorRef = useRef<HTMLElement>(null)
  useEffect(() => {
    if (!calcuratorRef.current) return
    const top = calcuratorRef.current.offsetTop
    const left = calcuratorRef.current.offsetLeft
    const width = calcuratorRef.current.clientWidth
    const height = calcuratorRef.current.clientHeight
    const cursor = { top, left, width, height }
    setCursor(cursor)
  }, [gridPosition])

Enter fullscreen mode Exit fullscreen mode

I append Cursor component that passed cursorRect. This element enable animation because isolated from grid and set position:absolute and animating attributes.


const Cursor = styled.div`
  position: absolute;
  transition: 0.4s;
  transition-timing-function: ease-in-out;
  background: green;
  opacity: 0.5;
  ${({ top, left, width, height }) => css`
    top: ${top}px;
    left: ${left}px;
    width: ${width}px;
    height: ${height}px;
  `};
`
export const Menu3 = () => {
  const [cursorRect, setCursor] = useState<null | Rect>(null) // append

  // ...
  return (
    <Container>
      {/* Append first for z-index! */}
      {cursorRect && <Cursor {...cursorRect} />}

Enter fullscreen mode Exit fullscreen mode

<Cursor> needs first element on Container because of z-index.

4. Text animation.

This is not related grid animation, but I follow original.

Preview

4

Full Cod

https://stackblitz.com/edit/react-ts-animation-grid-4

Description

I want to use only :hover pseudo, but I append active prop because it's can't sync to cursor state

export const Menu4 = () => {
  const [gridPosition, setGridPosition] = useState<number>(1)
  const isActive = useCallback((x) => gridPosition === x, [gridPosition])
  const icons = [["Home", FiHome], ["Inbox", FiInbox], ["Profile", FiUser]]
  return (
    {/* ... */}
    {icons.map(([text, Icon], i) => (
      <Item x={i + 1} onMouseOver={(e) => setGridPosition(i + 1)}>
        <AnimateIcon
          x={i + 1}
          onMouseOver={(e) => setGridPosition(i + 1)}
          text={text}
          active={isActive(i + 1)}
        >
          <Icon />
        </AnimateIcon>
      </Item>
    ))}
  )
Enter fullscreen mode Exit fullscreen mode

and change AnimateIcon

const AnimateIconInner = styled(IconWrap)`
  transition: 0.5s;
  ::after {
    font-size: 0.6em;
    transition: 0.5s;

    overflow: hidden;
    content: attr(data-text);
    ${({ active }) => css`
      width: ${active ? "100%" : "0px"};
    `}
  }
`

const AnimationContainer = styled.div`
  width: auto;
`

const AnimateIcon = ({ x, onMouseOver, active, children, text }) => {
  return (
    <Item x={x} onMouseOver={onMouseOver}>
      <AnimateIconInner active={active} data-text={text}>
        <AnimationContainer>{children}</AnimationContainer>
      </AnimateIconInner>
    </Item>
  )
}
Enter fullscreen mode Exit fullscreen mode

5. Turning & Finish.

Something turned version is here ( same as first demo)

5

Full Code: https://stackblitz.com/edit/react-ts-animation-grid-final

Conclusion

I cannot found CSS Grid animating with raw css.
On the other hand, it seems useful to be able to use the grid as coordinate calculation.

I create another pattern like Mega man stage select.
https://amination-grid-menu.netlify.com/

Happy animation with CSS Grid!

Top comments (4)

Collapse
 
mlaopane profile image
Mickaël

Hello,

You are using a lot of stuctures coming from nowhere.

This is actually not understandable from my point of view.

Collapse
 
terrierscript profile image
terrierscript

Thank you for comment!
I appended source code each step and refactor document format. Please read that if you need.

In addition, this article is for hobbies or experimental, and not for production code 👻

Collapse
 
digitalg profile image
Daniel North

I'm pretty sure this effect can be accomplished with raw css.

Buttons as labels for radio buttons and element behind just changing its position depending on radio checked.

Collapse
 
terrierscript profile image
terrierscript • Edited

Of cource, this is one of the trivial solution!
It's main point is animating css grid.