Animaciones de transición de pantallas (Page Transitions) en JavaFX
Aprende a crear transiciones suaves como 'Slide' y 'Fade In' al cambiar de vista en tu aplicación JavaFX utilizando el API de Animaciones nativo.
JJ Arroyo
2 de marzo de 2026 • 7 min de lectura

Las aplicaciones modernas rara vez cambian de pantalla con un salto brusco. Ya sea en web o en móvil, esperamos que las vistas entren deslizándose (Slide) o apareciendo suavemente (Fade In). Esto no solo hace que la aplicación se sienta "Premium", sino que ayuda al usuario a entender espacialmente de dónde vienen las cosas.
JavaFX cuenta con un potente framework de animaciones (javafx.animation.*) que nos permite transformar las transiciones rígidas de Node en coreografías fluidas, sin depender de librerías externas de terceros.
En este artículo, construiremos un gestor de ventanas básico y aprenderemos a inyectarle animaciones Fade In, Slide Left y Slide Up.
El Concepto Básico: StackPane al Rescate
Para hacer transiciones entre pantallas, lo ideal no es cambiar la escena entera del Stage (stage.setScene(...)), porque eso destruye el contexto y es imposible de animar suavemente entre la escena vieja y la nueva.
Lo correcto es mantener un contenedor "Raíz" inmutable —usualmente un StackPane, que apila los elementos uno encima del otro— e ir agregando (y luego eliminando) las diferentes vistas dentro de él.
- La Vista Vieja está visible en el StackPane.
- La Vista Nueva se agrega al StackPane, quedando por encima de la Vieja. Al principio, la posicionas fuera de la pantalla (para un Slide) o con opacidad cero (para un Fade).
- Disparas la animación sobre la Nueva Vista.
- Una vez terminada la animación, eliminas la Vista Vieja del StackPane (para liberar memoria).
1. Transición con Fade In (Desvanecimiento)
El Fade In cambia la propiedad opacity del nuevo nodo desde 0.0 hasta 1.0. En JavaFX, lograr esto toma solo un par de líneas usando FadeTransition.
import javafx.animation.FadeTransition;
import javafx.util.Duration;
import javafx.scene.layout.Region;
public void transitionFade(StackPane root, Region oldView, Region newView) {
// 1. Preparamos la nueva vista invisible
newView.setOpacity(0.0);
// 2. La añadimos encima de la vista actual
root.getChildren().add(newView);
// 3. Configuramos la animación (Duración de 500 ms)
FadeTransition fade = new FadeTransition(Duration.millis(500), newView);
fade.setFromValue(0.0);
fade.setToValue(1.0);
// 4. Limpiamos una vez terminado el trabajo
fade.setOnFinished(e -> {
if (oldView != null) {
root.getChildren().remove(oldView);
}
});
// 5. ¡A rodar!
fade.play();
}
2. Transición con Slide (Desplazamiento Lateral)
Para que una pantalla "empuje" a la otra o entre desde la derecha, necesitamos usar y animar la propiedad de traslación (translateXProperty). En lugar de FadeTransition, usaremos TranslateTransition.
Para que se vea impresionante, aplicaremos una interpolación ("Easing"). El Interpolator.EASE_BOTH (O aceleración y desaceleración suave) es el mejor amigo de un diseñador de UI.
import javafx.animation.TranslateTransition;
import javafx.animation.Interpolator;
import javafx.util.Duration;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
public void transitionSlideLeft(StackPane root, Region oldView, Region newView) {
// 1. Obtenemos el ancho actual de la ventana
double width = root.getWidth();
// 2. Movemos la nueva vista completamente hacia la derecha (fuera de la pantalla)
newView.setTranslateX(width);
root.getChildren().add(newView);
// 3. Preparamos el movimiento de la NUEVA vista (Entrar)
TranslateTransition slideIn = new TranslateTransition(Duration.millis(500), newView);
slideIn.setByX(-width); // Moverse un "width" entero hacia la izquierda
slideIn.setInterpolator(Interpolator.EASE_BOTH);
// 4. (Opcional pero recomendado) Preparamos el movimiento de la VIEJA vista (Salir)
TranslateTransition slideOut = new TranslateTransition(Duration.millis(500), oldView);
slideOut.setByX(-width); // La empujamos hacia la izquierda también
slideOut.setInterpolator(Interpolator.EASE_BOTH);
// Limpieza al terminar
slideIn.setOnFinished(e -> {
root.getChildren().remove(oldView);
// Reseteamos las posiciones X para reutilizar las pantallas sin bugs
oldView.setTranslateX(0);
newView.setTranslateX(0);
});
// ¡Ejecutamos ambas a la vez!
slideIn.play();
if (oldView != null) slideOut.play();
}
Ensamblando la Master Class (El Demo)
Para poner a prueba todo esto, vamos a construir una clase de ejemplo que tiene un StackPane principal y tres vistas (pantallas de colores distintos), con botones para alternar entre animaciones.
Cópiala y pruébala en tu IDE:
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PageTransitionsDemo extends Application {
private StackPane mainContainer;
private VBox activeView; // Referencia a la pantalla actual
@Override
public void start(Stage primaryStage) {
mainContainer = new StackPane();
mainContainer.setStyle("-fx-background-color: #222;");
// Creamos nuestra primera pantalla y la establecemos como activa inmediatamente
activeView = createScreen("Pantalla Principal", "#3b82f6");
mainContainer.getChildren().add(activeView);
Scene scene = new Scene(mainContainer, 800, 600);
primaryStage.setTitle("JavaFX Page Transitions");
primaryStage.setScene(scene);
primaryStage.show();
}
// --- CONSTRUCTOR DE PANTALLAS ---
private VBox createScreen(String titleText, String bgColorCode) {
VBox screen = new VBox(30);
screen.setAlignment(Pos.CENTER);
screen.setStyle("-fx-background-color: " + bgColorCode + ";");
Label title = new Label(titleText);
title.setFont(Font.font("System", javafx.scene.text.FontWeight.BOLD, 48));
title.setTextFill(Color.WHITE);
// Grupo de botones para ir a otras pantallas con diferentes efectos
HBox btnContainer = new HBox(15);
btnContainer.setAlignment(Pos.CENTER);
Button btnFade = styleButton("Ir a Verde (Fade)");
btnFade.setOnAction(e -> applyFadeTransition(createScreen("Pantalla Verde", "#10b981")));
Button btnSlideLeft = styleButton("Ir a Naranja (Slide Left)");
btnSlideLeft.setOnAction(e -> applySlideLeftTransition(createScreen("Pantalla Naranja", "#f59e0b")));
Button btnSlideUp = styleButton("Ir a Azul (Slide Up)");
btnSlideUp.setOnAction(e -> applySlideUpTransition(createScreen("Pantalla Principal", "#3b82f6")));
btnContainer.getChildren().addAll(btnFade, btnSlideLeft, btnSlideUp);
screen.getChildren().addAll(title, btnContainer);
return screen;
}
private Button styleButton(String text) {
Button btn = new Button(text);
btn.setStyle("-fx-font-size: 16px; -fx-padding: 12 24; -fx-background-color: rgba(255,255,255,0.2); -fx-text-fill: white; -fx-background-radius: 8; -fx-cursor: hand;");
btn.setOnMouseEntered(e -> btn.setStyle("-fx-font-size: 16px; -fx-padding: 12 24; -fx-background-color: rgba(255,255,255,0.4); -fx-text-fill: white; -fx-background-radius: 8; -fx-cursor: hand;"));
btn.setOnMouseExited(e -> btn.setStyle("-fx-font-size: 16px; -fx-padding: 12 24; -fx-background-color: rgba(255,255,255,0.2); -fx-text-fill: white; -fx-background-radius: 8; -fx-cursor: hand;"));
return btn;
}
// --- LOGICA DE TRANSICIONES ---
private void applyFadeTransition(VBox newScreen) {
VBox oldScreen = activeView;
newScreen.setOpacity(0.0);
mainContainer.getChildren().add(newScreen);
FadeTransition ft = new FadeTransition(Duration.millis(500), newScreen);
ft.setToValue(1.0);
ft.setOnFinished(e -> mainContainer.getChildren().remove(oldScreen));
ft.play();
activeView = newScreen;
}
private void applySlideLeftTransition(VBox newScreen) {
VBox oldScreen = activeView;
double width = mainContainer.getWidth();
newScreen.setTranslateX(width);
mainContainer.getChildren().add(newScreen);
TranslateTransition slideIn = new TranslateTransition(Duration.millis(600), newScreen);
slideIn.setByX(-width);
slideIn.setInterpolator(Interpolator.EASE_BOTH);
TranslateTransition slideOut = new TranslateTransition(Duration.millis(600), oldScreen);
slideOut.setByX(-width);
slideOut.setInterpolator(Interpolator.EASE_BOTH);
slideIn.setOnFinished(e -> mainContainer.getChildren().remove(oldScreen));
slideIn.play();
slideOut.play();
activeView = newScreen;
}
private void applySlideUpTransition(VBox newScreen) {
VBox oldScreen = activeView;
double height = mainContainer.getHeight();
newScreen.setTranslateY(height); // Posicionar abajo
mainContainer.getChildren().add(newScreen);
// Aceleración de "Salto" estilo Bottom Sheet
TranslateTransition slideIn = new TranslateTransition(Duration.millis(600), newScreen);
slideIn.setByY(-height);
slideIn.setInterpolator(Interpolator.SPLINE(0.25, 0.1, 0.25, 1)); // Curva bezier para un rebote elegante
// (Opcional) Podemos hacer que la pantalla vieja NO SE MUEVA, que solo se quede estática mientras la nueva la tapa subiendo
slideIn.setOnFinished(e -> mainContainer.getChildren().remove(oldScreen));
slideIn.play();
activeView = newScreen;
}
public static void main(String[] args) {
launch(args);
}
}
Conceptos Avanzados de Animación
Si quieres llevar tus UI al modo profesional puro, hay tres cosas fundamentales a observar en el ejemplo que escribimos:
- El
ParallelTransition: En nuestros métodosSlidedisparamosslideIn.play()yslideOut.play()al mismo tiempo de manera aislada. JavaFX incluye la claseParallelTransitionque te permite juntar varias animaciones en un solo grupo que comienza y termina al unísono. Ideal si debes mover un botón y girar un cuadro exactamente al mismo tiempo. Interpolator.SPLINE: EnapplySlideUpTransitionusamos una función de curva cúbica de Bezier. Esto altera drásticamente cómo la animación se siente porque podemos hacer que empiece ultra-rápido y frene lentamente, produciendo una sensación de masa y gravedad moderna similar a las cortinas inferiores de iOS.- Liberar Nodos (Memory Leaks): El paso más clave de todos los métodos descritos siempre, siempre es
mainContainer.getChildren().remove(oldScreen). Mantener "Vistas Ocultas" o tiradas fuera de la pantalla no recogidas afectará gravemente el consumo de RAM de tu programa.
¡A jugar con tus vistas!