Can CSS in Javafx Be Used for Animations

How to make an animation with CSS in JavaFX?

Is it possible to change style gradually, for example make some transition?

Yes.

You will need to use setStyle rather than style classes because classes are going to be static things defined in a css. There is no direct support in JavaFX css for animation. You need to perform the animation steps in Java code to modify the css style.

I'd only really recommend this approach when you want to use css to perform the transition because there are no corresponding Java APIs easily available.

To handle the animation, you can use the standard javafx animation Timeline to manipulate properties which the css style property depends on.

For example, bind your style property to string. Then vary the component you want to change (in this case the colorStringProperty) in a timeline.

warningButton.styleProperty().bind(
new SimpleStringProperty("-fx-base: ")
.concat(colorStringProperty)
.concat(";")
.concat("-fx-font-size: 20px;")
);

Here is a sample which flashes a button using css with a gradual color transition of it's base color from gray to red when pressed.

warninggray
warningred

import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

/** Shows how you can modify css styles dynamically using a timeline. */
public class Warning extends Application {

private static final String BACKGROUND = "http://bobgreiner.tripod.com/1cc2ce10.jpg";

@Override
public void start(Stage stage) throws Exception{
final ObjectProperty<Color> warningColor = new SimpleObjectProperty<>(Color.GRAY);
final StringProperty colorStringProperty = createWarningColorStringProperty(warningColor);

StackPane layout = new StackPane();
layout.getChildren().addAll(
new ImageView(new Image(BACKGROUND)),
createWarningButton(
warningColor,
colorStringProperty
)
);
stage.setScene(new Scene(layout));
stage.show();
}

private StringProperty createWarningColorStringProperty(final ObjectProperty<Color> warningColor) {
final StringProperty colorStringProperty = new SimpleStringProperty();
setColorStringFromColor(colorStringProperty, warningColor);
warningColor.addListener(new ChangeListener<Color>() {
@Override
public void changed(ObservableValue<? extends Color> observableValue, Color oldColor, Color newColor) {
setColorStringFromColor(colorStringProperty, warningColor);
}
});

return colorStringProperty;
}

private Button createWarningButton(final ObjectProperty<Color> warningColor, StringProperty colorStringProperty) {
final Button warningButton = new Button("Warning! Warning!");
warningButton.styleProperty().bind(
new SimpleStringProperty("-fx-base: ")
.concat(colorStringProperty)
.concat(";")
.concat("-fx-font-size: 20px;")
);

warningButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent actionEvent) {
Timeline flash = new Timeline(
new KeyFrame(Duration.seconds(0), new KeyValue(warningColor, Color.GRAY, Interpolator.LINEAR)),
new KeyFrame(Duration.seconds(0.25), new KeyValue(warningColor, Color.GRAY, Interpolator.LINEAR)),
new KeyFrame(Duration.seconds(1), new KeyValue(warningColor, Color.RED, Interpolator.LINEAR)),
new KeyFrame(Duration.seconds(1.25), new KeyValue(warningColor, Color.RED, Interpolator.LINEAR))
);
flash.setCycleCount(6);
flash.setAutoReverse(true);
flash.play();
}
});

return warningButton;
}

private void setColorStringFromColor(StringProperty colorStringProperty, ObjectProperty<Color> color) {
colorStringProperty.set(
"rgba("
+ ((int) (color.get().getRed() * 255)) + ","
+ ((int) (color.get().getGreen() * 255)) + ","
+ ((int) (color.get().getBlue() * 255)) + ","
+ color.get().getOpacity() +
")"
);
}

public static void main(String[] args) {
launch(args);
}
}

how to animate a text in javafx using css?

JavaFX 2.2 and JavaFX 8 have no support for animating text using css.

Your IDE is being misleading in suggesting the animation-timing-function CSS attribute for use in a CSS file to be used only by JavaFX. Most HTML type CSS attributes are unsupported by JavaFX. JavaFX instead has its own CSS attribute definitions, usually prefixed by -fx-. The set of supported CSS attributes for JavaFX is defined in the JavaFX CSS Reference Guide.

Note that you can load HTML into a JavaFX application via WebView and that HTML can make use of HTML CSS to perform animations on the HTML document displayed in the WebView (not the JavaFX SceneGraph), but that is probably not what you wish to do.

Is there any way to implement this CSS animation in JavaFX?

This simple application does pretty much the same as you showed:

package example;

import javafx.animation.Animation;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

public static void main(String[] args) {
launch(Main.class, args);
}

