- Phân tích
- Dựng component thô chưa có animate
- Animate với Flip technique
- Chăm chúc
- Background animation
Phân tích
See the Pen React Stripe Menu by Alex (@aholachek) on CodePen.
Trước tiên phân tích cái animation này ra thành nhiều phần để dể hình dung. Có thể xem dạng slow motion trên Codepen để xem nhé.
- container dropdown màu trắng sẽ thay đổi kích thước và vị trí
- background màu xám ở phía dưới của dropdown transition độ cao
- Khi di chuyển chuột khỏi dropdown, fade out nội dung trước đó đi và chuyển vị trí nó sang element mới, sau đó đưa nội dung mới lên.
Một vài lưu ý khi làm animation với React, cứ để browser quản lý layout. Thay vì sử dụng các dropdown khác nhau, chúng ta dùng 1 dropdown và thay đổi vị trí của nó, dùng Flip technique để giả lập như có 3 dropdown khác nhau.
Dựng component thô chưa có animate
See the Pen React Stripe Menu Before Animation by Alex (@aholachek) on CodePen.
Để bắt đầu ta dựng một component navbar
sử dụng styled-component
Ở đây thì chưa thêm phần gray background ở dưới dropdown, cái này nó sẽ nằm absolute
Animate với Flip technique
Chúng ta sẽ sử dụng react-flip-toolkit để thay đổi kích thước và vị trí dropdown.
<Flipper flipKey={currentIndex}>
<Navbar>
{navbar.Config.map((n, index) => {
// render navbar items
})}
</Navbar>
</Flipper>
Ở DropdownContainer
component, đưa các element sẽ animate vào bên trong Flipped
component, nhớ giá trị flipId
phải khác nhau
<DropdownRoot>
<Flipped flipId='dropdown-caret'>
<Caret />
</Flipped>
<Flipped flipId='dropdown'>
<DropdownBackground>
{children}
</DropdownBackground>
</Flipped>
</DropdownRoot>
Làm 2 cái animate trên <Caret/>
và <DropdownBackground/>
riêng biệt, để thuộc tính overflow: hidden
set trên <DropdownBackground />
không ảnh hưởng tới <Caret/>
Giờ có 1 vấn đề nhỏ là nội dung bên trong dropdown lúc xuất hiện bị stretch một cách kỳ cục, lý do là thuộc tính transforms: scale
nó áp luôn trên children. Xử lý cái này bằng cách đưa nội dung này vào trong <Flipped/>
kèm giá trị cho props inverseFlipId
, lúc này children sẽ không bị effect từ thằng cha nữa, đồng thời xác định luôn là ko muốn áp thằng scale
See the Pen React Stripe Menu -- Error #1: no scale adjustment by Alex (@aholachek) on CodePen.
<DropdownRoot>
<Flipped flipId="dropdown-caret">
<Caret />
</Flipped>
<Flipped flipId="dropdown">
<DropdownBackground>
<Flipped inverseFlipId="dropdown" scale>
{children}
</Flipped>
</DropdownBackground>
</Flipped>
</DropdownRoot>
Chăm chúc
Cũng gần được rồi, chúng ta cần để ý thêm vào những chi tiết nhỏ nhất để animate nhìn cool hơn
Styled-components hỗ trợ rất tốt việc thay đổi keyframe animation. Chúng ta sẽ sử dụng tính năng này để làm animation lúc enter và cross-fade nội dung dropdown khi thay đổi ví trí chuột.
const getFadeContainerKeyFrame = ({ animatingOut, direction }) => {
if (!direction) return;
return keyframes`
from {
transform: translateX(${
animatingOut ? 0 : direction === 'left' ? 20 : -20
}px);
}
to {
transform: translateX(${
!animatingOut ? 0 : direction === 'left' ? -20 : 20
}px);
opacity: ${animatingOut ? 0 : 1};
}
`;
};
const FadeContainer = styled.div`
animation-name: ${getFadeContainerKeyFrame};
animation-duration: ${props => props.duration * 0.5}ms;
animation-fill-mode: forwards;
position: ${props => (props.animatingOut ? "absolute" : "relative")};
opacity: ${props => (props.direction && !props.animatingOut ? 0 : 1)};
animation-timing-function: linear;
top: 0;
left: 0;
`
See the Pen React Stripe Menu -- Simple FLIP by Alex (@aholachek) on CodePen.
Khi user đưa chuột lên menu mới, chúng ta ko chỉ đưa vào dropdown hiện tại mà con dropdown trước đó như children cho DropdownContainer
, cùng với những thông tin về hướng di chuột của user. Sau đó DropdonwContainer
sẽ wrap hết tất cả children của nó trong một component mới, FadeContents
, thằng này sẽ sử dụng keyframe animation code ở trên để thêm animation tương ứng
Background animation
Cuối cùng chúng ta thêm gray background
const updateAltBackground = ({
altBackground,
prevDropdown,
currentDropdown
}) => {
const prevHeight = getFirstDropdownSectionHeight(prevDropdown)
const currentHeight = getFirstDropdownSectionHeight(currentDropdown)
// we'll use this function when we want a change
// to happen immediately, without CSS transitions
const immediateSetTranslateY = (el, translateY) => {
el.style.transform = `translateY(${translateY}px)`
el.style.transition = "transform 0s"
requestAnimationFrame(() => (el.style.transitionDuration = ""))
}
if (prevHeight) {
// transition the grey ("alt") background from its previous height
// to its current height
immediateSetTranslateY(altBackground, prevHeight)
requestAnimationFrame(() => {
altBackground.style.transform = `translateY(${currentHeight}px)`
})
} else {
// immediately set the background to the appropriate height
// since we don't have a stored value
immediateSetTranslateY(altBackground, currentHeight)
}
}
See the Pen React Stripe Menu -- Animated Background by Alex (@aholachek) on CodePen.
Initializing...