Search code examples
wpfanimationcolors

How to animate to a translucent colour in WPF?


Simplified(!) example:

<Window Background="Black">

    <Button Background="#ff272727"
            Content="Whatever"
            Foreground="White"
            Padding="16,8"
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
        <Button.Template>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border x:Name="container" Background="{TemplateBinding Background}">
                    <ContentPresenter Margin="{TemplateBinding Padding}" />

                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition GeneratedDuration="0:0:2" />
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetName="container"
                                                    Storyboard.TargetProperty="Background.Color"
                                                    To="#33ff8000" />
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Border>
            </ControlTemplate>
        </Button.Template>
    </Button>

</Window>

When hovering over the button, I would expect the colour to transition from #ff272727 (opaque dark gray) to #33ff8000 (translucent orange) over a duration of 2 seconds. However, what actually happens is that it first transitions very quickly (I estimate about a quarter of a second) to what looks like #ffff8000 and only then continues to #33ff8000 over the rest of the 2 seconds. Essentially, it seems like it is animating the colour and alpha components one after the other rather than both at the same time.

Interestingly, the same thing does not happen if I exchange the alpha values of the two colours, i.e. when I animate from a translucent colour to a fully opaque one. It only seems to happen when the To colour is not opaque.

Another interesting observation for me was that the exact same phenomenon occurs even with this storyboard:

<Storyboard>
    <DoubleAnimation Storyboard.TargetName="container"
                     Storyboard.TargetProperty="Background.Opacity"
                     To="0.2" />
    <ColorAnimation Storyboard.TargetName="container"
                    Storyboard.TargetProperty="Background.Color"
                    To="#ffff8000" />
</Storyboard>

This will also first animate the colour and then the opacity...

Am I doing something wrong or is this a known issue? If so, are there any known workarounds?


Solution

  • This is actually a case of your eyes playing tricks on you. Light is a tricky thing and it's intensity is not linear (either logarithmic or closer to square kind of depending on what aspect of light intensity/color we're talking about). Furthermore, going from gray to orange-ish is a much shorter step than the reduction opacity, so your eyes perceive this happening much sooner than the opacity change.

    Here are a couple links that go deeper into that topic:

    To illustrate this phenomena with your example, I changed your template to show what the animation is doing. In the result below, you can see that it is indeed moving through both values linearly.

    <Button Background="#FF272700" Foreground="White" Padding="16,8" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button.Template>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border x:Name="container" Background="{TemplateBinding Background}" Padding="16,8">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <TextBlock Text="{Binding Background.Color, ElementName=container}" />
                        <TextBlock Grid.Row="1" Text="{Binding Background.Opacity, ElementName=container}" />
                    </Grid>
                    <!--<ContentPresenter Margin="{TemplateBinding Padding}" />-->
    
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition GeneratedDuration="0:0:05" />
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver">
                                <Storyboard Changed="Storyboard_Changed">
                                    <DoubleAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="Background.Opacity" To="0.2" />
                                    <ColorAnimation Storyboard.TargetName="container" Storyboard.TargetProperty="Background.Color" To="#ff8000" />
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Border>
            </ControlTemplate>
        </Button.Template>
    </Button>
    

    enter image description here

    The fix is going to be to change the speeds and/or start points of your animations separately to get them to appear the way you want.