Vinyl store

This example shows how to create an immersive swipeable cards experience. The screen pops with color and you can even interact with the products shown!

Inspiration: this good-looking cards animation by Artem Miskevich. All images are taken from unsplash.com.

Animated folder and vinyl

The products in our application are represented as "cards", so we start out by creating a reusable ux:Class component for them. Although the Card class holds the whole layout for a product, we'll take a look at how the animated folder and vinyl are created.

Inside of our Card class, we put a Panel that holds our folder and vinyl. While the folder Rectangle is simply styled with ImageFill that fills it with the supplied cover image, the vinyl element uses a semi-transparent vinyl.png image as a mask on top of a scaled-down version of the same cover art. Finally, we set the Alignment="HorizontalCenter" on both elements, so that the folder is right on top of the vinyl.

<Panel Margin="16" MaxWidth="320">

	<Rectangle ux:Name="folder" CornerRadius="1" Color="#fff8" Alignment="HorizontalCenter" Width="168">
		<ImageFill File="{ReadProperty Cover}" StretchMode="UniformToFill" WrapMode="ClampToEdge" />
		<Stroke Width="1" Color="#FFF3" />
		<Shadow />
	</Rectangle>

	<Panel ux:Name="vinyl" Alignment="HorizontalCenter" Width="168">
		<Circle ux:Name="disc" Margin="2">
			<Image File="Assets/vinyl.png" />
			<Circle Width="50%" Height="50%">
				<ImageFill File="{ReadProperty Cover}" StretchMode="UniformToFill" WrapMode="ClampToEdge" />
			</Circle>
		</Circle>
		<Circle Color="#0004" Smoothness="24" />
	</Panel>

</Panel>

To make the vinyl slide in and out of the folder, we will change their Alignment. For that to work, we need to do several things. On the parent container, we set MaxWidth="320" so that the vinyl can never fully leave the folder. In addition to that, we add a WhileTrue trigger that changes the Alignment on both vinyl and folder.

<Panel Margin="16" MaxWidth="320">
	<WhileTrue ux:Name="folderOpen">
		<Change folder.Alignment="Left" />
		<Change vinyl.Alignment="Right" />
	</WhileTrue>
</Panel>

Since we don't want to move the folder and vinyl elements over a fixed width, we add a LayoutAnimation trigger to both of them. As we Change the Alignment, they will Move in an arbitrary direction (specified by Vector="1"), relative to the position change.

<LayoutAnimation>
	<Move Vector="1" RelativeTo="PositionChange" DurationBack="0.4" Easing="QuarticOut" EasingBack="QuarticIn" />
</LayoutAnimation>

Finally, we add a Tapped trigger on the folder element, which calls Toggle on the WhileTrue that triggers the alignment change.

<Tapped>
	<Toggle Target="folderOpen" />
</Tapped>

For the final touch here, we want to get the record spinning! We add another WhileTrue and Toggle it from a Tapped trigger on the vinyl circle. The enclosed Spin animator then continuously rotates the disc element.

<WhileTrue ux:Name="recordSpinning">
	<Spin Target="disc" Frequency="0.8" />
</WhileTrue>

<Panel ux:Name="vinyl" Alignment="Center" Width="168" Height="100%">
	<LayoutAnimation>
		<Move Vector="1" RelativeTo="PositionChange" DurationBack="0.4" Easing="QuarticOut" EasingBack="QuarticIn" />
	</LayoutAnimation>
	<Tapped>
		<Toggle Target="recordSpinning" />
	</Tapped>
	<Circle ux:Name="disc" Margin="2">
		<Image File="Assets/vinyl.png" />
		<Circle Width="50%" Height="50%">
			<ImageFill File="{ReadProperty Cover}" StretchMode="UniformToFill" WrapMode="ClampToEdge" />
		</Circle>
	</Circle>
	<Circle Color="#0004" Smoothness="24" />
</Panel>

Background gradient transitions

We put all the cards in a PageControl which allows us to swipe between them. The data for our cards comes from JavaScript.

<JavaScript>
	var cards = [
		{
			title: "FREE LIMITED LP",
			description: "Despite the title, Hundredth's third LP Free doesn't feel liberating by any means. There hasn't been a reinvention of t...",
			artist: "Hundredth",
			artistPic: "Assets/artist1.jpeg",
			cover: "Assets/1.jpg",
			albums: "4",
			topColor: "#cdb8b5",
			bottomColor: "#4f3250"
		},
		...
	];
	module.exports = {
		cards: cards
	};
</JavaScript>
...
<PageControl ux:Name="cards" Padding="24,32,24,8">
	<Each Items="{cards}">
		<Card Title="{title}" Description="{description}" Artist="{artist}"
				ArtistPic="{artistPic}" Cover="{cover}" Albums="{albums}"
				TopColor="{topColor}" BottomColor="{bottomColor}">
		</Card>
	</Each>
</PageControl>

We can use a WhileActive trigger together with our PageControl, and that comes in handy for the gradient color transitions. Under each card, we put a WhileActive trigger with a Threshold="0.5" property, making the trigger activate when the card we're navigating to is half-way in. From there we set values of two Attractor nodes.

The Attractor values are bound to the Color property of GradientStop nodes in a LinearGradient. The use of an Attractor allows us to smoothly animate colors between multiple arbitrary values, - and since we can swipe between our cards both left and right, it's exactly what we need!

<Card>
	<WhileActive Threshold="0.5">
		<Set topColor.Value="{topColor}" />
		<Set bottomColor.Value="{bottomColor}" />
	</WhileActive>
</Card>
...
<Attractor ux:Name="topColor" Target="colorTop.Color" Value="#cdb8b5" Type="Easing" Duration="0.2" DurationExp="0" />
<Attractor ux:Name="bottomColor" Target="colorBottom.Color" Value="#4f3250" Type="Easing" Duration="0.2" DurationExp="0" />
<Rectangle>
	<LinearGradient StartPoint="0,0" EndPoint="0,1" AngleDegrees="72">
		<GradientStop ux:Name="colorTop" Offset="0" />
		<GradientStop ux:Name="colorBottom" Offset="1" />
	</LinearGradient>
</Rectangle>

Custom page indicators

The use of PageControl presents another advantage, since it allows us to use the flexible PageIndicator class. Our indicator is a simple white Circle with a tiny Rectangle inside. We make it pop with a DeactivatingAnimation - as we navigate away from a card, the corresponding page indicator is faded out, rotated by 360 degrees and made a bit smaller. When a card is activated, the same animation is played backwards, so we only need one!

<PageIndicator Height="56" Navigation="cards" Alignment="Center">
	<StackLayout ItemSpacing="12" Orientation="Horizontal" />
	<Panel ux:Template="Dot">
		<DeactivatingAnimation>
			<Scale Factor="0.8" />
			<Change indicator.Opacity="0.4" />
			<Rotate Degrees="360" />
		</DeactivatingAnimation>
		<Circle ux:Name="indicator" Width="12" Height="12" Color="#fff">
			<Rectangle Width="2" Height="7" Color="#0008" CornerRadius="1">
				<Translation Vector="-2,-2,0" />
				<Rotation Degrees="-45" />
			</Rectangle>
		</Circle>
	</Panel>
</PageIndicator>

That's it for this example. Feel free to download and play with the source code.