Everything about the UWP ParallaxView Control

In this article we dive into the UWP ParallaxView control, an important player in Fluent Design that allows you to create a feeling of depth, perspective, and movement in your layered UI.

I created a small UWP app to demonstrate

  • defining a vertical parallax effect,
  • defining a horizontal parallax effect,
  • fine-tuning the animations, and
  • declaratively and programmatically adapting the UI to the Windows release.

I’m not going to spend too much time in introductions, but if you insist: there’s this great article on docs.microsoft.com. It also hosts this picture that says it all:

parallax_v2

To achieve  a parallax effect, all you need is

  • a (faster) scrolling foreground: any element with a ScrollViewer will do,
  • a (slower) scrolling background: any UIElement, but typically an image, and
  • a ParallaxView control to glue these together.

The ParallaxView control adjusts the size of the background to always remain in view, and registers a Composition Animation that moves the background when you scroll through the foreground element.

Vertical parallax

Here’s the canonical ParallaxView setup:

  • it’s wrapped around the background Image,
  • its Source property refers to the ListView in the foreground, and
  • its VerticalShift property defines the range in pixels (and the axis) of the parallax motion:
<ParallaxView Source="{x:Bind ForegroundElement}"
                VerticalShift="100">

    <!-- Background element -->
    <Image x:Name="BackgroundImage"
            Source="ms-appx:///Assets/cyberpunk.jpg"
            Stretch="UniformToFill" />
</ParallaxView>

<!-- Foreground element -->
<ListView x:Name="ForegroundElement"
            ScrollViewer.VerticalScrollBarVisibility="auto">
    <!-- ListView style and content ... -->
</ListView>

Here’s how this looks like in the sample app:

VerticalParallax

This page uses a double parallax effect: the white text in the center is animated too. I used a smaller VerticalShift value so it appears to dangle between the foreground and the background:

<ParallaxView Source="{x:Bind ForegroundElement}"
                VerticalShift="50">
    <TextBlock Text="Bladerunner 2049" />
</ParallaxView>

This is just to illustrate that the background element does not need to be an image, and that multiple ParallaxView instances can be tied to the same source.

Horizontal Parallax

If you have a horizontally scrolling app –such as the Weather app- then you can also achieve a parallax effect, just set the HorizontalShift property. Here’s three controls simulating the sea:

<ParallaxView Source="{x:Bind ForegroundElement}"
                HorizontalShift="33"
                Margin="0 0 -33 180">
    <Image Source="ms-appx:///Assets/waves.png"
            Stretch="UniformToFill"
            Height="200"
            VerticalAlignment="Bottom" />
</ParallaxView>

<ParallaxView Source="{x:Bind ForegroundElement}"
                HorizontalShift="66"
                Margin="-66 0 0 100">
    <Image Source="ms-appx:///Assets/waves.png"
            Stretch="UniformToFill"
            Height="200"
            VerticalAlignment="Bottom" />
</ParallaxView>

<ParallaxView Source="{x:Bind ForegroundElement}"
                HorizontalShift="100">
    <Image x:Name="BackgroundImage"
            Source="ms-appx:///Assets/waves.png"
            Stretch="UniformToFill"
            Height="200"
            VerticalAlignment="Bottom" />
</ParallaxView>

Here’s how this looks like in the sample app:

HorizontalParallax

By the way: this is a nice use case for playing with negative shift values. Feel free to install the sample app and give it a try…

Fine-tuning the shift animations

In most cases it suffices to setting the vertical or horizontal shift to create a decent parallax effect. In other cases you may want to adjust the animations to achieve pixel-perfectness or realistic behavior. The sample app comes with a configuration page where you can test the other relevant properties of the ParallaxView control: VerticalSourceStartOffset, VerticalSourceEndOffset, VerticalSourceOffsetKind, MaxVerticalShiftRatio, and IsVerticalShiftClamped. For the sake of completeness: all of these have also a a Horizontal counterpart.

This is how the configuration page looks like:

ConfigurationPage

Sometimes it’s hard to see the impact of changing one of the parameters. This is especially the case when the background image automatically resizes itself (e.g. when it’s Stretched to Uniform or UniformToFill, which is … well… always).

For the edge cases I created an alternative background with a non-stretched ruler from 0 to 100. To enable is, just switch the comments in the configuration view:

<ParallaxView x:Name="Parallax"
                Source="{x:Bind ForegroundElement}">
    <Image x:Name="BackgroundImage"
            Source="ms-appx:///Assets/mountains.jpg"
            Stretch="UniformToFill"
            HorizontalAlignment="Center" />
    <!--<Image x:Name="BackgroundImage"
            Source="ms-appx:///Assets/ruler.jpg"
            Stretch="None" />-->
</ParallaxView>

The page looks less sexy like this, but it’s a lot more useful. Here’s a standard vertical parallax with a VerticalShift of 200 pixels:

ConfigurationDetails_1

In this configuration, you lose part of the background. The start and the end of the ruler (the 0 and 100 marker) are off-screen and only become visible when scrolling by touch – because of the built-in inertia animations. If you’re using a mouse, these parts of the background will never show.

