코딩/Today I Learn

React animaiton #framer-motion

코딩쪼렙 2022. 9. 26. 14:47
728x90

stylecomponent 자동채우기

https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components

 

import styled from "styled-components";

import { motion } from "framer-motion";
const Wrapper styled(motion.div)`
    height: 100vh;
    width: 100vw;
    display: flex;
    align-items: center;
    justify-content: center;
`;
const Box = styled(motion.div)`
    width: 200px;
    height: 200px;
    background-color: white;
    border-radius: 15px;
    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;

function App() {
    return (
        <Wrapper>
            <Box
                transition={{ type: "spring", damping: 20, delay: 1 }} //spring is a default effect
                initial={{ scale: 0 }}
                animate={{ scale: 1, rotate: 180 }}
            />
        </Wrapper>
    );
}

export default App;

 

https://www.framer.com/docs/transition/

 

Transition | Framer for Developers

A transition defines how values animate from one state to another.

www.framer.com

 

Variants 무대 

설정을 분리된 변수로 옮기기 

const myVars = {
    start: { scale: 0 },
    end: { scale: 1, rotate: 180, transition = { type: "spring", delay: 0.5 } },
};
function App() {
    return (
        <Wrapper>
            <Box variants={myVarsinitial="start" animate="end" />
        </Wrapper>
    );
}

 

 

◆부모의 컴포넌트에 적용된 initial ,animate는 자식에도 그대로 적용된다.(motion에서 기본적으로 적용)

<Box variants={myVars} initial="start" animate="end" >

   <Circle/ 그대로 적용된다>

   <Circle/ 그대로 적용된다>

</Box>

 

◆Orchestration
delayChildren: 딜레이 시간(초) 후에 하위 애니메이션이 시작됩니다.
staggerChildren: 하위 컴포넌트의 애니메이션에 지속 시간(초)만큼 시차를 둘 수 있습니다. 예를 들어, staggerChildren이 0.01이면 첫 번째 자식은 0초, 두 번째 자식은 0.01초, 세 번째 자식은 0.02초 지연되는 식입니다. 계산된 stagger 딜레이가 delayChildren에 추가됩니다.
https://www.framer.com/docs/transition/#orchestration

inherit: boolean
부모로부터 variant 변경 사항을 상속하지 않도록 하려면 false로 설정합니다.

custom: any
각 애니메이션 컴포넌트에 대해 dynamic variants을 다르게 사용할 사용자 지정 데이터입니다.
```
const variants = {
visible: (custom) => ({
opacity: 1,
transition: { delay: custom * 0.2 }
})
}

< motion.div inherit={false} custom={0} animate="visible" variants={variants} / >
< motion.div custom={1} animate="visible" variants={variants} / >
< motion.div custom={2} animate="visible" variants={variants} / >
```
https://www.framer.com/docs/component/###inherit

place-items (Container Properties)
justify-items과 align-items를 합친 축약형

place-self (Item Properties)
justify-self와 align-self를 합친 축약형

 

const Circle = styled(motion.div)`
    background: white;
    height: 70px;
    width: 70px;
    place-self: center;
    border-radius: 35px;
    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;

wlsdnr12910 months ago
You can apply types in this way (auto complete available)
import { motion, Variants } from "framer-motion";
const variants: Variants = {
start: {},
end: {}
}

◆listening 이벤트 while~

function App() {
    return (
        <Wrapper>
            <Box
                whileHover={{ scale: 1.5, rotate: 90 }}
                whileTap={{ borderRadius: "100px" }}
            />
        </Wrapper>
    );
}

 

---->변수를 만들어서 정리

const boxVariants = {
    hover: { scale: 1.5, rotate: 90 },
    click: { borderRadius: "100px" },
};
function App() {
    return (
        <Wrapper>
            <Box variants={boxVariants} whileHover="hover" whileTap="click" />
        </Wrapper>
    );
}

 

function App() {
    return (
        <Wrapper>
            <Box
                drag
                variants={boxVariants}
                whileDrag={{ backgroundColor: "red" }} //"red"는 string이기때문에 걸어놨던 animation이 작동하지 않는다.rgba나 rgb로 작성해야함.
                whileHover="hover"
                whileTap="click"
            />
        </Wrapper>
    );
}

---->정리

const boxVariants = {
    hover: { scale: 1.5, rotate: 90 },
    click: { borderRadius: "100px" },
    drag: { backgroundColor: "rgb(52, 152, 219)", transition: { duration: 3 } },
};
function App() {
    return (
        <Wrapper>
            <Box
                drag //drag를 입력하기만해도 끌어당겨짐
                variants={boxVariants}
                whileDrag="drag"
                whileHover="hover"
                whileTap="click"
            />
        </Wrapper>
    );
}

 

