Esercizio 12: Advanced CSS + JS

Keywords: DOM, Math.random, arrow functions, const, animations, svg, intervals, positioning

Esercizio precedente Esercizio successivo

Let's review together:

This lesson will try to wrap up many of the concepts we have seen until now, taking each of them a step further.

CSS concepts:

  • CSS Variables (root)
  • The Display property
  • Gradients and moving Backgrounds
  • Combining CSS and JS for complex svg animation

:root and CSS variables

The :root pseudo-class matches the root element of a document; :root is mostly used to store CSS variables.

Variables

Declaring a custom property is done using a custom property name that begins with a double hyphen (--), and a property value that can be any valid CSS value.

:root {
--main-bg-color: brown;
}
.main {
    background-color: var(--main-bg-color);
}

Background

  • background-color: bg color of an element. Can take any color value.
  • background-image: specifies one or more background images for an element. The images are drawn in layers, with the first image specified being the bottommost layer (separated by commas). Images can be specified using the url(), linear-gradient() or radial-gradient().
  • background-repeat: how background images are repeated (tiled) within an element. Possible values: repeat, repeat-x, repeat-y, or no-repeat.
  • background-attachment: like position:fixed; but for a background images. Values: scroll, fixed, or local.
  • background-position: sets the initial position of a background image within an element. Values: 'left', 'center', 'right', 'top', 'bottom'; lengths (e.g., '50px');percentages (e.g., '50%').
  • background-size: size of the background image(s). Values: auto, cover, contain, or a specific size
  • background-clip: Specifies the painting area of the background. It can take one of the following values: border-box, padding-box, or content-box. The border-box value includes the background in the area within the border, padding-box includes the background only within the padding area, and content-box includes the background only within the content area.

Read More

What is Webkit?

-webkit is a CSS vendor prefix used for properties and features that are specific to the WebKit browser engine. Vendor prefixes are added to experimental or non-standard CSS properties and features by browser vendors to allow developers to use and test them before they become part of the official CSS specification.

Display

Each element in HTML has a native display, some follow the normal flow of the document, some are inline elements (e.g. span, i, em, etc) and so on.

Display Property Usage
Block display: block; Any block element generates a new line.
Inline display: inline; It takes space as much as the content requires; they don't generate a new line.
Inline-block display: inline-block; Behaves like a block but does not generate a new line.
Flex display: inline-flex/flex; Display properties related to flexbox;

When using CSS, properties usually apply regardless of display type. A few exceptions:

  • width and height: Inline elements do not respect the width and height properties.
  • margin and padding: Inline elements respect margin-left, margin-right, padding-left, and padding-right, but they do not respect margin-top, margin-bottom, padding-top, and padding-bottom.
  • absolute positioning with inline elements doesn't work often as intended (depends on context).

In general, if you need to apply specific dimensions, positioning, or other layout-related properties to an element, it's best to use display: block;, display: inline-block;

Gradients

Gradients in CSS are a way to create smooth color transitions between two or more colors within a single element's background or other properties that accept images. Gradients are generated using the linear-gradient() or radial-gradient() functions, which create an image consisting of a continuous blend of colors.

Linear gradients: A linear gradient transitions colors along a straight line. The linear-gradient() function takes multiple arguments: the direction (angle or keywords) and two or more color stops. The direction defines the angle at which the gradient progresses, and the color stops determine the colors and their relative positions within the gradient.

In this example, the gradient starts at the bottom left corner of the element (45 degrees) and transitions from red to yellow and then to blue.

.background {
background-image: linear-gradient(45deg, red, yellow, blue);
}

Radial gradients: A radial gradient transitions colors in a circular or elliptical pattern, radiating from a center point. The radial-gradient() function takes multiple arguments: the shape and size (optional), the position (optional), and two or more color stops. The shape can be circle or ellipse, and the size can be defined using keywords or explicit dimensions. The position determines the center of the gradient.

.background {
background-image: radial-gradient(circle at center, red, yellow, blue);
}

Animations

CSS animations can start as very simple and become very complex depending on the design's needs.

Animations can be as trivial as making some image bigger on hover to change completely the appearance of a page.

The main elements to create an animation with are:

  • animation: animation-name time behaviour
  • @keyframes animation-name {from {} to{}}

Examples

Animating backgrounds

.example-1 {
background-color: red;
animation: example 4s infinite;
}

@keyframes example {
from {background-color: red;}
to {background-color: yellow;}
}

In this case, the animation changes the color from red to yellow. The animation property to the div of class example-1 specifies that the animation lasts 4s and it must be repeated infinitely. You can also see that CSS fills in any movement or color in between by itself.

.example-2 {
background-color: red;
animation: example-2 4s infinite;
}
@keyframes example-2 {
0% {background-color: red;}
50% {background-color: yellow;}
100% {background-color: red;}
}

In this second example, by using percentages, we make the bg-color go from red to yellow back to red, to have a smoother transition between the two colors.

Animating Images

Apart from color, we can also animate images. In this context, it is often better to use SVGs. This is a good explanation on how this works.

In this example, transform-origin: center; fixes the animation to its center point; if we try to inspect the element, we can see that it is actually a box, which moves along its center axis like a wheel. The animation also states that it must have a linear behaviour, and the animation must go on forever.

The animation name, rotate-circle, has a behavior that uses the rotate function (native) which starts from 360dg and goes on until 0. By using a simple Javascript function that changes the style innerHTML with the opposite starting points, we can go from clockwise to counter-clockwise.

