Create animating element on CSS Grid with React Hooks 
Tags: react, css, cssinjs, animation
Published: 2019-11-06 00:46:53

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](https://www.uplabs.com/posts/smoothbottombar-android-library).

And It's can succeed

[demo & Full Source Code](https://stackblitz.com/edit/react-ts-animation-grid-final)

![5](https://user-images.githubusercontent.com/13282103/68121377-f8e60e00-ff4a-11e9-966f-2f8985f6a0d8.gif)



## Making Step
### 1. Create base grid

First, I create bare gird. 

#### Preview
![1](https://user-images.githubusercontent.com/13282103/68120744-7c066480-ff49-11e9-8c00-f32af06fc19e.png)

#### Full Code 

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

#### Description

Main grid is here. I use [`react-icons`](https://react-icons.netlify.com/#/). It's very useful.

That `icons` array maybe tricky. 

```tsx
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>
  )
}
```



### 2. Create element for calcurate.


Next, append cell for calcurate.


#### Preview

![2](https://user-images.githubusercontent.com/13282103/68121380-f97ea480-ff4a-11e9-9e4c-f1516468eea2.gif)

#### 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.

```jsx

// 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>
        ))}
  // ...
}
```

and append cell that named `PositionCalcurator`.

```tsx

// 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>
  )
}
```


### 3. Append animating cursor.

Next, append hover cursor.

#### Preview
![3](https://user-images.githubusercontent.com/13282103/68121379-f97ea480-ff4a-11e9-894d-24bb18e308c1.gif)

#### 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`)


```jsx
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])
  
```


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

```jsx

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} />}

```

`<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](https://user-images.githubusercontent.com/13282103/68121378-f97ea480-ff4a-11e9-9339-792b24d367d5.gif)

#### 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


```jsx
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>
    ))}
  )
```

and change `AnimateIcon`

```jsx
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>
  )
}
```



### 5. Turning & Finish.

Something turned version is here ( same as first demo)

![5](https://user-images.githubusercontent.com/13282103/68121377-f8e60e00-ff4a-11e9-966f-2f8985f6a0d8.gif)

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!