Hover
hover 제스처는 포인터가 컴포넌트 위로 이동하거나 컴포넌트를 떠날 때를 감지합니다. onMouseEnter 및 onMouseLeave와는 달리 실제 마우스 이벤트의 결과로만 호버가 실행되도록 보장됩니다.

whileHover: VariantLabels | TargetAndTransition
호버 제스처가 인식되는 동안 애니메이션할 속성 또는 변형 레이블입니다.
< motion.div whileHover={{ scale: 0.8 }} / >
https://www.framer.com/docs/gestures/#hover

Tap
whileTap: VariantLabels | TargetAndTransition
컴포넌트를 누르고 있는 동안 애니메이션할 속성 또는 변형 레이블입니다.
< motion.div whileTap={{ scale: 0.8 }} / >
https://www.framer.com/docs/gestures/#tap

Drag
drag: boolean | "x" | "y"
이 요소에 대해 끌기를 활성화합니다. 기본적으로 false로 설정됩니다. 양방향으로 드래그하려면 true로 설정하십시오. 특정 방향으로만 드래그하려면 "x" 또는 "y"를 설정합니다.
< motion.div drag="x" / >

whileDrag: VariantLabels | TargetAndTransition
드래그 제스처가 인식되는 동안 애니메이션할 속성 또는 변형 레이블입니다.
< motion.div whileDrag={{ scale: 1.2 }} / >
https://www.framer.com/docs/gestures/#drag

rgb(46, 204, 113)

 

제약:

drag:"x"라고 작성하면 x로만 이동, y도 마찬가지

boxConstraints --> 드래그를 가두는 박스를 만들 수 있다.범위를 넘어가면 다시 돌아옴

 

수동으로

function App() {
    return (
        <Wrapper>
            <BiggerBox >
                <Box
                    drag
                    dragConstraints={{
                        top: -200,
                        bottom: 200,
                        left: -200,
                        right: 200,
                    }}
                    variants={boxVariants}
                    whileDrag="drag"
                    whileHover="hover"
                    whileTap="click"
                />
            </BiggerBox>
        </Wrapper>
    );

 

자동으로 ref

function App() {
    const biggerBoxRef = useRef<HTMLDivElement>(null);
    return (
        <Wrapper>
            <BiggerBox ref={{ biggerBoxRef }}>
                <Box
                    drag
                    dragConstraints={biggerBoxRef} //biggerBox 범위를 넘지않게
                    variants={boxVariants}
                    whileDrag="drag"
                    whileHover="hover"
                    whileTap="click"
                />
            </BiggerBox>
        </Wrapper>
    );
}

 

멀리가면 중간으로 돌아가는거

  <Box

                    drag
                    dragSnapToOrigin 
                    dragElastic={0.5}
                    dragConstraints={biggerBoxRef}
                    variants={boxVariants}
                    whileDrag="drag"
                    whileHover="hover"
                    whileTap="click"
                />

 

dragConstraints
허용된 드래그 가능 영역에 제약 조건을 적용합니다.
dragConstraints 에는 드래그 가능한 컴포넌트의 가장자리 거리를 정의합니다. (드래그 가능한 영역에 가장자리에서 얼마만큼까지 허용할 것인지 지정)
```
// 픽셀 이용
< motion.div drag="x" dragConstraints={{ left: 0, right: 300 }}/ >

// ref이용
const MyComponent = () => {
const constraintsRef = useRef(null)

return (
< motion.div ref={constraintsRef}>
< motion.div drag dragConstraints={constraintsRef} />
< /motion.div>
)
}
```

dragSnapToOrigin: boolean
true인 경우 드래그 가능한 요소는 드래그를 놓을 때, 원점으로 다시 애니메이션됩니다.
dragSnapToOrigin={true}

dragElastic: DragElastic
외부 제약 조건에서 허용되는 이동 정도. 0 = 움직임 없음, 1 = 전체 움직임. 기본적으로 0.5로 설정됩니다. 움직임을 비활성화하기 위해 false로 설정할 수도 있습니다.
dragElastic={0.2}

 

motionValue 

 

MotionValue

MotionValues는 애니메이션 값의 상태(state)와 속도(velocity)를 추적합니다. 모든 모션 컴포넌트는 내부적으로 MotionValues를 사용하여 애니메이션 값의 상태와 속도를 추적합니다. 일반적으로 이들은 자동으로 생성됩니다. (MotionValue는 React State가 아니기 때문에 Motion Value값이 바뀌어도 리랜더링이 일어나지 않는다.)

React Rendering Cycle을 발동시키지않아

style에서만변경됨

재랜더링 하지않기 때문에 console.log 딱 한번만 발ㄹ동됨 


```
import { motion, useMotionValue } from "framer-motion"

export function MyComponent() {
const x = useMotionValue(0)
return < motion.div style={{ x }} />
}
```
const x = useMotionValue(0)
useMotionValue 후크로 MotionValues를 생성할 수 있습니다. useMotionValue에 전달된 값은 MotionValue의 초기 상태로 작동합니다.

 

motionvalue 기능임 , react x
x.set(100)
set 메서드로 업데이트할 수 있습니다.
이것은 React 리렌더링을 트리거하지 않습니다.

x.get() // 100
MotionValue는 문자열이나 숫자가 될 수 있습니다.
get 메소드로 값을 읽을 수 있습니다.
https://www.framer.com/docs/motionvalue/

 
ㅑㅕㅕㅕㅕㅕㅕ

 

function App() {
    const x = useMotionValue(0);
    useEffect(() => {
        x.onChange(() => console.log(x.get()));
    }, [x]);
    return (
        <Wrapper>
            <button onClick={() => x.set(200)}>clickme</button>
            <Box style={{ x }} drag="x" dragSnapToOrigin />
        </Wrapper>
    );
}

useTransform



useTransform 훅을 통해 MotionValues를 연결합니다.
useTransform()는 한 값 범위에서 다른 값 범위로 매핑하여 다른 MotionValue의 output을 변환하는 MotionValue를 만듭니다.
x(Motion Value)값을 다른 output값으로 변환해준다.
ex) x: -400 => 1
```
const x = useMotionValue(0)
const input = [-200, 0, 200]
const output = [0, 1, 0]
const opacity = useTransform(x, input, output)

return < motion.div drag="x" style={{ x, opacity }} />
```
https://www.framer.com/docs/motionvalue/##usetransform