Starbucks Coffee Logo Cerco un centro di gravità permanent che non mi faccia mai cambiare idea
.rotating-circle {
transform-origin: center;
animation: rotate-circle 10s linear infinite;
}
@keyframes rotate-circle {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

Animating Background Gradients

CSS gives lots of possibilities to create different animations, but sometimes they may be not so intuitive to understand. If we want to animate the color of a text snippet, it may be not immediate to think that we must create a background, animate it, and then make the text transparent, and the surrounding background filled.

This example shows exactly this case.

Ciao amore, ciao

:root {
    --purple: rgb(123, 31, 162);
    --violet: rgb(103, 58, 183);
    --pink: rgb(244, 143, 177);
    --bblack: rgb(10, 10, 10);
    }
                                
    @keyframes background-pan {
    from {
        background-position: 0% center;}
    to {
        background-position: -200% center;
    }
    }
    
    body {
    background-color: var(--bblack);
    margin: 0px;
    overflow: hidden;
    }
    
    h1 {
    color: white;
    font-family: "Helvetica";
    font-size: clamp(2em, 2vw, 4em);
    font-weight: 400;
    margin: 0px;
    padding: 20px;
    text-align: center;
    }
    
    h1 > .magic {
    animation: background-pan 3s linear infinite;
    background: linear-gradient(to right, var(--purple), var(--violet), var(--pink), var(--purple));
    background-size: 200%;
    white-space: nowrap;
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent; 
    }

In this case, we used:

  1. We define the color variables
  2. We fix the background to black, and hide any overflow
  3. We style the color as white, Helvetica font, clamp the size of it, add some padding and center it
  4. Then, since we want the animation to occupy only a section of the text, add a span around "amore", and give it a linear gradient background
  5. After this, we should see the text with only the background, but the text is still white.
  6. We use two webkit properties, background-clip to make the background only apply to the text, and then fill the text with transparent color using text-fill-color.
  7. We can move to the animation: we want it to be linear and infinite over 3 seconds.
  8. background-pan is an animation that goes from position 0 to -200%, so that it moves from side to side
  9. Finally, we add the background animation to the span using the animation property.

clamp() is a function that allows to specify a minimum, preferred, and maximum value for a property. It is useful for responsive designs.

clamp(minimum, preferred, maximum)

Have a look at the whole design on Codepen

Wrapping All Together

By adding some Javascript and SVGs, we can complete the styling of this text animation.

First, we add three SVG stars, put them inside a span element and give them the "magic-star" class.

<span class="magic-star">
    <svg viewBox="0 0 512 512">
        <path d="M512 255.1c0 11.34-7.406 
        20.86-18.44 23.64l-171.3 42.78l-42.78 
        171.1C276.7 504.6 267.2 512 255.9 
        512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 
        279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 
        7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 
        7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 
        171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
    </svg>
</span>
                            

Then, we style the stars so that they have a strict size, behave as block, have an absolute position and a variable-based position, and they can scale up and down based on height and width variables.

h1 > .magic > .magic-star {
--size: clamp(20px, 1.5vw, 30px);

animation: scale 700ms ease forwards;
display: block;
height: var(--size);
left: var(--star-left);
position: absolute;
top: var(--star-top);
width: var(--size);
}

Here comes JS: what we need is to change the stars position. First we must give a relative position to the span so that absolute works.

We create a function that generates a random number based on a minimu and maximum range.


// Function to generate a random number between 'min' and 'max'
function rand(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

Then we initialize two variables

let index = 0;
let interval = 1000;

Based on these two, we create an animation function.

We need to change the property of the star so that its variables --star-left and --star-top are randomized each time.

We also need to reset the animation each time, and force the browser to re-calculate the element's properties.

function animate(star) {
// Set the star's left and top position randomly within the specified range
star.style.setProperty("--star-left", rand(-10, 100) + '%');
star.style.setProperty("--star-top", rand(-40, 80) + '%');

// Reset the star's animation
star.style.animation = 'none';
star.offsetHeight; // Force the browser to re-calculate the element's properties
star.style.animation = '';
}

Now the fun part: let's select all the starts, and create a list of them:

var stars = document.getElementsByClassName("magic-star");
                    
  1. A for loop is used to iterate over each element in the stars array.
  2. An Immediately Invoked Function Expression (IIFE) is used to preserve the value of i (the index of the current star in the stars array) for each iteration. This is necessary because the setTimeout and setInterval functions used later in the code create new function scopes, and without the IIFE, they would all share the same value of i.
  3. A setTimeout function is used to set a delay for the initial animation of each star. The delay is determined by the current index multiplied by a third of the interval variable value (which is 1000 / 3).
  4. The animate function is called for the current star element, using the preserved index value.
  5. A setInterval function is set up to repeatedly call the animate function for the current star element, with a delay equal to the interval variable value (1000ms or 1 second).
  6. The setTimeout function is closed, and the IIFE is immediately invoked with the current value of i.

In summary, this snippet sets an initial delay for each star's animation and then repeatedly animates each star with a 1-second interval. The IIFE is used to preserve the index value for each star in the loop.

// Loop through each star and set up the animation
for (var i = 0; i < stars.length; i++) {
    // Use an immediately invoked function expression (IIFE) to preserve the index value
    (function(index) {
    // Set a delay for each star's initial animation
    setTimeout(function() {
        // Call the animate function for the current star
        animate(stars[index]);

        // Set an interval to repeatedly call the animate function for the current star
        setInterval(function() {
        animate(stars[index]);
        }, 1000);
    }, index * (interval / 3));
    })(i);
}

We can see here the final animation. Thanks to Hyperplexed for the design recreation.


© Andrea Schimmenti & Fabio Vitali. TW 2022-2023.