Flick - Roact Animation Library

While working on some other projects that involved some UI, I decided to create an animation library for Roact. I’ve been implementing it while using it in another project. Now, I feel like it’s in a good enough state to share it with the world.

The source code is available on my gitlab: https://gitlab.com/seaofvoices/flick
You can also grab the Roblox model file on the release page: https://gitlab.com/seaofvoices/flick/-/releases

I have not written a documentation site yet but if you are giving it a try, feel free to ping me on discord if you have any questions.

I’ll end this post with some kind of example/API reference in you want to try it!

Happy developing :computer:

How It Works

Provider

Flick works using a Roact context, so you need to wrap the top level component with the engine provider.

local function App(props)
    return Roact.createElement(Flick.Provider, {
        value = Flick.Engine.new(),
    }, {
        ChildComponent = Roact.createElement(SomeComponent),
    })
end

withAnimation

Animate your components by using Flick.withAnimation. This will pass a new prop to your component named flick, that let’s you trigger animations.

Pass the different values you want to animate in the initialValues table. This table must map strings to a value that flick is able to animate. Right now, flick supports these types:

  • number
  • Color3
  • Vector2
  • Vector3
  • UDim
  • UDim2

Example

An animated button, that changes the color on hover and while it’s pressed:

local function Button(props)
    local flick = props.flick
    local text = props.text
    local onClick = props.onClick

    return Roact.createElement('TextButton', {
        AutoButtonColor = false,
        Text = 'Click'

        -- here we assign using the name of the generated binding
        BackgroundColor3 = flick.bindings.foo,

        [Roact.Event.MouseButton1Click] = onClick,
        [Roact.Event.MouseEnter] = function()
            props.flick:setGoals({
                foo = Color3.new(0.75, 0.75, 0.75),
            })
        end,
        [Roact.Event.MouseLeave] = function()
            props.flick:resetGoals() 
        end,
        [Roact.Event.MouseButton1Down] = function()
            props.flick:setGoals({
                foo = Color3.new(0.5, 0.5, 0.5),
            })
        end,
        [Roact.Event.MouseButton1Up] = function()
            props.flick:resetGoals() 
        end,
    })
end

local AnimatedButton = Flick.withAnimation({
    initialValues = {
        foo = Color3.new(1, 1, 1),
    }, 
    motion = Flick.Motions.Timed(0.15),
})(Button)

return AnimatedButton

API

Just to avoid any confusion, the library itself is referred as Flick, where the injected property you get into the animated component is referred as flick

flick prop

flick.bindings

This table contains all the bindings generated by the library for you. For each entry you have in the initialValues given to withAnimation, you’ll have a binding named the same.

Example

If you wrap MyComponent with this configuration

return Flick.withAnimation({
    initialValues = {
        color = Color3.new(1, 1, 1),
        transparency = 0,
    }, 
    motion = Flick.Motions.Timed(0.15),
})(MyComponent)

You can assign the two bindings to the BackgroundColor property and the BackgroundTransparency

    BackgroundColor = flick.bindings.color,
    BackgroundTransparency = flick.bindings.transparency,

flick:setGoals(goals, goalReached: (string) -> ())

Animates each given goal to its associated value.
Pass the table that maps the new values you want to reach. You don’t have to specify all the values, simply put those you want to animate to. With the previous example, you could do

flick:setGoals({
    transparency = 1,
})
flick:setGoals({
    color = Color3.new(0.25, 0.25, 0.25),
})
flick:setGoals({
    transparency = 1,
    color = Color3.new(0.25, 0.25, 0.25),
})

The callback is called whenever one goal is reached. It receives the goal name.

flick:resetGoals(reached: (string) -> ())

Works like setGoals, except that it animates all values to their initial value (given using withAnimation).

flick:jumpToGoals(goals)

Works like setGoals, except that it does not animate, it goes directly to the given values.

Flick.Motions

Motions are used to configure how your animations will animate. For now, there is only one option which is the timed motion.

Flick.Motions.Timed(duration: number | config)

where config = { duration: number, easing: 'quad' | 'linear' }

Flick.Motions.Timed(0.2) -- give the duration and easing is defaulted to 'quad'
Flick.Motions.Timed({
    duration = 0.2,
    easing = 'quad',
})

Flick.withAnimation(config) -> (Component) -> Component

where config: { initialValues: table, motion: Flick.Motions }
The type is just returned might look confusing, but it just returns a function that wraps the given component with the configuration you passed to withAnimation.

local AnimatedButton = Flick.withAnimation({
    initialValues = {
        foo = Color3.new(1, 1, 1),
    }, 
    motion = Flick.Motions.Timed(0.15),
})(Button)

When you’ll render the AnimatedButton, you don’t have to pass the flick prop. It will automatically get it, merge it to the other props and pass them down to Button.

8 Likes