function App() {
    const x = useMotionValue(0);
    const potato = useTransform(x, [-800, 0, 800], [2, 1, 0.1]);
    useEffect(() => {
        potato.onChange(() => console.log(potato.get())); ///이전과 달리 2,1,0.1 사이값으로 나옴
    }, [x]);
    return (
        <Wrapper>
            <button onClick={() => x.set(200)}>clickme</button>
            <Box style={{ x, scale: potato }} drag="x" dragSnapToOrigin />
        </Wrapper>
    );
}

 

useViewPortScroll()

 

useViewportScroll(): ScrollMotionValues
뷰포트가 스크롤될 때 업데이트되는 MotionValues를 리턴합니다.
아래 값들은 모두 MotionValue< number >를 넘겨줍니다.
scrollX: 실제 수평 스크롤 픽셀 ex) 500px
scrollY: 실제 수직 스크롤 픽셀 ex) 500px
scrollXProgress : 0 ~ 1 사이의 수평 스크롤
scrollYProgress : 0 ~ 1 사이의 수직 스크롤(가장 상단 0, 가장 하단 1)
```
export const MyComponent = () => {
const { scrollYProgress } = useViewportScroll()
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

 

useViewportScroll -> useScroll 로 이름 변경 되었습니다.

 

function App() {
    const x = useMotionValue(0);
    const rotate = useTransform(x, [-800, 800], [-360, 360]);
    useEffect(() => {
        rotate.onChange(() => console.log(rotate.get()));
    }, [x]);
    const gradient = useTransform(
        x,
        [-800, 0, 800],
        [
            "linear-gradient(135deg,rgb(106, 137, 204),rgb(130, 204, 221))",
            "linear-gradient(135deg,rgb(246, 185, 59),rgb(96, 163, 188))",
            "linear-gradient (135deg, rgb(238, 0, 153), rgba(221, 0, 28))",
        ]
    );
    const { scrollYProgress } = useViewportScroll();
    const scale = useTransform(scrollYProgress, [0, 1], [0, 5]);
    return (
        <Wrapper style={{ background: gradient }}>
            <Box style={{ x, rotate, scale }} drag="x" dragSnapToOrigin />
        </Wrapper>
    );
}

 

 

sgv animation

 

특정 속성따로 transition 하는법

const Svg = styled.svg`
    width: 300px;
    height: 300px;
    path {    
        stroke: white;
        stroke-width: 5;
    }