@Override
public void start(Stage stage) throws Exception {
final Pane root = new Pane();
// this color will be used for the Text-Color (not hovered), Border and the Background-Color (on hover)
final Paint color = Color.web("#B17461");

// basic stuff: text, font and text-color
final Label label = new Label("NEXT");
label.setFont(Font.font("Arial", 50.0));
label.setTextFill(color);
label.setTextAlignment(TextAlignment.CENTER);
// same padding as you had
label.setPadding(new Insets(15, 70, 15, 70));
// border with the same settings as you had (5px solid)
label.setBorder(new Border(new BorderStroke(color, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(5))));

// maybe there's a better way to do this, but this one is the one I know. Simple Transition-Animation with a cycle of .5s
// the interpolate-Method will be called by JavaFX on every frame with the current "progress" (from 0.0 to 1.0), so we simply calculate the background size in there
final Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(500L));
}

@Override
protected void interpolate(double progress) {
final double total = label.getWidth() / 2.0;
final double current = (1.0 - progress) * total;

label.setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, new Insets(0.0, current, 0.0, current))));
}
};

// "hover": change text color and start the animation
label.setOnMouseEntered(event -> {
label.setTextFill(Color.web("#fff"));
animation.playFromStart();
});

// "unhover": stop animation, reset background and text color
label.setOnMouseExited(event -> {
animation.stop();
label.setBackground(null);
label.setTextFill(color);
});


root.getChildren().add(label);

stage.setScene(new Scene(root));
stage.sizeToScene();
stage.show();
}
}

Basically I just did everything you have in your css programmatically on the label.
For the animation, I used a simple Transition combined with the Insets value of the labels background. The Insets describe how much space the background should not fill, it's like a padding but only for the background.

Javafx button hover effect

Did you try specifying a CSS stylesheet as explained in:

  • Button Hover and pressed effect CSS Javafx

CSS Stylesheet file (using IDs)

File name: ButtonStyles.css

#my-button {
-fx-border-width: 2;
-fx-border-color: #FFE4C4;
-fx-background-color: #FFDAB9; /* 鼠标移出 */
cursor: pointer; // seems like Cursor.HAND
}

/* I want MouseMoved button background-color effect 鼠标移入 */
#my-button:hover {
-fx-background-color: #FFC125;
}

#my-button:pressed {
-fx-background-color: #FFDAB9; /* maybe some other color */
}

Then those styles are applied to elements with the id of my-button.
So add the styles to the button(s) and set their id.

Button button1 = new Button("new");
// set id and add stylesheet
Button button2 = new Button("open");
// set id and add stylesheet
Button button3 = new Button("save");
// set id and add stylesheet
Button button4 = new Button("settings");
// set id and add stylesheet
Button button5 = new Button("help");
// set id and add stylesheet

Button button6 = new Button("about");
// set id and add stylesheet
button6.setId("my-button");
button6.getStylesheets().add(getClass().getResource("ButtonStyles.css").toExternalForm());

The buttons 1..5 are left to you.

Explained

The cursor is set to a pointing finger, with css-property cursor.
The styles have been copied from your Java-code.
Another guide is: styling JavaFx buttons with CSS.

You may also improve and create a List of buttons, so you can add the style to all buttons in and a loop.

Simplify using a Scene

Like explained in:

  • JavaFX - CSS tutorial
  • JavaFX Button Hover

So you can overwrite the CSS-class .button (used in JavaFX to style buttons) instead of single IDs. So replace id-selector #my-button by .button in the CSS file.

new CSS Stylesheet file (using classes)

File name: button.css

.button {
-fx-border-width: 2;
-fx-border-color: #FFE4C4;
-fx-background-color: #FFDAB9; /* 鼠标移出 */
cursor: pointer; // seems like Cursor.HAND
}

/* I want MouseMoved button background-color effect 鼠标移入 */
.button:hover {
-fx-background-color: #FFC125;
}

.button:pressed {
-fx-background-color: #FFDAB9; /* maybe some other color */
}

Adding a style to the scene

Then define the scene (optionally restricted to appropriate dimension), add the stylesheet to it, set it to your container and it will apply to all buttons inside the container.

You had already defined and set your scene to the primaryStage, great So you need only to add the CSS stylesheet.

        Scene scene = new Scene(borderPane);
scene.getStylesheets().add(this.getClass().getResource("button.css").toExternalForm()); // add the CSS stylesheet

primaryStage.setWidth(440);
primaryStage.setHeight(300);
primaryStage.setResizable(false);
primaryStage.setScene(scene);

primaryStage.show();

Transition of background-color

CSS Transitions (for testing)

They are explained in the tutorial CSS Fundamentals: CSS3 Transitions.
You can also test them first in regular HTML like the one below:

/* NOTE: removed the "-fx-" prefixes from properties, to make it work with HTML in browser

.button {
border-width: 2;
border-color: #FFE4C4; /* color-name Bisque */
background-color: #FFDAB9; /* 鼠标移出 */
cursor: pointer; // seems like Cursor.HAND
}

