CodeOnly can initiate CSS transitions by applying classes to DOM elements when certain actions are initiated:
if
directivesdisplay
directiveclass_
directiveskey
Transitions are considered to be in either an "in-state" or an "out-state".
if
directives - the active branch has the in-state and all other branches
are in the out-state.display
directive - a shown item is in the in-state, a hidden
item is in the out-state.class_
directives - a true condition is the in-state
and a false condition is the out-state.When the state of an element switches from the out-state to the in-state it is said to be "entering". When an item switches from the in-state to the out-state is it said to be leaving.
When changing the content of an embed slot, or changing and item's key
To declare that transitions should be used, wrap the callback
that determines the in/out state with the transition
directive:
import { transition } from "@codeonlyjs/core";
{
if: transition(c => c.includeCondition),
type: "div",
}
For if
blocks with elseif
or else
conditions, the transition
directive must be on the first if
condition.
ie: you can't declare different transitions for different branches.
The same format is used for the display
directive:
{
display: transition(c => c.someCondition),
}
and for boolean classes:
{
class_selected: transition(c => c.isSelected),
}
Here's a simple example that fades an element in/out using a transition on an
if
directive.
The CSS class names used by transitions are covered in detail below, suffice to say for now:
.tx-active
class is added to an element while a transition is active. We
use this to add the CSS transition property to the element indicating that the
opacity should be transitioned..tx-out
class represents the out state of the element - in this case
we're saying the out state is opacity zero creating a fade in/out effect.{
if: transition(c => c.showItem),
type: "div .item",
text: "Item"
},
css`
.transition-demo1
{
.item
{
&.tx-active
{
transition: opacity 1s;
}
&.tx-out
{
opacity: 0;
}
}
}
`
You'll notice in the above example that while the element fades in and out, there's a jump in the height when the element is added and removed.
This is because this example is only animating the item's opacity and not its height. See below for an example that also animates the height.
Another way to trigger transitions is by setting a key
property on an element.
In this case the transition triggers when the key changes.
When a transition is triggered by a key change:
The key
property is not to be confused with the item keys
used by foreach
directives.
This example, uses the key
property to create a cross-fade
effect as a value changes.
{
type: "div .container",
$: {
key: transition(c => c.display),
type: "div .item",
text: c => c.display,
},
}
css`
.transition-demo2
{
.container
{
position: relative;
height: 1rem;
}
.item
{
position: absolute;
&.tx-active
{
transition: opacity 1s;
}
&.tx-out
{
opacity: 0;
}
}
}
`
Notice too in the above example how the .item
is absolutely positioned
within a .container
so the old and new items can overlay each other.
Transitions on embed slots are declared by placing the transition
directive on the content property.
This example creates a new component each time the button is clicked and then transitions the embed slot's content property. It also introduces some transform animations and different start/end states - these will be explained in more details in the next section.
{
type: "embed-slot",
content: transition(c => c.content),
}
We've snipped most of the code in the above example, but don't forget you can click the "Edit" link just above to see the full code sample and to experiment with it.
While a full discussion of CSS transition and animation techniques is beyond the scope of this documentation, here is the previously promised example of how to avoid jumps when item's are shown/hidden.
The basic idea is to:
To understand the set of CSS classes used by transitions it's useful to consider the two main types of transitions - symmetric vs asymmetric.
The following sections describe the default CSS class names - see below for information on how to customize these names.
A symmetric transition is one where the leave transition is the exact inverse of the enter transition.
A fade in/out transition is symmetric:
A slide in/out transition is symmetric if it slides in in one direction and slides out in the opposite direction.
A slide in/out transition that slides in from one side of the screen and out the other would not be symmetric because the start and end positions are different. It's an asymmetric transition.
The classes used in transitions are designed to be easy to use for both cases but more concise for symmetric transitions.
For symmetric transitions the following classes are used for transitions:
tx-active
- transitioning in or outtx-out
- the out state of the transitiontx-in
- the in state of the transition (rarely used)A fade in/out transition can be configured with just two CSS declarations:
.scoping-selector-for-you-element
{
.tx-active
{
/* While active class is set CSS transition property
for opacity */
transition: opacity 1s;
}
.tx-out
{
/* In the out state, the opacity is zero */
opacity: 0;
}
}
There's no need to use the tx-in
class here because the default opacity
of an element is 1 and doesn't need to be explicitly set.
We recommend specifying the transition
properties on the tx-active
class
instead of the non-qualified element selector as it helps avoid conflicts
with other transition settings.
For asymmetric transitions use the following classes for used for the enter side of the transition:
tx-entering
- element is enteringtx-enter-start
- state to enter fromtx-enter-end
- state to enter toand these when leaving:
tx-leaving
- element is leavingtx-leave-start
- start to leave fromtx-leave-end
- start to leave toSuppose you wanted an animation that entered by sliding in from below and left by fading out:
.scoping-selector-for-you-element
{
/* This controls the entering transition - transform */
.tx-entering
{
transition: transform 1s;
}
.tx-enter-start
{
transform: translateY(30);
}
/* This controls the leaving transition - opacity */
.tx-leaving
{
transition: opacity 0.5s;
}
.tx-leave-end
{
opacity: 0;
}
}
Of course you can combine things too.
eg: for a slide in from the bottom, slide out to the top with fading
.scoping-selector-for-you-element
{
.tx-active
{
/* This applies to both enter and leave */
transition: transform 1s, opacity 1s;
}
.tx-out
{
/* This is the out-state of both enter and leave */
opacity: 0;
}
.tx-enter-start
{
/* This is out-state for entering */
transform: translateY(30);
}
.tx-leave-end
{
/* This is out-state for leaving */
transform: translateY(-30);
}
}
There's no need to specify whether a transition is symmetric or asymmetric - in both cases both sets of classes are applied.
The full list of class names is therefore:
tx-active
- the element is actively entering or leavingtx-entering
- the element is actively enteringtx-enter-start
- set for one frame when an element starts enteringtx-enter-end
- set for the rest of the frames until the transition endstx-leaving
- the element is actively leavingtx-leave-start
- set for one frame when an element starts leavingtx-leave-end
- set for the rest of the frames until the transition endstx-out
- same as tx-enter-start
for entering elements and tx-leave-end
for leaving elementstx-in
- same as tx-enter-end
for entering elements and tx-leave-start
for leaving elementsLooking at this from the perspective of an entering element:
tx-active
,
tx-entering
, tx-enter-start
and tx-out
classes are added.tx-enter-start
and tx-out
classes are removed
and the tx-enter-end
and tx-in
classes are addedtx-active
, tx-entering
, tx-enter-end
and tx-in
).The inverse happens for a leaving element:
tx-active
, tx-leaving
, tx-leave-start
and tx-in
classes are
added.tx-leave-start
and tx-in
classes are removed
and the tx-leave-end
and tx-out
classes are addedtx-active
, tx-leaving
, tx-leave-end
and tx-out
).Transitions have various options that control transition behaviour.
Set these options by passing an object to the transition function.
The value callback can be specified with the value
key:
The following is equivalent to transition(c => c.showThisDiv)
.
{
type: "div",
display: transition({
value: c => c.showThisDiv,
// other options go here.
})
}
Alternatively you can specify options as a series of values and objects. Each successive argument is merged over the previous ones:
value
propertyname
propertyObject.assign
.eg:
transition(c => c.show, "transition",
{
// other options
mode: "enter-leave"
},
{
// more options
on_finish: c => c.onFinish(),
}
)
Is the equivalent of
transition({
value: c => c.show,
name: "transition",
mode: "enter-leave",
on_finish: c => c.onFinish()
});
By default when a transition has both entering and leaving elements, both the enter and leave transitions run concurrently.
For example, consider an if
/else
block with a fade transition. The
old active branch will be faded out at
the same time the new active branch is faded in.
The mode
option on a transition
can be used to make the two
transitions run one after the other.
if: transition({
value: c => c.someCondition,
mode: "enter-leave"
})
The mode propery can be a string or a callback returning a string.
This example demonstrates the various modes:
By default transitions have a prefix name of "tx-" (as shown in all the
above examples). By setting the transition's name
option the prefix
can be changed:
{
type: "div",
display: transition({
value: c => c.showThisDiv,
name: "fade",
})
},
Now the transition will use class names fade-active
, fade-out
,
fade-in
etc...
The name propery can be a string or a callback returning a string.
For even more control over class names you can explicitly specify them
by setting the classNames
property in the transition options:
{
type: "div",
display: transition({
value: c => c.showThisDiv,
classNames: {
"entering": "my-enter",
"enter-start": "my-enter-start",
"enter-end": "my-enter-end",
"leaving": "my-leaving",
"leave-start": "my-leave-start",
"leave-end": "*my-leave-end",
},
})
},
Any classes not specified will revert to the defaults:
{
"entering": "*-entering;*-active",
"enter-start": "*-enter-start;*-out",
"enter-end": "*-enter-end;*-in",
"leaving": "*-leaving;*-active",
"leave-start": "*-leave-start;*-in",
"leave-end": "*-leave-end;*-out",
}
Note that "*
" will be replaced by the transition's name property
(or "tx
" if not set) and multiple class names can be specified by
separating them with semi-colons.
The classNames
propery can be a object map of class names or a
callback that returns one.
To change the default class names globally, change the defaultClassNames
property of the TransitionCss
function:
import { TransitionCss } from "@codeonlyjs/core";
TransitionCss.defaultClassNames = {
// Your class names
}
The supplied object must include class name entries for all classes.
The duration
option lets you specify an explicit duration of a transition
instead of watching for the element's animations to complete.
This can be useful in complex scenarios with many animations where determining the correct end condition might be ambiguous.
The duration
is specified in milliseconds and can be:
[0]
is the entering duration and [1]
the
leaving duration.By default when watching for a transition to complete, the entire sub-tree of the contained elements are monitored for animations.
If a transition only affects the immediate child elements contained within
it, the subtree
property can be set to false
to only monitor those elements.
This can be used in situations when:
To receive notifications when a transition starts, ends or is cancelled, set callbacks on the options object:
on_start
- notifies a transition has startedon_finish
- notifies a transition has finishedon_cancel
- notifies a transition has been canceledThe arguments passed to the callbacks are the same as other dynamic property callbacks from templates.
{
type: "div",
display: transition({
value: c => c.showThisDiv,
on_finish: c => c.onTransitionFinished(),
})
},
An animation is cancelled if a new transition is started before the current transition has completed.
Everything described above applies to CodeOnly's built-in
TransitionCss
transition handler.
You can however built your own transition handlers that use JavaScript or other animation techniques and libraries for animations.
If the transition options has a type
property it is used as a
constructor to create a custom transition handler.
The handler is constructed using the new
operator
and passed the following parameters:
options
- the options object passed to the transition
function.context
- the template's context object.The owning Component instance is available as the context.model
property.
The object returned by the constructor is expected to have the following members, all of which are required:
A function that receives an array of nodes being entered. May be called multiple times so the transition object should accumulate all passed nodes.
Same as enterNodes
except it receives the nodes that are leaving.
Receives a function that the custom transition must call when the enter phase of the transition is about to start.
This callback is used to mutate the DOM for the entering state.
Receives a function that the custom transition must call after the leave phase of the transition has completed.
This callback is used to mutate the DOM for the leaving state.
Important: the onWillEnter
callback must always be called before
the onDidLeave
. This is because usually the entering nodes will
be replacing the leaving nodes. If the leaving nodes are removed
before the entering nodes are inserted the position in the DOM can
be lost.
Instructs the transition to start.
Once start
has been called the onWillEnter
and onDidLeave
supplied
callbacks must be called - either at some point in the future as the transition
runs, or in response to the finish
method being called.
Further, onWillEnter
and onDidLeave
must be called
exactly once each.
Instructs the transition to finish, cancelling any pending actions
and immediately completing any pending calls to onWillEnter
and
onDidLeave
.