`;

const svg = {
    start: { fill: "rgba(255,255,255,0)", pathLength: 0 },
    end: {
        fill: "rgba(255,255,255,1)",
        pathLength: "1",
    },
};
function App() {
    return (
        <Wrapper>
            <Svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
                <motion.path
                    Variants={svg}
                    initial="start"
                    animate="end"
                    transition={{
                        default: { duration: 5 },
                        fill: { duration: 2, delay: 3 },
                    }}
                    fill="transparent"
                    d="M224 373.12c-25.24-31.67-40.08-59.43-45-83.18-22.55-88 112.61-88 90.06 0-5.45 24.25-20.29 52-45 83.18zm138.15 73.23c-42.06 18.31-83.67-10.88-119.3-50.47 103.9-130.07 46.11-200-18.85-200-54.92 0-85.16 46.51-73.28 100.5 6.93 29.19 25.23 62.39 54.43 99.5-32.53 36.05-60.55 52.69-85.15 54.92-50 7.43-89.11-41.06-71.3-91.09 15.1-39.16 111.72-231.18 115.87-241.56 15.75-30.07 25.56-57.4 59.38-57.4 32.34 0 43.4 25.94 60.37 59.87 36 70.62 89.35 177.48 114.84 239.09 13.17 33.07-1.37 71.29-37.01 86.64zm47-136.12C280.27 35.93 273.13 32 224 32c-45.52 0-64.87 31.67-84.66 72.79C33.18 317.1 22.89 347.19 22 349.81-3.22 419.14 48.74 480 111.63 480c21.71 0 60.61-6.06 112.37-62.4 58.68 63.78 101.26 62.4 112.37 62.4 62.89.05 114.85-60.86 89.61-130.19.02-3.89-16.82-38.9-16.82-39.58zwesome - https://fontawes"
                />
            </Svg>
        </Wrapper>
    );
}

 

Fontawesome Airbnb Logo
< />모양 클릭해서 svg복사 후 사용하시면 됩니다.
https://fontawesome.com/v5.15/icons/airbnb?style=brands

Line drawing
svg 엘리먼트에 'pathLength', 'pathSpacing', 'pathOffset' 속성을 사용하여 Line drawing 애니메이션을 만들 수 있습니다.
https://www.framer.com/docs/examples/#line-drawing

path (SVG)
path SVG 엘리먼트는 모양을 정의하는 일반 엘리먼트입니다.모든 기본 모양은 path 엘리먼트로 만들 수 있습니다.
path의 속성 d는 경로의 모양을 정의합니다.
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path

Path
motion.path 컴포넌트는 세 가지 강력한 SVG path 속성인 pathLength, pathSpacing 및 pathOffset을 가지고 있습니다. 수동 경로 측정이 필요 없이 모두 0과 1 사이의 값으로 설정됩니다.

Line drawing
선 그리기 애니메이션은 pathLength, pathSpacing 및 pathOffset의 세 가지 특수 속성을 사용하여 많은 SVG 요소로 만들 수 있습니다.
ex) motion.circle initial={{ pathLength: 0 }} animate={{ pathLength: 1 }}
https://www.framer.com/docs/examples/#line-drawing

 

 

AnimatePresence

이 전에는 기초적인 나타나는 애니메이션까지만 가능했고 없어질 때 안됐음.

딱한가지규칙 visible 상태이여야함.

내부에 조건문이 있어야함

내부에 보여지거나 사라지는게있으면 animate하게 도와줌. 


AnimatePresence를 사용하면 React 트리에서 컴포넌트가 제거될 때 제거되는 컴포넌트에 애니메이션 효과를 줄 수 있습니다. React에는 다음과 같은 수명 주기 메서드가 없기 때문에 종료 애니메이션을 활성화해야 합니다.

exit
이 컴포넌트가 트리에서 제거될 때 애니메이션할 대상입니다.
```
import { motion, AnimatePresence } from "framer-motion"

