Framer X » Animation » Example animations » 30. Stack: Animation

30. Stack: Animation

The Frames inside the Stack resize. Note that the distribution of the Stack is adjusted so that one Frame can push the other Frame to respectively the left or right side.

Open Framer Motion version in CodeSandbox

Code Component

export function CC30StackAnimation() {
    const [align, setAlign] = useState<"start" | "end">("start")

    const variants = {
        normal: {
            width: 70,
        },
        expanded: {
            width: 150,
        },
    }

    const [animate, cycle] = useCycle("normal", "expanded")
    const [animate2, cycle2] = useCycle("normal", "expanded")

    return (
        <Stack
            // Visual & layout
            size={150}
            radius={30}
            center
            // Stack
            direction="horizontal"
            overflow="hidden"
            // Animation
            distribution={align}
        >
            <Frame
                // Visual & layout
                width={70}
                height={150}
                radius={10}
                backgroundColor="#fff"
                // Animation
                variants={variants}
                animate={animate}
                onTap={() => {
                    cycle()
                    setAlign("start")
                }}
            />
            <Frame
                // Visual & layout
                width={70}
                height={150}
                radius={10}
                backgroundColor="#fff"
                // Animation
                variants={variants}
                animate={animate2}
                onTap={() => {
                    cycle2()
                    setAlign("end")
                }}
            />
        </Stack>
    )
}

Framer Motion

The Framer Motion library doesn’t have Stacks, so here I used a regular Flexbox.

export function FM30FlexboxAnimation() {
    const [state, setState] = useState("both")

    const variants = {
        hidden: {
            width: 0,
        },
        normal: {
            width: 70,
        },
        expanded: {
            width: 150,
        },
    }

    return (
        <Center>
            <div
                style={{
                    width: 150,
                    height: 150,
                    borderRadius: 30,
                    overflow: "hidden",
                    display: "flex",
                    justifyContent: "space-between",
                    transform: "translateZ(0)",
                }}
            >
                <motion.div
                    style={{
                        width: 70,
                        height: 150,
                        borderRadius: 10,
                        backgroundColor: "#fff",
                        cursor: "pointer",
                    }}
                    variants={variants}
                    animate={
                        state === "left"
                            ? "expanded"
                            : state === "both"
                            ? "normal"
                            : "hidden"
                    }
                    onTap={() => {
                        if (state === "both") {
                            setState("left")
                        } else {
                            setState("both")
                        }
                    }}
                />
                <motion.div
                    style={{
                        width: 70,
                        height: 150,
                        borderRadius: 10,
                        backgroundColor: "#fff",
                        cursor: "pointer",
                    }}
                    variants={variants}
                    animate={
                        state === "right"
                            ? "expanded"
                            : state === "both"
                            ? "normal"
                            : "hidden"
                    }
                    onTap={() => {
                        if (state === "both") {
                            setState("right")
                        } else {
                            setState("both")
                        }
                    }}
                />
            </div>
        </Center>
    )
}

Dynamically changing the flexDirection (from row to row-reverse) didn’t make a difference, so the only option was to animate the width of both Divs simultaneously.

Override

const appState = Data({
    stackDistribution: "start",
})

export function Stack(): Override {
    return { distribution: appState.stackDistribution }
}

export function Left_frame(): Override {
    const [animate, cycle] = useCycle("normal", "expanded")

    return {
        variants: {
            normal: {
                width: 70,
            },
            expanded: {
                width: 150,
            },
        },
        animate: animate,
        onTap() {
            cycle()
            appState.stackDistribution = "start"
        },
    }
}

export function Right_frame(): Override {
    const [animate, cycle] = useCycle("normal", "expanded")

    return {
        variants: {
            normal: {
                width: 70,
            },
            expanded: {
                width: 150,
            },
        },
        animate: animate,
        onTap() {
            cycle()
            appState.stackDistribution = "end"
        },
    }
}

4 comments on “30. Stack: Animation”

  • Espen Staver says:

    I’ve encountered a bit of “strange” behaviour with stacks so far, and I wonder if you might know the answer. For example, if I just put a couple of frames in a stack on the canvas and apply a hover which animates height, the other frame(s) will move and the gap between the frames stay consistent. However, if I put a couple of code components in that same stack, this does not happen. They all stay in place while the hovered component changes height.

    Is this because of the ComponentContainer?

    • Tes Mat says:

      Probably, yes. Because when you add components to a stack in code they do resize.

      Try this:

      import * as React from "react"
      import { Frame, Stack } from "framer"
      
      export function A_component(props) {
          return <Frame {...props}>Hello</Frame>
      }
      
      export function Stacked(props) {
          return (
              <Stack {...props}>
                  <A_component whileHover={{ height: 100 }} />
                  <A_component whileHover={{ height: 100 }} />
                  <A_component whileHover={{ height: 100 }} />
              </Stack>
          )
      }

      (I added the whileHover directly instead of applying it with an override, but the result is the same.)

      • Espen Staver says:

        I see, thanks. What would be the best way to solve property controls for the component in your example?

        • Tes Mat says:

          You can’t use the property controls when using a component in code, of course, so you just pass those properties (here: text and tint) in the code.

          import * as React from "react"
          import { Frame, addPropertyControls, ControlType, Stack } from "framer"
          
          // The component
          export function A_component(props) {
              const { text, tint, ...rest } = props
          
              return (
                  <Frame {...rest} background={tint}>
                      {text}
                  </Frame>
              )
          }
          
          addPropertyControls(A_component, {
              text: {
                  title: "Text",
                  type: ControlType.String,
                  defaultValue: "Hello Framer!",
              },
              tint: {
                  title: "Tint",
                  type: ControlType.Color,
                  defaultValue: "#0099ff",
              },
          })
          
          // The component used in a Stack (could also be in another file)
          export function Stacked(props) {
              return (
                  <Stack {...props}>
                      <A_component
                          text="One"
                          tint="Orange"
                          whileHover={{ height: 100 }}
                      />
                      <A_component
                          text="Two"
                          tint="Salmon"
                          whileHover={{ height: 100 }}
                      />
                      <A_component text="Three" tint="Red" whileHover={{ height: 100 }} />
                  </Stack>
              )
          }

          Another option would be to add properties controls to the Stack, and then pass those properties to the components.

          (BTW, the component’s properties are now unpacked, just like in Framer’s boilerplate code.)

Leave a Reply