.button:hover {
background-color: #FFC125; /* color-name Goldenrod */
/* linear transition gradually fading to the color for 1 second */
-webkit-transition: background-color 1s linear;
-ms-transition: background-color 1000ms linear;
transition: background-color 1s linear;
}
<button class="button">Button with Transition</button> 

JavaFX animation on button hover

There are several ways you can accomplish this, and even let it work on Scene Builder.

I'd start by subclassing the button skin, where you can add the event handlers with your animations. In this case let's have a fade in/fade out animation:

MyButtonSkin.java

public class MyButtonSkin extends ButtonSkin {

public MyButtonSkin(Button control) {
super(control);

final FadeTransition fadeIn = new FadeTransition(Duration.millis(100));
fadeIn.setNode(control);
fadeIn.setToValue(1);
control.setOnMouseEntered(e -> fadeIn.playFromStart());

final FadeTransition fadeOut = new FadeTransition(Duration.millis(100));
fadeOut.setNode(control);
fadeOut.setToValue(0.5);
control.setOnMouseExited(e -> fadeOut.playFromStart());

control.setOpacity(0.5);
}

}

Now you can apply this custom skin to a regular JavaFX Button:

One way, by code:

MyApplication.java

@Override
public void start(Stage primaryStage) throws IOException {
Button btn = new Button("Button");
btn.setSkin(new MyButtonSkin(btn));
StackPane root = new StackPane(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}

Another option is via css, let's add a style.css file:

style.css

.button {
-fx-skin: 'your.package.name.MyButtonSkin';
}

and now:

MyApplication.java

@Override
public void start(Stage primaryStage) throws IOException {
Button btn = new Button("Button");
StackPane root = new StackPane(btn);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}

In both cases, you will have the button animations working when you run your application:

button gif

Scene Builder

If you add now a regular JavaFX Button to an FXML file:

FXML.fxml

<AnchorPane id="AnchorPane" prefWidth="300" prefHeight="250" stylesheets="@style.css" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="your.package.name.FXMLController">
<children>
<Button layoutX="100" layoutY="100" text="Button" />
</children>
</AnchorPane>

Scene Builder won't know about the custom skin (whether it was applied by code or via css), and it won't display or preview it.

In this case, this solution is good enough, since after all Scene Builder is just a designer tool, and the animation will work when you run your application.

But if you really want to preview the animation from Scene Builder, you still can do it, but for this you need to:

  • Subclass Button, and
  • Import this class (jar) into Scene Builder

So let's create MyButton class:

MyButton.java

public class MyButton extends Button {

public MyButton() {
super();
}

public MyButton(String text) {
super(text);
}

@Override
protected Skin<?> createDefaultSkin() {
return new MyButtonSkin(this);
}

}

Now build your project. At least should contain MyButton and MyButtonSkin), but it can contain a demo application class to test it:

@Override
public void start(Stage primaryStage) throws IOException {
MyButton btn = new MyButton("Button");
StackPane root = new StackPane(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}

Import the project into Scene Builder:

Open Scene Builder, and select JAR/FXML Manager -> Add Library/FXML from file system. Locate your jar, and click Import Component:

import custom jar

Now you'll be able to drag a MyButton control from the Custom panel to the top left.

FXML.fxml

<?import javafx.scene.layout.AnchorPane?>
<?import your.package.name.MyButton?>

<AnchorPane prefHeight="250.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<MyButton layoutX="100" layoutY="100" text="Button" />
</children>
</AnchorPane>

Finally, when you click Preview -> Show Preview in Window, you will be able to see the animation.

Scene Builder and preview

Button border-radius transition in JavaFX/TornadoFX

JavaFX does not allow you to create any kind of animation via CSS and as far as I can tell there's no way of applying a animation even with TornadoFX. (Note tough that I just started with TornadoFX today, so I may be wrong.)

The only way of getting an animation in via stylesheet would be to use set the skin to a custom skin implementing the corner animation. While you could make the skin provide CSS property controling the roundedness of the corners.

Usually you'd extend Button adding a property, but in this case I simply store it in the properties map.

Skin to assign to the button

package org.example

import com.sun.javafx.scene.control.skin.ButtonSkin

import javafx.beans.binding.Bindings
import javafx.beans.binding.DoubleBinding
import javafx.beans.property.DoubleProperty
import javafx.beans.property.SimpleDoubleProperty

import java.util.function.Function
import javafx.css.SimpleStyleableBooleanProperty
import javafx.css.CssMetaData
import javafx.css.StyleablePropertyFactory
import javafx.css.Styleable
import javafx.css.StyleableProperty
import javafx.scene.control.Button
import javafx.scene.shape.ArcTo
import javafx.scene.shape.ClosePath
import javafx.scene.shape.HLineTo
import javafx.scene.shape.MoveTo
import javafx.scene.shape.Path
import javafx.scene.shape.VLineTo

import tornadofx.*

class AnimatedButtonSkin(button: Button) : ButtonSkin(button) {

companion object {
@JvmField
val CSS_ROUNDED_KEY = "org.example.AnimatedButtonSkin.rounded"
@JvmField
val CSS_ROUNDED_METADATA: CssMetaData<Button, Boolean>
@JvmField
val FACTORY = StyleablePropertyFactory<Button>(javafx.scene.control.SkinBase.getClassCssMetaData())

init {
CSS_ROUNDED_METADATA = FACTORY.createBooleanCssMetaData(
"-fx-rounded",
object : Function<Button, StyleableProperty<kotlin.Boolean>> {

override fun apply(b: Button): StyleableProperty<Boolean> {
// property stored in properties to avoid extending button
val v = b.getProperties().get(CSS_ROUNDED_KEY)
return v as StyleableProperty<Boolean>
}
},
true
)
}
}

override fun dispose() {
// get rid of the property and the shape
val b = getSkinnable()
b.getProperties().remove(CSS_ROUNDED_KEY)
b.setShape(null)

super.dispose()
}

private fun createArc(
cornerSizeH: DoubleBinding,
cornerSizeV: DoubleBinding,
invertX: Boolean,
invertY: Boolean
): ArcTo {
return ArcTo().apply {
setAbsolute(false)
setSweepFlag(true)
radiusXProperty().bind(cornerSizeH)
radiusYProperty().bind(cornerSizeV)
xProperty().bind(if (invertX) cornerSizeH.negate() else cornerSizeH)
yProperty().bind(if (invertY) cornerSizeV.negate() else cornerSizeV)
}
}

override fun getCssMetaData(): List<CssMetaData<out Styleable, *>>? {
return FACTORY.getCssMetaData()
}

init {
val prop = SimpleStyleableBooleanProperty(CSS_ROUNDED_METADATA, true)
button.getProperties().put(CSS_ROUNDED_KEY, prop)

// relative part of width/height that is rounded
// size for single corner:
// 0 -> rectangular button
// 0.5 -> circular button
val cornerSize = SimpleDoubleProperty(.5)

val w = button.widthProperty()
val h = button.heightProperty()

// bindings for horizontal measures
val cornerHSize = w.multiply(cornerSize)
val doubleHCornerSize = cornerHSize.multiply(2.0);

// measures for vertical measures
val cornerVSize = h.multiply(cornerSize)
val doubleVCornerSize = cornerVSize.multiply(2.0);

// lower part of the top-left corner
val start = MoveTo().apply {
yProperty().bind(cornerSize);
}

// straight path of top
val top = HLineTo().apply {
setAbsolute(false)
xProperty().bind(w.subtract(doubleHCornerSize))
}

// straight part of the right
var right = VLineTo().apply {
setAbsolute(false)
yProperty().bind(h.subtract(doubleVCornerSize))
}

// straight part of the bottom
val bottom = HLineTo().apply {
setAbsolute(false)
xProperty().bind(top.xProperty().negate())
}

// assemble the parts
val shape = Path(
start,
createArc(cornerHSize, cornerVSize, false, true), top,
createArc(cornerHSize, cornerVSize, false, false), right,
createArc(cornerHSize, cornerVSize, true, false), bottom,
createArc(cornerHSize, cornerVSize, true, true), ClosePath()
)
button.shape = shape

// animate open/close on change of stylable property
prop.addListener({ _, _, new -> cornerSize.animate(endValue = if (new) .5 else .2, duration = .2.seconds) })
}

}

Style

class NavigatorButtonViewCss: Stylesheet() {
companion object {
val face by cssclass()

val rounded by cssproperty<Boolean>("-fx-rounded")

val buttonBackgroundColor = c("#36393F")
val buttonHoverBackgroundColor = c("#7289DA")
val textColor = c("#C8C9CB")
}

init {
indicator {
prefWidth = 10.px
}
face {
prefWidth = 50.px
prefHeight = 50.px

backgroundColor += buttonBackgroundColor

textFill = textColor

skin = AnimatedButtonSkin::class

and(hover) {
rounded.value = false // update corners
backgroundColor += buttonHoverBackgroundColor
}
}
}
}

JavaFX 2.X - Animated background and animated controls

after much struggle, I and some users of the Oracle community could resolve this issue. I see no need to repeat here all the resolution made ​​by us, so I'll post the link so you can access the solution of the problem. I hope this benefits us all. Thanks for your attention anyway.

https://community.oracle.com/thread/2620500



Related Topics



Leave a reply



Submit