发布于 2026-01-06 5 阅读
0

使用 React Hooks 在 CSS Grid 中创建动画元素

使用 React Hooks 在 CSS Grid 中创建动画元素

一般来说,grid-row无法grid-columns进行过渡,而且很难创建动态动画元素。

我发现使用创建元素进行计算是可行的。

例如,我尝试复现UpLabs SmoothBottomBar

而且它能够成功

演示和完整源代码

5

迈步

1. 创建基础网格

首先,我创建裸网格。

预览

1

完整代码

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

描述

主网格在这里。我用的是它react-icons。它非常实用。

那个icons数组可能有点棘手。

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. 创建用于计算的元素。

接下来,添加用于计算的单元格。

预览

2

完整代码

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

描述

我需要向网格列中添加图标,并获取单元格位置状态。


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

并附加名为“.”的单元格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. 添加动画光标。

接下来,添加悬停光标。

预览

3

完整代码

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

描述

我已将其设置refPositionCalcurator,现在我们可以获得与网格位置相同的计算参考矩形位置。

我创建了cursorRect一个状态和一个钩子useEffect。当状态改变时,这个效果会被调用gridPosition。(但使用时可能会有警告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

我添加了Cursor传递过来的组件cursorRect。该元素启用了动画功能,因为它与网格隔离,position:absolute并且设置了动画属性。


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>Container由于 z-index 的原因,需要将第一个元素放在前面。

4. 文字动画。

这与网格动画无关,但我模仿了原作。

预览

4

完整代码

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

描述

我只想使用:hover伪类,但我添加了active属性,因为它无法与光标状态同步。

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

和变化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. 车削和精加工。

这里是修改后的版本(与第一个演示版相同)

5

完整代码:https://stackblitz.com/edit/react-ts-animation-grid-final

结论

我找不到使用纯 CSS 实现 CSS Grid 动画的方法。
另一方面,能够使用网格进行坐标计算似乎很有用。

我创建了另一个类似《洛克人》关卡选择界面的模式。https
://amination-grid-menu.netlify.com/

使用 CSS Grid 实现快乐动画!

文章来源:https://dev.to/terrierscript/create-animating-element-on-css-grid-with-react-hooks-50g9