Tạo một animation cho component trong React
Sử dụng React, styled-components, react-flip-toolkit để tạo animation giống như mên trên trang chủ của Stripe
Phân tích
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.
Tìm việc React lương cao trong tháng up to 25M
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
Để 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
<DropdownRoot>
<Flipped flipId="dropdown-caret">
<Caret />
</Flipped>
<Flipped flipId="dropdown">
<DropdownBackground>
<Flipped inverseFlipId="dropdown" scale>
{children}
</Flipped>
</DropdownBackground>
</Flipped>
</DropdownRoot>
Chăm chút
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;
`
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)
}
}
Source: https://github.com/aholachek/react-stripe-menu
TopDev via Vuilaptrinh
Xem thêm các vị trí tuyển nhân viên it tại đây
- i iOS 18 có gì mới? Có nên cập nhật iOS 18 cho iPhone của bạn?
- G Gamma AI là gì? Cách tạo slide chuyên nghiệp chỉ trong vài phút
- P Power BI là gì? Vì sao doanh nghiệp nên sử dụng PBI?
- K KICC HCMC x TOPDEV – Bước đệm nâng tầm sự nghiệp cho nhân tài IT Việt Nam
- T Trello là gì? Cách sử dụng Trello để quản lý công việc
- T TOP 10 SỰ KIỆN CÔNG NGHỆ THƯỜNG NIÊN KHÔNG NÊN BỎ LỠ
- T Tìm hiểu Laptop AI – So sánh Laptop AI với Laptop thường
- M MySQL vs MS SQL Server: Phân biệt hai RDBMS phổ biến nhất
- S SearchGPT là gì? Công cụ tìm kiếm mới có thể đánh bại Google?
- C Cách tích hợp ChatGPT vào Google Search siêu dễ