export const MyComponent = ({ isVisible }) => (
< AnimatePresence>
{isVisible && (
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
< /AnimatePresence>
)
```
https://www.framer.com/docs/animate-presence/

 

const boxVariants = {
    inital: {
        opacity: 0,
        scale: 0,
    },
    visible: {
        opcity: 1,
        scale: 1,
        rotate: 360,
    },
    leaving: {
        opcity: 0,
        scale: 0,
        y: 50,
    },
};
function App() {
    const [showing, setShowing] = useState(false);
    const toggleShowing = () => setShowing((prev) => !prev);
    return (
        <Wrapper>
            <button onClick={toggleShowing}>click</button>
            <AnimatePresence>
                {showing ? (
                    <Box
                        variants={boxVariants}
                        initial="initial"
                        animate="visible"
                        exit="leaving"
                    />
                ) : null}
            </AnimatePresence>
        </Wrapper>
    );
}

 

 

 


AnimatePresence의 단일 자식 key를 변경하여 슬라이드쇼(슬라이더)와 같은 컴포넌트를 쉽게 만들 수 있습니다.
```
export const Slideshow = ({ image }) => (
< AnimatePresence>
key={image.src}
src={image.src}
initial={{ x: 300, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -300, opacity: 0 }}
/>
< /AnimatePresence>
)
```
https://www.framer.com/docs/animate-presence/##unmount-animations

Slider 예시 코드
https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?from-embed

 

const box = {
    invisible: {
        opacity: 0,
        x: 500,
        scale: 0,
    },
    visible: {
        opacity: 1,
        x: 0,
        scale: 1,
        transition: {
            duration: 1,
        },
    },
    exit: {
        opacity: 0,
        x: -500,
        scale: 0,
        transition: {
            duration: 1,
        },
    },
};
function App() {
    const [visible, setVisible] = useState(1);
    const prev = () => setVisible((prev) => (prev === 1 ? 1 : prev - 1));
    const next = () => setVisible((prev) => (prev === 10 ? 10 : prev + 1));
    return (
        <Wrapper>
            <AnimatePresence>
                {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) =>
                    i === visible ? (
                        <Box
                            variants={box}
                            initial="invisible"
                            animate="visible"
                            exit="exit"
                            key={i}
                        >
                            {i}
                        </Box>
                    ) : null
                )}
            </AnimatePresence>
            <button onClick={prev}>prev</button>
            <button onClick={next}>next</button>
        </Wrapper>
    );
}

 

custom

각 애니메이션 컴포넌트에 대해 동적 variants를 다르게 적용할 때 사용할 수 있는 사용자 지정 데이터입니다.
```
const variants = {
visible: (custom) => ({ prop으로 전달했기 때문에 이름 상관없음 , potato도 가능 
opacity: 1,
transition: { delay: custom * 0.2 }
})
}

< motion.div custom={0} animate="visible" variants={variants} /> //custom prop
< motion.div custom={1} animate="visible" variants={variants} />
< motion.div custom={2} animate="visible" variants={variants} />
```
https://www.framer.com/docs/component/###custom

exitBeforeEnter
true로 설정하면 AnimatePresence는 한 번에 하나의 컴포넌트만 랜더링합니다. exiting중인 컴포넌트는 entering하는 컴포넌트가 렌더링되기 전에 exit 애니메이션을 완료합니다.
```
< AnimatePresence exitBeforeEnter>
< motion.div key={currentItem} exit={{ opacity: 0 }} />
< /AnimatePresence>
```
https://www.framer.com/docs/animate-presence/###exitbeforeenter

 

 

const box = {
    entry: (backPotato: boolean) => ({ //custom이라는 prop에 저장되어서 이름 상관없음, 오브젝트형식아니고 함수형식
        opacity: 0,
        x: backPotato ? -500 : 500,
        scale: 0,
    }),
    center: {
        opacity: 1,
        x: 0,
        scale: 1,
        transition: {
            duration: 1,
        },
    },
    exit: (backPotato: boolean) => ({
        opacity: 0,
        x: backPotato ? 500 : -500,
        scale: 0,
        transition: {
            duration: 1,
        },
    }),
};
function App() {
    const [visible, setVisible] = useState(1);
    const [back, setBack] = useState(false);
    const prev = () => {
        setBack(true);
        setVisible((prev) => (prev === 1 ? 1 : prev - 1));
    };
    const next = () => {
        setBack(false);
        setVisible((prev) => (prev === 10 ? 10 : prev + 1));
    };

    return (
        <Wrapper>
            <AnimatePresence mode="wait" custom={back}> //규칙에 따라 custom을 AnimatePresence 안에도 넣어야함
                <Box
                    custom={back}
                    variants={box}
                    initial="entry"
                    animate="center"
                    exit="exit"
                    key={visible}
                >
                    {visible}
                </Box>
            </AnimatePresence>
            <button onClick={prev}>prev</button>
            <button onClick={next}>next</button>
        </Wrapper>
    );
}

 

 

exitBeforeEnter은 더이상 지원하지 않습니다. mode="wait"를 작성해주면되겠습니다.