CSS Transition Classes

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Right now, the search preview results are hiding and showing correctly... but there are no CSS transitions yet. Why not?

Because... adding the transition is... actually up to us! By defining it in CSS.

Adding the Transition Classes

Go back to assets/styles/app.css and head to the bottom. I'm going to paste in three new CSS rules.

... lines 1 - 154
.fade-enter-active, .fade-leave-active {
transition: opacity 2000ms;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.fade-enter-to, .fade-leave-from {
opacity: 1;
}

This 2000 milliseconds here is probably too slow... but it will make it easy to see how the feature works.

Before we talk about what's going on... it should already work! Move over and refresh the page. Type and... beautiful! It faded in! When I click off, it fades out! Amazing!

The Class Lifecycle of Showing an Element

But this... deserves some explanation. Back in the controller, we defined six classes that useTransition should use. The option keys come from useTransition, but I totally made up the class names on the right. Though... they make sense! Because we're going to create a "fade" transition, each class starts with fade and then matches the option name it relates to.

Anyways, move back to the CSS file where we define the style for these classes. Here's the magic. When we call this.enter(), useTransition immediately adds the fade-enter-active class. That doesn't cause a transition, but it establishes that, if the opacity changes, we want it to transition over 2000 milliseconds.

One frame later, it adds another class - fade-enter-from - and removes the d-none class. The result is that the element is now "shown"... but with an opacity set to 0. One frame after that, it removes fade-enter-from but adds fade-enter-to. Thanks to this, our browser starts transitioning the opacity from 0 to 1! Awesome!

So... what happens next? useTransition is smart. It detects that a transition is currently happening and will take 2000 milliseconds. So... it waits. Yup! It literally waits for two seconds for the transition to finish. And then it removes both fade-enter-to and fade-enter-active because its work is done. The element faded in and is now fully visible.

Isn't that amazing? stimulus-use didn't invent this idea: you'll see it in other libraries like Vue. But it is so handy.

The Class Lifecycle of Hiding an Element

In our controller, when we call this.leave() to hide the element, a similar process happens. First, fade-leave-active is added to the element, which establishes that we want a 2000 millisecond transition on opacity. Next, it adds fade-leave-from, which makes sure that the opacity is definitely set to 1, which it already was. One frame later, it removes fade-leave-from and replaces it with fade-leave-to. The result is that the element starts a 2 second opacity transition from 1 to 0. Two seconds later, after the transition has finished, useTransition adds the d-none class and removes both fade-leave-to and fade-leave-active. The element is now fully hidden.

How cool is that? Learning how this works is fun. But the result is even better. And in your day-to-day use, it's really simple. Now that we have these three CSS rules defined, we could reuse this exact useTransition in any other controller to add, fade in and fade out functionality to it. Heck, you could even create a re-usable JavaScript module that sets up the behavior and these options automatically for you!

Next: there's one last thing I want to talk about. Stimulus is used by a lot of people, including the Ruby on Rails world. And so, it turns out that there are a bunch of pre-made Stimulus controllers that you can download and use directly in your app! Yay! Let's install one and learn how to register it with our Stimulus application.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=7.4.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
        "doctrine/annotations": "^1.0", // 1.11.1
        "doctrine/doctrine-bundle": "^2.2", // 2.2.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
        "doctrine/orm": "^2.8", // 2.8.1
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^5.6", // v5.6.1
        "symfony/asset": "5.2.*", // v5.2.3
        "symfony/console": "5.2.*", // v5.2.3
        "symfony/dotenv": "5.2.*", // v5.2.3
        "symfony/flex": "^1.3.1", // v1.12.1
        "symfony/form": "5.2.*", // v5.2.3
        "symfony/framework-bundle": "5.2.*", // v5.2.3
        "symfony/property-access": "5.2.*", // v5.2.3
        "symfony/property-info": "5.2.*", // v5.2.3
        "symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
        "symfony/security-bundle": "5.2.*", // v5.2.3
        "symfony/serializer": "5.2.*", // v5.2.3
        "symfony/twig-bundle": "5.2.*", // v5.2.3
        "symfony/ux-chartjs": "^1.1", // v1.2.0
        "symfony/validator": "5.2.*", // v5.2.3
        "symfony/webpack-encore-bundle": "^1.9", // v1.11.1
        "symfony/yaml": "5.2.*", // v5.2.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.2.1
        "twig/intl-extra": "^3.2", // v3.2.1
        "twig/twig": "^2.12|^3.0" // v3.2.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
        "symfony/debug-bundle": "^5.2", // v5.2.3
        "symfony/maker-bundle": "^1.27", // v1.30.0
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/stopwatch": "^5.2", // v5.2.3
        "symfony/var-dumper": "^5.2", // v5.2.3
        "symfony/web-profiler-bundle": "^5.2" // v5.2.3
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@babel/preset-react": "^7.0.0", // 7.12.13
        "@popperjs/core": "^2.9.1", // 2.9.1
        "@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
        "@symfony/webpack-encore": "^1.0.0", // 1.0.4
        "bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
        "core-js": "^3.0.0", // 3.8.3
        "jquery": "^3.6.0", // 3.6.0
        "react": "^17.0.1", // 17.0.1
        "react-dom": "^17.0.1", // 17.0.1
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "stimulus": "^2.0.0", // 2.0.0
        "stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
        "stimulus-use": "^0.24.0-1", // 0.24.0-1
        "sweetalert2": "^10.13.0", // 10.14.0
        "webpack-bundle-analyzer": "^4.4.0", // 4.4.0
        "webpack-notifier": "^1.6.0" // 1.13.0
    }
}