Fortunately, with VerticalSourceStartOffset and VerticalSourceEndOffset and their horizontal counterparts, you can move the start and the end position of the background element. When we move the vertical source start offset to 100 pixels, the zero marker appears on top:

ConfigurationDetails_2

Likewise, setting the vertical source end offset to –100 reveals the 100 marker when you scroll to the bottom of the foreground (note: that was using Stretch.Uniform on the background):

ConfigurationDetails_3

Please observe that this behavior of the ParallaxView is only a side effect (pun intended Smile). These properties actually specify the vertical scroll offset at which parallax motion starts and ends. This would allow you to do small adjustments in the foreground element without starting an animation on the background – I assume.

Unfortunately the settings only have an effect against a background image that does not stretch (i.e. almost never) and only when you start scrolling from the top or the bottom.

Here’s how this looks like in practice – there’s a small delay in the background animation:

VerticalSourceStartOffset

Use MaxVerticalShiftRatio (with a range from 0 to 1) to limit the background scrolling speed. If you set it to 0.2 in the sample app and scroll down, you’ll see that you can’t reach the end of the ruler anymore:

ConfigurationDetails_4

I assume that the IsVerticalShiftClamped property switches the vertical shift on and off, but I see no effect in the sample app.

The VerticalSourceOffsetKind is even more mysterious. The documentation states that it can be Absolute or Relative, and that’s it. I can’t see the logic in the state changes – yet. I do observe some unexpected shifts from time to time, like this:

ConfigurationDetails_5

We want more

If you want more control over the parallax effect than what’s provided by the ParallaxView, then you have to roll the animations yourself. Here are two fine examples:

Targeting pre-FCU versions

The ParallaxView is an easy way to get some of that Fluent Design into your app. It was introduced with the Fall Creators Update (a.k.a. FCU, or Build 16299), and your apps may be targeting earlier releases. Don’t worry: it’s easy to make the ParallaxView an optional component in your UI. With the same code base, you can present  a static background image for the users on an earlier release, and use the parallax effect for the ones on a newer release.

Let me prove that to you. The Conditional XAML page in the sample app is divided in three zones:

  • on the left is the canonical implementation that requires FCU,
  • the image in the middle uses declarative conditional XAML, while
  • the right hand part uses programmatic conditional XAML.

Here’s how it looks like at run-time:

ConditionalPage

Declarative conditional XAML

If you’re targeting Creators Update (Build 15063) or higher, then you can use Conditional XAML –a markup extension on top of IsApiContractPresent -to configure the UI based on the release.

Here’s how to use it. First declare the conditional XAML namespaces to see whether or not you’re running on Fall Creators Update (that’s UniversalApiContract 5) or higher:

xmlns:PriorToFcu="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,5)"
xmlns:FcuOrHigher="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,5)"

Now you can create conditional elements in your XAML, like this:

<!-- Declarative conditional XAML expressions. -->
<FcuOrHigher:ParallaxView Source="{x:Bind ForegroundElement}"
                            VerticalShift="100">
    <Image Source="ms-appx:///Assets/beach.jpg" />
</FcuOrHigher:ParallaxView>
<PriorToFcu:Image Source="ms-appx:///Assets/beach.jpg" />

These will be evaluated at run-time to spawn the appropriate XAML.

Programmatic conditional XAML

Alternatively you can call the primitives on which the markup extension is built. I’m not sure it’s a good idea to call IsApiContractPresent all over the place, so I created this nice helper class –with room for extension- in a MVVM Versioning service. It allows me to evaluate Sdk.SupportsParallaxView:

public static class Sdk
{
    public static bool SupportsParallaxView
    {
        get
        {
            return ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 5);
        }
    }
}

For the XAML part, just draw the image. Don’t forget to give it a Name:

<!-- Programmatic conditional XAML expressions. -->
<Image x:Name="BeachImage"
        Source="ms-appx:///Assets/beach.jpg" />

The Grid that hosts the elements should also get a name – I called it ContentGrid. Here’s the code that detects the support for ParallaxView and modifies the XAML programmatically. It executes in the Loaded event, when the Visual Tree was populated and is ready for interaction:

private void ConditionalPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
    if (Sdk.SupportsParallaxView)
    {
        // Insert ParallaxView.
        var parallax = new ParallaxView();
        parallax.Source = ForegroundElement;
        parallax.VerticalShift = 100;
        Grid.SetColumn(parallax, 2);
        ContentGrid.Children.Remove(BeachImage);
        parallax.Child = BeachImage;
        ContentGrid.Children.Insert(0, parallax);
    }
}

I definitely prefer the programmatic approach here, because

  • I can adapt the XAML not only based on the Windows Release, but also on hardware specifications, or on user preferences, and
  • it comes with decent XAML Designer support.

This is how the page looks at design time. All conditional XAML elements are simply ignored:

XamlDesigner

Call to action

You see: nothing prevents you from using ParallaxView in your apps to bring in some Fluent Design. Just make sure you don’t exaggerate…

The code

The ParallaxView sample app lives here on GitHub.

Enjoy!

Leave a comment