justify-content와 justify-items차이점
justify-content: Grid 아이템들의 너비를 모두 합한 값이 Grid 컨테이너의 너비보다 작을 때 Grid 아이템들을 통째로 정렬합니다.
justify-items: 아이템들을 가로(row축) 방향으로 정렬합니다. 컨테이너에 적용합니다.
place-items
align-items와 justify-items를 같이 쓸 수 있는 단축 속성이에요.
align-items, justify-items의 순서로 작성하고, 하나의 값만 쓰면 두 속성 모두에 적용됩니다.
place-content
align-content와 justify-content를 같이 쓸 수 있는 단축 속성이에요.
align-content, justify-content의 순서로 작성하고, 하나의 값만 쓰면 두 속성 모두에 적용됩니다.
CSS Grid 설명 참고사이트
https://studiomeal.com/archives/533
Layout 애니메이션 다양한 사용 예시들
https://www.framer.com/docs/animate-shared-layout/#syncing-layout-animations
AnimateSharedLayout | Framer for Developers
Animate layout changes across, and between, multiple components.
www.framer.com
import styled from "styled-components";
import { useState } from "react";
import { motion, AnimatePresence, Variants } from "framer-motion";
const Wrapper = styled(motion.div)`
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient (135deg, rgb(238, 0, 153), rgba(221, 0, 28));
`;
const Box = styled(motion.div)`
height: 400px;
background-color: rgba(255, 255, 255, 1);
border-radius: 40px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const Grid = styled.div`
width: 50vw;
gap: 10px;
display: grid;
grid-template-columns: repeat(3, 1fr); //총6칸
div:first-child,
div:last-child {
grid-column: span 2;
}
`;
const Overlay = styled(motion.div)` //애니메이션효과주려면
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
display: flex;
justify-content: center;
align-items: center;
`;
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClicked}>
<Grid>
<Box layoutId="hello" /> //박스네개
<Box />
<Box />
<Box />
</Grid>
<AnimatePresence>
{clicked ? (
<Overlay
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Box
layoutId="hello"
style={{ width: 400, height: 400 }}
/>
</Overlay>
) : null}
</AnimatePresence>
</Wrapper>
);
}
export default App;
function App() {
const [id, setId] = useState<null | string>(null);
console.log(id);
return (
<Wrapper>
<Grid>
{["1", "2", "3", "4"].map((n) => (
<Box onClick={() => setId(n)} key={n} layoutId={n} /> //바로 setId()하면 누르지않고 바로 시작하게되니 조심
))}
</Grid>
<AnimatePresence>
{id ? (
<Overlay
onClick={() => setId(null)}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Box
layoutId={id}
style={{ width: 400, height: 400 }}
/>
</Overlay>
) : null}
</AnimatePresence>
</Wrapper>
);
}
export default App;
absolute 에서 정중앙에 위치 시킬 때 트릭
left:0, right:0, margin: 0 auto;
우리는 같은 도메인일 때 <a></a> 를 사용하면 안된다. 새로고침하기 때문에 <Link>사용해야함
React Router 6버전에서는 useRoute()가 useMatch()로 대체되었습니다.
Framer Motion repeat
https://www.framer.com/docs/transition/###repeat
whileHover
호버 제스처가 인식되는 동안 애니메이션할 속성 또는 variant label입니다.
ex) < motion.div whileHover={{ scale: 1.2 }} / >
https://www.framer.com/docs/gestures/###whilehover
Gestures | Framer for Developers
A powerful gesture recognition system for the browser.
www.framer.com
*react-router-dom v5 vs v6**
1. Link에서 to는 상대경로로 적으시면 됩니다
ex. '/tv' -> 'tv'
2. exact가 사라졌습니다
대신 알아서 최적의 경로를 react-router-dom이 판단하여 매칭해줍니다
3. useRouteMatch가 useMatch로 변경되었습니다
이 또한 상대경로로 작성하는 것으로 변경되었습니다
ex. useRouteMatch('/tv') -> useMatch('tv')
https://reactrouter.com/docs/en/v6/upgrading/v5#upgrade-to-react-router-v6
transform-origin
transform-origin CSS 속성은 엘리먼트 transformation의 원점을 설정합니다.
```
transform-origin: center;
transform-origin: top left;
transform-origin: bottom right 60px;
```
https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin
transform-origin - CSS: Cascading Style Sheets | MDN
The transform-origin CSS property sets the origin for an element's transformations.
developer.mozilla.org
컴포넌트 프롭이아닌 애니메이션 코드로 실행시키기
애니메이션 20개를 동시에 작동시켜야한다면 코드로 실행하는 것이 더 나을 것 같은내용
코드로 실행시키기
mport { motion, useAnimation } from "framer-motion"
function Header() {
const [searchOpen, setSerachOpen] = useState(false);
const inputAnimation = useAnimation();
const toggleSearch = () => {
if (searchOpen) {
inputAnimation.start({
scaleX: 0,
});
} else {
inputAnimation.start({ scaleX: 1 });
}
setSerachOpen((prev) => !prev);
};
return (
<Nav>
<Col>
...
</Col>
<Col>
<Search onClick={toggleSearch}>
<motion.svg
animate={{ x: searchOpen ? -200 : 0 }}
transition={{ type: "linear" }}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clipRule="evenodd"
></path>
</motion.svg>
<Input
transition={{ type: "linear" }}
animate={inputAnimation}
placeholder="serach for movie"
/>
</Search>
</Col>
</Nav>
);
}
useViewportScroll() 스크롤 움직일 때 밑에서부터 얼마나 멀리 떨어져있는지 알려줌
첫번 째 값은 progress
두가지 값을 얻는데 x,y에대한 스크롤 진행도를 0에서부터 1 사이값으로 알 수 있음.
얼마나 멀리 이동했는지 픽셀단위로 나타내는 것 .
----> useScroll
코드로 실행시키는 방법을 알려주기위해 다음같이 만든 것.
애니메이트 시킬게 배경화면만 있지 않으니
실제로 사용할 때는 prop animate로 animate={{background: scrollY >80? :}} 이렇게 사용하는게 좋다!
useAnimation()
useAnimation 훅을사용하여 시작 및 중지 메서드가 있는 AnimationControls을 만들 수 있습니다.
```
const MyComponent = () => {
const controls = useAnimation()
return < motion.div animate={controls} />
}
// 애니메이션은 controls.start 메소드로 시작할 수 있습니다.
controls.start({ x: "100%", transition: { duration: 3 }})
```
https://www.framer.com/docs/animation/#component-animation-controls
useScroll(): ScrollMotionValues
viewport가 스크롤될 때 업데이트되는 MotionValues를 반환합니다.
주의! body 또는 html을 height: 100% 또는 이와 유사한 것으로 설정하면 페이지 길이를 정확하게 측정하는 브라우저의 기능이 손상되므로 Progress 값이 손상됩니다.
```
export const MyComponent = () => {
const { scrollYProgress } = useScroll()
return < motion.div style={{ scaleX: scrollYProgress }} />
}
```
https://www.framer.com/docs/motionvalue/###useviewportscroll
Motion values overview | Framer for Developers
Motion values track the state and velocity of animating values.
www.framer.com
import { motion, useAnimation, useScroll } from "framer-motion";
import { Link, useMatch } from "react-router-dom";
import { useEffect, useState } from "react";
const Nav = styled(motion.nav)`
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
width: 100%;
top: 0;
height: 80px;
font-size: 14px;
padding: 20px 60px;
`;
const Col = styled.div`
display: flex;
align-items: center;
`;
const Logo = styled(motion.svg)`
margin-right: 50px;
width: 95px;
height: 25px;
fill: ${(props) => props.theme.red};
path {
stroke-width: 6px;
stroke: white;
}
`;
const Items = styled.ul`
display: flex;
align-items: center;
`;
const Item = styled.li`
margin-right: 20px;
color: ${(props) => props.theme.white.darker};
transition: color 0.3s ease-in-out;
position: relative;
display: flex;
justify-content: center;
flex-direction: column;
&:hover {
color: ${(props) => props.theme.white.lighter};
}
`;
const Search = styled.span`
color: white;
display: flex;
align-items: center;
position: relative;
svg {
height: 25px;
}
`;
const Circle = styled(motion.span)`
position: absolute;
width: 5px;
height: 5px;
border-radius: 50%;
bottom: -5px;
left: 0;
right: 0;
margin: 0 auto;
background-color: ${(props) => props.theme.red};
`;
const Input = styled(motion.input)`
transform-origin: right center;
position: absolute;
right: 0px;
padding: 5px 10px;
padding-left: 40px;
z-index: -1;
color: white;
font-size: 16px;
background-color: transparent;
border: 1px solid ${(props) => props.theme.white.lighter};
`;
const logoVariants = {
normal: {
fillOpacity: 1,
},
active: {
fillOpacity: [0, 1, 0],
transition: {
repeat: Infinity,
},
},
};
const navVariants = {
top: {
backgroundColor: "rgba(0,0,0,0)",
},
scroll: {
backgroundColor: "rgba(0,0,0,1)",
},
};
function Header() {
const [searchOpen, setSearchOpen] = useState(false);
const homeMatch = useMatch("/");
const tvMatch = useMatch("tv");
const inputAnimation = useAnimation();
const navAnimation = useAnimation();
const { scrollY } = useScroll();
const toggleSearch = () => {
if (searchOpen) {
inputAnimation.start({
scaleX: 0,
});
} else {
inputAnimation.start({ scaleX: 1 });
}
setSearchOpen((prev) => !prev);
};
useEffect(() => {
scrollY.onChange(() => {
if (scrollY.get() > 80) {
navAnimation.start("scroll");
} else {
navAnimation.start("top");
}
console.log(scrollY.get());
});
}, [scrollY]);
return (
<Nav variants={navVariants} animate={navAnimation} initial={"top"}>
<Col>
<Logo
variants={logoVariants}
whileHover="active"
initial="normal"
xmlns="http://www.w3.org/2000/svg"
width="1024"
height="276.742"
viewBox="0 0 1024 276.742"
>
<motion.path d="M140.803 258.904c-15.404 2.705-31.079 3.516-47.294 5.676l-49.458-144.856v151.073c-15.404 1.621-29.457 3.783-44.051 5.945v-276.742h41.08l56.212 157.021v-157.021h43.511v258.904zm85.131-157.558c16.757 0 42.431-.811 57.835-.811v43.24c-19.189 0-41.619 0-57.835.811v64.322c25.405-1.621 50.809-3.785 76.482-4.596v41.617l-119.724 9.461v-255.39h119.724v43.241h-76.482v58.105zm237.284-58.104h-44.862v198.908c-14.594 0-29.188 0-43.239.539v-199.447h-44.862v-43.242h132.965l-.002 43.242zm70.266 55.132h59.187v43.24h-59.187v98.104h-42.433v-239.718h120.808v43.241h-78.375v55.133zm148.641 103.507c24.594.539 49.456 2.434 73.51 3.783v42.701c-38.646-2.434-77.293-4.863-116.75-5.676v-242.689h43.24v201.881zm109.994 49.457c13.783.812 28.377 1.623 42.43 3.242v-254.58h-42.43v251.338zm231.881-251.338l-54.863 131.615 54.863 145.127c-16.217-2.162-32.432-5.135-48.648-7.838l-31.078-79.994-31.617 73.51c-15.678-2.705-30.812-3.516-46.484-5.678l55.672-126.75-50.269-129.992h46.482l28.377 72.699 30.27-72.699h47.295z" />
</Logo>
<Items>
<Item>
<Link to="/">
Home
{homeMatch && <Circle layoutId="circle" />}
</Link>
</Item>
<Item>
<Link to="tv">
Tv Shows
{tvMatch && <Circle layoutId="circle" />}
</Link>
</Item>
</Items>
</Col>
<Col>
<Search onClick={toggleSearch}>
<motion.svg
animate={{ x: searchOpen ? -200 : 0 }}
transition={{ type: "linear" }}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clipRule="evenodd"
></path>
</motion.svg>
<Input
initial={{ scaleX: 0 }}
transition={{ type: "linear" }}
animate={inputAnimation}
placeholder="serach for movie"
/>
</Search>
</Col>
</Nav>
);
}
export default Header;
'코딩 > Today I Learn' 카테고리의 다른 글
#3 NETFLIX SLIDER (0) | 2022.10.25 |
---|---|
#2 NETFLIX HOME SCREEN (0) | 2022.10.12 |
React animaiton #framer-motion (0) | 2022.09.26 |
React Js Master #10 Ref,CrossBoardMovement (0) | 2022.08.03 |
React Js Master #9 selector set, beautiful-dnd (0) | 2022.07.11 |