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
Making Step
1. Create base grid
First, I create bare gird.
Preview
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>
)
}
2. Create element for calcurate.
Next, append cell for calcurate.
Preview
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>
))}
// ...
}
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>
)
}
3. Append animating cursor.
Next, append hover cursor.
Preview
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])
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} />}
<Cursor>
needs first element on Container
because of z-index.
4. Text animation.
This is not related grid animation, but I follow original.
Preview
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>
))}
)
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>
)
}
5. Turning & Finish.
Something turned version is here ( same as first demo)
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)
Hello,
You are using a lot of stuctures coming from nowhere.
This is actually not understandable from my point of view.
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 👻
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.
Of cource, this is one of the trivial solution!
It's main point is animating css grid.