In this tutorial, we will explore how we can add a bit of flair to a website by animating elements onto the page based on their viewport visibility. We will do so with the help of Animista - a CSS animations on-demand library and Scrollama - a lightweight JavaScript library for scrollytelling based on Intersection Observer API.
Prerequisites:
- Basic knowledge of HTML
- Basic knowledge of CSS/SCSS
- Basic knowledge of JavaScript
- Basic knowledge of terminal (node, npm)
We won't be focusing on the design aspect, so go ahead and download this free Agency bootstrap theme. It looks nice, it is responsive and will make for an excellent starting point. The project runs on Gulp so make sure Node.js is installed on your development machine. It is already set up with Sass and Browsersync which will refresh our browser in the background whenever we make any changes in the code.
Once you have downloaded the theme, fire up the terminal, navigate to the project root folder, install the dependencies and run the project by running the following commands in the terminal:
$~ npm install
$~ npm start
If everything went well you should be looking at the theme on your machine's default browser. Next, go ahead and open the theme folder in your editor of choice (e.g. VS Code).
Structuring the Content
Before we proceed, let's stop for a moment and think about our page HTML structure.
We will be grouping all animated elements by the animation used. Basically, we will use some kind of a wrapper element where we can define the animation that is going to be applied to its children. Let's give this wrapper element the animista
class.
Furthermore, we will be animating only some of the children elements, so we need another CSS class for this purpose. We can use that same class for controlling the animation delay as well. In our case, we will be using anim-delay-{number}
class. By doing so, we gain a certain degree of control over animation delay values in a simple, declarative manner.
Finally, we will be triggering our animations by adding the appear
class to animista
wrapper. If this sounds confusing โ don't worry, it will make more sense later on.
Laying the groundwork
Let's begin by creating animista.scss
file inside the scss
folder. We will write all of our animation related CSS code here. We want all animated elements to be initially hidden. This is where the CSS attribute selector comes in handy, as it allows us to target all elements that have class attribute value containing anim-delay-
. Let's write our SCSS code like this:
// Our wrapper element
.animista {
// Hide all animated elements
[class*="anim-delay-"] {
opacity: 0;
}
}
Next, open up the agency.scss
file in the scss
folder and import the animista.scss
file that we have just created:
// ...
// Animista
@import "animista.scss";
Time to test if everything works. Let's assign our newly created classes to an element on our page. Open the main index.html
file in the project root folder and change the Services section heading and subheading like so:
...
<!-- The following layer is the animista wrapper -->
<div class="col-lg-12 text-center animista">
<!-- This is the animated element -->
<h2 class="section-heading text-uppercase anim-delay-0">Services</h2>
<!-- This is the animated element too, only with a delay -->
<h3 class="section-subheading text-muted anim-delay-1">Lorem ipsum dolor sit amet consectetur.</h3>
</div>
...
Excellent, the heading and subheading are now both hidden. Let's try and animate those two elements with simple slide-in animation. Head on over to Animista entrances category and select the 'fade-in-bottom' animation. You can tweak the animation options in the options
panel. Great thing about Animista is that you can grab only the code for the animation you actually plan on using. So once you are happy, click the generate code
button at the top right of the main stage (the round button with the curly braces icon). We don't need the auto-prefixed option so you can turn that off.
Copy the animation keyframes code and paste it into animista.scss
file at the end. You can copy the animation class code too, but we will modify it slightly.
// Our wrapper element
.animista {
// Hide all animated elements
[class*="anim-delay-"] {
opacity: 0;
}
// Appearing animations
&.appear {
// Fade in bottom animation
&.fade-in-bottom [class*="anim-delay-"] {
animation-name: fade-in-bottom 0.6s cubic-bezier(0.230, 1.000, 0.320, 1.000) both;
}
}
}
/* ----------------------------------------------
* Generated by Animista on 2020-1-29 20:13:59
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */
/**
* ----------------------------------------
* animation fade-in-bottom
* ----------------------------------------
*/
@keyframes fade-in-bottom {
0% {
transform: translateY(50px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
Connecting the Pieces
We now have everything set up. Next thing we need to do is include the Scrollama library. Add the following code at the end of the index.html
, right before the closing </body>
element, after all other <script>
tags.
...
<body>
...
<!-- Scrollama and intersection observer polyfill -->
<script src="https://unpkg.com/intersection-observer@0.5.1/intersection-observer.js"></script>
<script src="https://unpkg.com/scrollama@2.1.4/build/scrollama.js"></script>
<!-- Our page scroll animation code -->
<script src="js/scroll-anim.js"></script>
</body>
...
Note that we don't yet have the scroll-anim.js
file, so let's create it now in the project's js
folder and add the following code:
var scroller = scrollama();
scroller
.setup({
step: '.animista',
offset: 0.85
})
.onStepEnter(function(response) {
response.element.classList.add('appear');
});
window.addEventListener('resize', scroller.resize);
Basically, we are telling Scrollama to add the appear
class to all animista
wrapper elements when they scroll into the viewport. The default offset is 0.5
which means the trigger is set to the center of the viewport. We are going to move the threshold further down to 0.85 so that animations get triggered a bit earlier.
Finally, we need to tell our animista
wrapper element to use the newly created animation, like so:
<div class="col-lg-12 text-center animista fade-in-bottom">
...
</div>
Creating Staggered Animations
And now if we scroll down a bit, we see that the heading and subheading both slide in onto the page. However they do so at the same time and, as discussed earlier, we would like to have some control over animation-delay
values. While at it, we can also modify our code so we have some default animation properties and are able to over-ride them should we decide to.
.animista {
// Hide all animated elements
// ...
// Appearing animations
&.appear {
// Default animation options
[class*="anim-delay-"]{
animation-duration: 0.75s;
animation-timing-function: cubic-bezier(0.230, 1.000, 0.320, 1.000);
animation-fill-mode: both;
}
// Fade in bottom animation
&.fade-in-bottom [class*="anim-delay-"] {
animation-name: fade-in-bottom;
}
// Let's add animation delays so we can
// create staggered animation effect
.anim-delay-1 { animation-delay: 0.1s; }
.anim-delay-2 { animation-delay: 0.2s; }
.anim-delay-3 { animation-delay: 0.3s; }
.anim-delay-4 { animation-delay: 0.4s; }
.anim-delay-5 { animation-delay: 0.5s; }
.anim-delay-6 { animation-delay: 0.6s; }
.anim-delay-7 { animation-delay: 0.7s; }
}
}
We are almost there, let's try adding more animated elements inside the Services
section. Note how we are only adding animista
and anim-delay-{number}
classes to create a nice staggered animation effect.
...
<!-- Services -->
<section class="page-section" id="services">
<div class="container">
<div class="row">
<!-- The following layer will is the animista wrapper -->
<div class="col-lg-12 text-center animista fade-in-bottom">
<!-- This is the animated element -->
<h2 class="section-heading text-uppercase anim-delay-0">Services</h2>
<!-- This is the animated element too, only with a delay -->
<h3 class="section-subheading text-muted anim-delay-1">Lorem ipsum dolor sit amet consectetur.</h3>
</div>
</div>
<div class="row text-center">
<div class="col-md-4 animista fade-in-bottom">
<span class="fa-stack fa-4x anim-delay-0">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-shopping-cart fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading anim-delay-1">E-Commerce</h4>
<p class="text-muted anim-delay-2">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima maxime quam architecto quo inventore harum ex magni, dicta impedit.</p>
</div>
<div class="col-md-4 animista fade-in-bottom">
<span class="fa-stack fa-4x anim-delay-2">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-laptop fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading anim-delay-3">Responsive Design</h4>
<p class="text-muted anim-delay-4">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima maxime quam architecto quo inventore harum ex magni, dicta impedit.</p>
</div>
<div class="col-md-4 animista fade-in-bottom">
<span class="fa-stack fa-4x anim-delay-4">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-lock fa-stack-1x fa-inverse"></i>
</span>
<h4 class="service-heading anim-delay-5">Web Security</h4>
<p class="text-muted anim-delay-6">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima maxime quam architecto quo inventore harum ex magni, dicta impedit.</p>
</div>
</div>
</div>
</section>
...
Perfect, this looks much better! Now, how about we try using different animation for portfolio items. Head on over to Animista again, grab only the keyframes code for the 'tilt-in-br' animation and paste it at the end of animista.scss
.
// ...
/* ----------------------------------------------
* Generated by Animista on 2020-1-29 21:14:0
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */
/**
* ----------------------------------------
* animation tilt-in-br
* ----------------------------------------
*/
@keyframes tilt-in-br {
0% {
transform: rotateY(-35deg) rotateX(-20deg) translate(250px, 250px) skew(12deg, 15deg);
opacity: 0;
}
100% {
transform: rotateY(0) rotateX(0deg) translate(0, 0) skew(0deg, 0deg);
opacity: 1;
}
}
Next, add the tilt-in-br
class after the fade-in-bottom
and before the delay definitions:
.animista {
// ...
// Appearing animations
&.appear {
// Default animation options
// ...
// Slide in blurred animation
// ...
// Tilt in BR animation
&.tilt-in-br [class*="anim-delay-"] {
animation-name: tilt-in-br;
}
// Let's add animation delays so we can
// create staggered animation effect
// ...
}
}
We also need to update the Portfolio Grid section in our index.html
:
...
<!-- Portfolio Grid -->
<section class="bg-light page-section" id="portfolio">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center animista tilt-in-br">
<h2 class="section-heading text-uppercase anim-delay-0">Portfolio</h2>
<h3 class="section-subheading text-muted anim-delay-1">Lorem ipsum dolor sit amet consectetur.</h3>
</div>
</div>
<div class="row animista tilt-in-br">
<div class="col-md-4 col-sm-6 portfolio-item anim-delay-0">
<a class="portfolio-link" data-toggle="modal" href="#portfolioModal1">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<i class="fas fa-plus fa-3x"></i>
</div>
</div>
<img class="img-fluid" src="img/portfolio/01-thumbnail.jpg" alt="">
</a>
<div class="portfolio-caption">
<h4>Threads</h4>
<p class="text-muted">Illustration</p>
</div>
</div>
<div class="col-md-4 col-sm-6 portfolio-item anim-delay-1">
<a class="portfolio-link" data-toggle="modal" href="#portfolioModal2">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<i class="fas fa-plus fa-3x"></i>
</div>
</div>
<img class="img-fluid" src="img/portfolio/02-thumbnail.jpg" alt="">
</a>
<div class="portfolio-caption">
<h4>Explore</h4>
<p class="text-muted">Graphic Design</p>
</div>
</div>
<div class="col-md-4 col-sm-6 portfolio-item anim-delay-2">
<a class="portfolio-link" data-toggle="modal" href="#portfolioModal3">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<i class="fas fa-plus fa-3x"></i>
</div>
</div>
<img class="img-fluid" src="img/portfolio/03-thumbnail.jpg" alt="">
</a>
<div class="portfolio-caption">
<h4>Finish</h4>
<p class="text-muted">Identity</p>
</div>
</div>
<div class="col-md-4 col-sm-6 portfolio-item anim-delay-3">
<a class="portfolio-link" data-toggle="modal" href="#portfolioModal4">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<i class="fas fa-plus fa-3x"></i>
</div>
</div>
<img class="img-fluid" src="img/portfolio/04-thumbnail.jpg" alt="">
</a>
<div class="portfolio-caption">
<h4>Lines</h4>
<p class="text-muted">Branding</p>
</div>
</div>
<div class="col-md-4 col-sm-6 portfolio-item anim-delay-4">
<a class="portfolio-link" data-toggle="modal" href="#portfolioModal5">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<i class="fas fa-plus fa-3x"></i>
</div>
</div>
<img class="img-fluid" src="img/portfolio/05-thumbnail.jpg" alt="">
</a>
<div class="portfolio-caption">
<h4>Southwest</h4>
<p class="text-muted">Website Design</p>
</div>
</div>
<div class="col-md-4 col-sm-6 portfolio-item anim-delay-5">
<a class="portfolio-link" data-toggle="modal" href="#portfolioModal6">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<i class="fas fa-plus fa-3x"></i>
</div>
</div>
<img class="img-fluid" src="img/portfolio/06-thumbnail.jpg" alt="">
</a>
<div class="portfolio-caption">
<h4>Window</h4>
<p class="text-muted">Photography</p>
</div>
</div>
</div>
</div>
</section>
...
Cool! Let's add another round of animations. This time we will get the keyframes code for the 'flip-in-hor-top' animation and add it at the end of the animista.scss
.
// ...
/* ----------------------------------------------
* Generated by Animista on 2020-1-29 21:26:34
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */
/**
* ----------------------------------------
* animation flip-in-hor-top
* ----------------------------------------
*/
@keyframes flip-in-hor-top {
0% {
transform: rotateX(-80deg);
opacity: 0;
}
100% {
transform: rotateX(0);
opacity: 1;
}
}
Add the animation class code too:
.animista {
// ...
// Appearing animations
&.appear {
// Default animation options
// ...
// Slide in blurred animation
// ...
// Tilt in BR animation
// ...
// Flip in horizontal top animation
&.flip-in-hor-top [class*="anim-delay-"] {
animation-name: flip-in-hor-top;
}
// Let's add animation delays so we can
// create staggered animation effect
// ...
}
}
Finally, update the About section in our index.html
file (simply add our CSS classes where needed):
...
<!-- About -->
<section class="page-section" id="about">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center animista fade-in-bottom">
<h2 class="section-heading text-uppercase anim-delay-0">About</h2>
<h3 class="section-subheading text-muted anim-delay-1">Lorem ipsum dolor sit amet consectetur.</h3>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<ul class="timeline">
<li class="animista flip-in-hor-top">
<div class="timeline-image anim-delay-0">
<img class="rounded-circle img-fluid" src="img/about/1.jpg" alt="">
</div>
<div class="timeline-panel anim-delay-1">
<div class="timeline-heading">
<h4>2009-2011</h4>
<h4 class="subheading">Our Humble Beginnings</h4>
</div>
<div class="timeline-body">
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt ut voluptatum eius sapiente, totam reiciendis temporibus qui quibusdam, recusandae sit vero unde, sed, incidunt et ea quo dolore laudantium consectetur!</p>
</div>
</div>
</li>
<li class="timeline-inverted animista flip-in-hor-top">
<div class="timeline-image anim-delay-0">
<img class="rounded-circle img-fluid" src="img/about/2.jpg" alt="">
</div>
<div class="timeline-panel anim-delay-1">
<div class="timeline-heading">
<h4>March 2011</h4>
<h4 class="subheading">An Agency is Born</h4>
</div>
<div class="timeline-body">
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt ut voluptatum eius sapiente, totam reiciendis temporibus qui quibusdam, recusandae sit vero unde, sed, incidunt et ea quo dolore laudantium consectetur!</p>
</div>
</div>
</li>
<li class="animista flip-in-hor-top">
<div class="timeline-image anim-delay-0">
<img class="rounded-circle img-fluid" src="img/about/3.jpg" alt="">
</div>
<div class="timeline-panel anim-delay-1">
<div class="timeline-heading">
<h4>December 2012</h4>
<h4 class="subheading">Transition to Full Service</h4>
</div>
<div class="timeline-body">
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt ut voluptatum eius sapiente, totam reiciendis temporibus qui quibusdam, recusandae sit vero unde, sed, incidunt et ea quo dolore laudantium consectetur!</p>
</div>
</div>
</li>
<li class="timeline-inverted animista flip-in-hor-top">
<div class="timeline-image anim-delay-0">
<img class="rounded-circle img-fluid" src="img/about/4.jpg" alt="">
</div>
<div class="timeline-panel anim-delay-1">
<div class="timeline-heading">
<h4>July 2014</h4>
<h4 class="subheading">Phase Two Expansion</h4>
</div>
<div class="timeline-body">
<p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt ut voluptatum eius sapiente, totam reiciendis temporibus qui quibusdam, recusandae sit vero unde, sed, incidunt et ea quo dolore laudantium consectetur!</p>
</div>
</div>
</li>
<li class="timeline-inverted animista flip-in-hor-top">
<div class="timeline-image anim-delay-0">
<h4>Be Part
<br>Of Our
<br>Story!</h4>
</div>
</li>
</ul>
</div>
</div>
</div>
</section>
...
From this point on, you can add just about any animation from the Animista entrances category without much effort. The JS code stays the same, only the HTML and the CSS code need updating.
Let's demonstrate this by adding a final round of animations to the team section. This time however, let's reuse the 'fade-in-bottom' animation that we already have and only update the Team section in our index.html
like so:
...
<!-- Team -->
<section class="bg-light page-section" id="team">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center animista fade-in-bottom">
<h2 class="section-heading text-uppercase anim-delay-0">Our Amazing Team</h2>
<h3 class="section-subheading text-muted anim-delay-1">Lorem ipsum dolor sit amet consectetur.</h3>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<div class="team-member animista fade-in-bottom">
<img class="mx-auto rounded-circle anim-delay-0" src="img/team/1.jpg" alt="">
<h4 class="anim-delay-1">Kay Garland</h4 >
<p class="text-muted anim-delay-2">Lead Designer</p>
<ul class="list-inline social-buttons">
<li class="list-inline-item anim-delay-3">
<a href="#">
<i class="fab fa-twitter"></i>
</a>
</li>
<li class="list-inline-item anim-delay-4">
<a href="#">
<i class="fab fa-facebook-f"></i>
</a>
</li>
<li class="list-inline-item anim-delay-5">
<a href="#">
<i class="fab fa-linkedin-in"></i>
</a>
</li>
</ul>
</div>
</div>
<div class="col-sm-4">
<div class="team-member animista fade-in-bottom">
<img class="mx-auto rounded-circle anim-delay-1" src="img/team/2.jpg" alt="">
<h4 class="anim-delay-2">Larry Parker</h4>
<p class="text-muted anim-delay-3">Lead Marketer</p>
<ul class="list-inline social-buttons">
<li class="list-inline-item anim-delay-4">
<a href="#">
<i class="fab fa-twitter"></i>
</a>
</li>
<li class="list-inline-item anim-delay-5">
<a href="#">
<i class="fab fa-facebook-f"></i>
</a>
</li>
<li class="list-inline-item anim-delay-6">
<a href="#">
<i class="fab fa-linkedin-in"></i>
</a>
</li>
</ul>
</div>
</div>
<div class="col-sm-4">
<div class="team-member animista fade-in-bottom">
<img class="mx-auto rounded-circle anim-delay-2" src="img/team/3.jpg" alt="">
<h4 class="anim-delay-3">Diana Pertersen</h4>
<p class="text-muted anim-delay-4">Lead Developer</p>
<ul class="list-inline social-buttons">
<li class="list-inline-item anim-delay-5">
<a href="#">
<i class="fab fa-twitter"></i>
</a>
</li>
<li class="list-inline-item anim-delay-6">
<a href="#">
<i class="fab fa-facebook-f"></i>
</a>
</li>
<li class="list-inline-item anim-delay-7">
<a href="#">
<i class="fab fa-linkedin-in"></i>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="row animista fade-in-bottom">
<div class="col-lg-8 mx-auto text-center anim-delay-0">
<p class="large text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut eaque, laboriosam veritatis, quos non quis ad perspiciatis, totam corporis ea, alias ut unde.</p>
</div>
</div>
</div>
</section>
...
Closing Thoughts
Congratulations, you have made it to the end of the tutorial! Hopefully it gives you an idea on how to liven up your website with neat scroll-based animation effects. A bit of CSS, HTML, and very little JS knowledge gives you lots of creative possibilities. You can try experimenting with different entrance animations from Animista. Careful though, once you get the hang of it, it is fairly easy to overdo it. As ever โ use your own judgment.
Resources
Further Reading
If you are just diving into CSS animation, here are a few places that may help you gain better insight and learn basics:
Top comments (0)