MyGo
Transitions

Guides

Transitions

Elements that move, resize and recolor smoothly as the layout changes, and come and go.

Transition makes an element move smoothly where the layout changes it: from where the last frame showed it to where it is now, in its parent, at its size, and in its background and border colors. The view says only where things are; the transition animates the way there.

var rowMotion = ui.ElementTransition{
	Enter: &ui.Motion{Collapse: true},
	Exit:  &ui.Motion{Collapse: true},
}

ui.Column(c).Children(func() {
	removed := -1
	for _, it := range app.items {
		ui.Row(c).Key(it.ID).Padding(8).Transition(rowMotion).Children(func() {
			ui.Text(c, it.Title).Grow(1)
			if ui.Button(c, "Remove").Clicked() {
				removed = it.ID // once the loop is done (see Views)
			}
		})
	}
	if removed >= 0 {
		app.remove(removed)
	}
})

Rows added grow in, rows removed collapse, and the others slide to their places, whether the items were added, removed, sorted or shuffled.

What moves

An ElementTransition animates over its Duration (200 ms when zero), along its Ease (ui.EaseOut when nil, or any easing):

  • Position: where the element is in its parent. Moving with its parent, as when a scroll view scrolls, is not a change.
  • Size: its width and height. The content lays out at each size on the way, as a panel opening wraps its text anew.
  • Colors: its background and border colors, as a hover fades in and out.

All three animate unless Position, Size or Colors choose some:

tile := ui.Box(c).Size(64, 48).Radius(8).Background(t.Surface)
if tile.Hovered() {
	tile.Background(t.SurfaceHover)
}
tile.Transition(ui.ElementTransition{Colors: true, Duration: 120 * time.Millisecond})

The element needs the same ID from frame to frame: a Key, or the same place among its siblings. A transition animates the element alone: the elements that a change moves too need a transition of their own to move smoothly, the same one to move in step, as the rows above, and the column holding them if its size changes:

open := app.sidebar
w := float32(56)
if open {
	w = 240
}
ui.Row(c).Fill().Children(func() {
	ui.Column(c).Width(w).ClipX().Transition(ui.ElementTransition{Size: true})
	ui.Column(c).Grow(1).Transition(ui.ElementTransition{})
})

Coming and going

Enter is where an element comes from as it appears, and Exit where it goes as it leaves, from its place in the layout: a Motion moved by X and Y, at an Opacity (0, transparent, when not set), and with Collapse, without height in a column or width in a row, its content clipped.

toast := ui.ElementTransition{
	Enter: &ui.Motion{Y: 12},
	Exit:  &ui.Motion{Opacity: 1, X: 300},
}

An element appears with its Enter only in a parent that was there in the frame before, so that a page or a dialog showing all at once does not animate every element in it. When an element is no longer built while its parent still is, a copy of it, as the last frame showed it, goes to its Exit under its siblings: it takes no room, so the siblings move into its place at once, smoothly if they have transitions, and it takes neither the pointer nor the focus. Built again before its exit ends, the element comes back from where its copy was.

When nothing moves

Changes that resizing the window makes do not animate, nor does anything while the desktop asks for less motion (c.Preferences().ReduceMotion). Frames are drawn only while something moves.

Rows of a List are placed by the list, which builds only those in view: their content can have transitions, but the rows do not slide, come or go with one.