Skip to content
Shiny.Net.HttpServer v1 - A lightweight feature rich HTTP Server - Tunnels, Websockets, AOT, ASPNET Featureset, & Works EVERYWHERE!Let me see!

Triggers

Trigger is a [Flags] enum, so an icon can loop and respond to a tap.

[Flags]
public enum MotionTrigger
{
Manual = 0,
Loop = 1,
Hover = 2,
Press = 4,
Appear = 8
}

The default is Hover | Press — hover for desktop, press for touch, so an icon does something sensible everywhere without being told.

Runs continuously. Interval inserts a resting gap between cycles, which is almost always what you want: a bell that rings without pause reads as broken.

<shiny:MotionIconView Icon="bell" Trigger="Loop" Interval="0:0:1.5" />

The gap is folded into the animation itself rather than scheduled by a timer. That is what keeps the two hosts honest — a CSS animation has no way to pause between iterations, so a spec that expressed the gap externally would need a JavaScript timer on the web and a dispatcher timer on MAUI, and the two would drift apart. Squeezing the keys into the front of a longer cycle and holding the resting pose through the remainder produces the same result with nothing but animation-iteration-count: infinite.

Runs while the pointer is over the icon. When the pointer leaves, the icon finishes the cycle it is in and then stops, so a half-swung bell settles upright instead of snapping.

<MotionIcon Icon="settings" Trigger="MotionTrigger.Hover" />

Hover needs a pointer, so pair it with Press for touch — which the default already does.

One play per tap or click.

<shiny:MotionIconView Icon="heart" Trigger="Press" Command="{Binding LikeCommand}" />

On MAUI a Command is invoked whether or not Press is set, so the icon can act as a button while some other trigger drives its motion. On Blazor the equivalent is OnClick.

Press only hears presses that land on the icon itself. An icon sitting inside a larger tap target — a button, a list row — should be Manual, with the host playing it, or a click on the label around it will animate nothing. That is exactly what ShinyButton does with its slot icons on both hosts.

RepeatCount controls how many cycles a triggered play runs for — set it to 2 for a double-take, or 0 to keep going until something stops it.

Plays once when the icon first becomes visible. On the web this is an IntersectionObserver; on MAUI it fires when the view loads.

<MotionIcon Icon="check" Trigger="MotionTrigger.Appear" Title="Saved" />

It is deliberately one-shot. An icon that replays every time it scrolls past is a distraction; if you want that, drive IsPlaying yourself.

Manual is the absence of any automatic trigger. Drive it with the methods, or — far more often — bind IsPlaying:

<shiny:MotionIconView Icon="loader" Trigger="Manual" IsPlaying="{Binding IsBusy}" />
<MotionIcon Icon="loader" Trigger="MotionTrigger.Manual" IsPlaying="@busy" />

While the flag is set the icon loops; when it clears, the icon returns to its resting pose. For the spinner that means settling back into a full three-quarter ring rather than snapping wider the instant it stops.

Method Does
Play() Plays for RepeatCount cycles.
Loop() Blazor only — plays continuously.
PlayAsync() MAUI only — completes when the animation finishes.
Stop() Stops now and returns to rest.
StopAtCycleEnd() Lets the current cycle finish, then stops.
Reset() MAUI only — back to rest without changing playback.

StopAtCycleEnd() is what the hover trigger uses internally, and it is usually the better choice when a user-visible state ends: it costs at most one more cycle and always finishes on the resting pose.

Because evaluating the animation is a pure function of progress — nothing reads the previous frame — an icon can be driven directly from a slider or a gesture:

<shiny:MotionIconView Icon="menu" Trigger="Manual"
Progress="{Binding Source={x:Reference Scrubber}, Path=Value}" />
<Slider x:Name="Scrubber" Minimum="0" Maximum="1" />

Dragging the slider morphs the hamburger into a cross and back. The same property reports position while the animation plays.