runTransition method
void
runTransition(
- String propertyName,
- dynamic begin,
- dynamic end
)
Implementation
void runTransition(String propertyName, begin, end) {
if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
cssLogger.info('[transition][run] property=$propertyName begin=${begin ?? 'null'} end=$end');
}
// For box-shadow, prefer the current computed shadow list as the
// transition begin value when the previous CSS text is var()-based.
// This avoids snapping when variables are updated before the
// shorthand (e.g., Tailwind's --tw-shadow patterns).
if (propertyName == BOX_SHADOW && begin is String && begin.contains('var(')) {
final dynamic current = getProperty(propertyName);
if (current is List<CSSBoxShadow> && current.isNotEmpty) {
try {
begin = _stringifyBoxShadowForTransition(current);
if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
cssLogger.info('[transition][run] property=$propertyName override-begin-from-computed "$begin"');
}
} catch (_) {
// Fallback to string-based begin if serialization fails.
}
}
}
if (_hasRunningTransition(propertyName)) {
Animation animation = _propertyRunningTransition[propertyName]!;
if (cssTransitionHandlers.containsKey(propertyName) && animation.effect is KeyframeEffect) {
KeyframeEffect effect = animation.effect as KeyframeEffect;
var interpolation = effect.interpolations.firstWhere((interpolation) => interpolation.property == propertyName);
var stringifyFunc = cssTransitionHandlers[propertyName]![2];
// Matrix4 begin, Matrix4 end, double t, String property, CSSRenderStyle renderStyle
begin = stringifyFunc(
interpolation.lerp(interpolation.begin, interpolation.end, animation.progress, propertyName, this));
}
if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
cssLogger.info('[transition][run] cancel-existing property=$propertyName progress=${animation.progress.toStringAsFixed(3)}');
}
animation.cancel();
// An Event fired when a CSS transition has been cancelled.
target.dispatchEvent(Event(EVENT_TRANSITION_CANCEL));
}
if (begin == null || (begin is String && begin.isEmpty)) {
begin = cssInitialValues[propertyName] ?? '';
if (begin == CURRENT_COLOR) {
begin = currentColor;
}
}
if (end == null || (end is String && end.isEmpty)) {
end = cssInitialValues[propertyName] ?? '';
}
// Keyframe.value is typed as String; ensure our transition endpoints
// are always serialized strings before constructing keyframes.
if (begin is! String) {
begin = begin.toString();
}
if (end is! String) {
end = end.toString();
}
EffectTiming? options = getTransitionEffectTiming(propertyName);
// Fallback: if effective duration is 0, apply end immediately rather than
// creating a no-op animation that never fires finish.
final double durationMs = options?.duration ?? 0;
if (durationMs <= 0) {
if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
cssLogger.info('[transition][run] property=$propertyName duration=0; direct-apply "$end"');
}
target.setRenderStyle(propertyName, end);
return;
}
List<Keyframe> keyframes = [
Keyframe(propertyName, begin, 0, LINEAR),
Keyframe(propertyName, end, 1, LINEAR),
];
KeyframeEffect effect = KeyframeEffect(this, keyframes, options, isTransition: true);
Animation animation = Animation(effect, target.ownerDocument.animationTimeline);
_propertyRunningTransition[propertyName] = animation;
animation.onstart = () {
// An Event fired when a CSS transition is created,
// when it is added to a set of running transitions,
// though not necessarily started.
target.dispatchEvent(TransitionEvent(
EVENT_TRANSITION_START,
propertyName: _toHyphenatedCSSProperty(propertyName),
elapsedTime: 0.0,
));
};
animation.onfinish = (AnimationPlaybackEvent event) {
_propertyRunningTransition.remove(propertyName);
target.setRenderStyle(propertyName, end);
// An Event fired when a CSS transition has finished playing.
if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
cssLogger.info('[transition][finish] property=$propertyName applied-end "$end"');
}
// elapsedTime is the time the transition has run, in seconds,
// excluding any transition-delay. Our EffectTiming stores duration
// in milliseconds.
final double durationMs = options?.duration ?? 0.0;
final double elapsedSec = durationMs > 0 ? durationMs / 1000.0 : 0.0;
target.dispatchEvent(TransitionEvent(
EVENT_TRANSITION_END,
propertyName: _toHyphenatedCSSProperty(propertyName),
elapsedTime: elapsedSec,
));
};
// For transitionrun, elapsedTime is always 0.
target.dispatchEvent(TransitionEvent(
EVENT_TRANSITION_RUN,
propertyName: _toHyphenatedCSSProperty(propertyName),
elapsedTime: 0.0,
));
if (DebugFlags.shouldLogTransitionForProp(propertyName)) {
cssLogger.info('[transition][run] play property=$propertyName duration=${options?.duration} easing=${options?.easing} delay=${options?.delay}');
}
animation.play();
}