Using CSS to create a background-clip effect on text along with a smooth opacity

I've coded a unique effect for my <h1> heading text where each letter is enclosed in its own <span> element, like this:

<h1 class='gradient-heading' id='fade'>
  <span>H</span>
  <span>e</span>
  <span>l</span>
  <span>l</span>
  <span>o</span>
</h1>

To add some flair, I wanted to apply a gradient color to this heading text. Using CSS properties such as background-clip: text, I made the text transparent and added a gradient background with the following style:

.gradient-heading {
  background: linear-gradient(90deg, rgba(239,255,0,1) 0%, rgba(255,182,0,1) 48%, rgba(199,0,255,1) 100%);
  background-clip: text;
  color: transparent;
}

Now comes the tricky part - fading each individual letter within the <h1> using JavaScript. I tried implementing an animation with the following script:

function fadeLetters() {
  const heading = document.getElementById('fade');
  const spans = heading.querySelectorAll('span');

  let delay = 0;
  let delayIncrement = 200;
  let numLettersFadedIn = 0;
  const totalLetters = spans.length;

  spans.forEach((span, index) => {
    setTimeout(() => {
      span.style.transition = 'opacity 1s ease-in-out';
      span.style.opacity = 1;
      numLettersFadedIn++;

      if (numLettersFadedIn === totalLetters) {
        setTimeout(() => {
          spans.forEach((span, index) => {
            setTimeout(() => {
              span.style.transition = 'opacity 1s ease-in-out';
              span.style.opacity = 0;
            }, index * delayIncrement);
          });
          setTimeout(() => {
            numLettersFadedIn = 0;
            fadeLetters();
          }, totalLetters * delayIncrement);
        }, delayIncrement);
      }
    }, delay);

    delay += delayIncrement;
  });
}

fadeLetters();

However, the animation does not seem to work as expected. The fadeIn/Out effect doesn't occur smoothly - instead, letters blink on and off without transitioning properly. I suspect that this issue may be related to the background-clip: text CSS property, but I'm unsure of the exact problem.

You can view and test this issue on Codepen here: https://codepen.io/mknelsen/pen/vYVxKeb

If anyone has encountered a similar challenge or can provide insights into why this isn't working correctly, your input would be greatly appreciated!

Answer №1

Altering the opacity of an already transparent element may seem illogical at first glance. The reasoning behind it remains a mystery.

Nevertheless, such an effect can be achieved without the use of JavaScript. The key is to animate the color property from rgba(r, g, b, 0) (with alpha 0, representing transparency) to rgba(r, g, b, 1) (with alpha 1, reflecting the original color), and vice versa in alternating fashion:

(Check out the vanilla CSS implementation below.)

.gradient-heading {
  background: var(--background);
  -webkit-background-clip: text;
  background-clip: text;
}

span {
  animation: flash var(--duration) ease-in-out infinite alternate;
}

@for $i from 1 through 5 {
  span:nth-child(#{$i}) {
    animation-delay: calc(var(--delay-increment) * #{$i});
  }
}

@keyframes flash {
  0% {
    color: var(--color-0);
  }
  100% {
    color: var(--color-1);
  }
}

Give it a try:

:root {
  --duration: 1s;
  --delay-increment: 0.2s;
  --color-1: rgba(0, 0, 0, 1);
  --color-0: rgba(0, 0, 0, 0);
  --background: linear-gradient(90deg, #efff00 0%, #ffb600 48%, #c700ff 100%);
}

.gradient-heading {
  background: var(--background);
  -webkit-background-clip: text;
  background-clip: text;
}

span {
  animation-name: flash;
  animation-duration: var(--duration);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

span:nth-child(1) {
  animation-delay: calc(var(--delay-increment) * 1);
}

span:nth-child(2) {
  animation-delay: calc(var(--delay-increment) * 2);
}

span:nth-child(3) {
  animation-delay: calc(var(--delay-increment) * 3);
}

span:nth-child(4) {
  animation-delay: calc(var(--delay-increment) * 4);
}

span:nth-child(5) {
  animation-delay: calc(var(--delay-increment) * 5);
}

@keyframes flash {
  0% {
    color: var(--color-0);
  }
  100% {
    color: var(--color-1);
  }
}

/* For demonstration purposes only */


body {
  font-family: system-ui;
  background: var(--color-1);
  text-align: center;
}

h1 {
  font-size: 70px;
}
<h1 class="gradient-heading">
  <span>H</span>
  <span>e</span>
  <span>l</span>
  <span>l</span>
  <span>o</span>
</h1>

Answer №2

After trying various methods, I found success with a unique approach that involved implementing a trick similar to the one showcased in this CSS Tricks post: https://css-tricks.com/how-to-do-knockout-text/

function fadeLetters() {
  const heading = document.getElementById('fade');
  const spans = heading.querySelectorAll('span');

  let delay = 0;
  let delayIncrement = 200;
  let numLettersFadedIn = 0;
  const totalLetters = spans.length;

  spans.forEach((span, index) => {
    setTimeout(() => {
      span.style.color = 'rgba(255, 255, 255, 1)';
      numLettersFadedIn++;

      if (numLettersFadedIn === totalLetters) {
        setTimeout(() => {
          spans.forEach((span, index) => {
            setTimeout(() => {
              span.style.color = 'rgba(255, 255, 255, 0)';
            }, index * delayIncrement);
          });
          setTimeout(() => {
            numLettersFadedIn = 0;
            fadeLetters();
          }, totalLetters * delayIncrement);
        }, delayIncrement);
      }
    }, delay);

    delay += delayIncrement;
  });
}

fadeLetters();
body {
  font-family: system-ui;
  background: black;
  text-align: center;
}

h1 {
  background-color: black;
  border: 1px solid black;
  mix-blend-mode: lighten;
  position: relative;
}

h1::before {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  display: block;
  content: '';
  background: linear-gradient(90deg, rgba(239,255,0,1) 0%, rgba(255,182,0,1) 48%, rgba(199,0,255,1) 100%);
  background-size: 400% 400%;
  mix-blend-mode: multiply;
  animation: gradient 10s ease infinite;
}

@keyframes gradient {
  0% {
    background-position: 0% 50%;
  }

  50% {
    background-position: 100% 50%;
  }

  100% {
    background-position: 0% 50%;
  }
}

span {
  color: rgba(255, 255, 255, 0);
  transition: color 1s ease-in-out;
  font-size: 80px;
}
<h1 class='gradient-heading' id='fade'>
  <span>H</span>
  <span>e</span>
  <span>l</span>
  <span>l</span>
  <span>o</span>
</h1>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to view a list of URLs all at once in a single window?

Is there a way to open multiple URLs in a single tab? I have a list of URLs that I want to open and cache the contents without cluttering my browser with numerous tabs. ...

Exploring ways to replicate the functionality of Gmail's Contact manager

Looking to develop a contact manager similar to Gmail's interface. While I have a basic understanding of AJAX and some experience with jQuery, my JavaScript skills are limited. Any suggestions for books or blogs to improve them would be welcome. Tha ...

Transferring selected text to a Cordova application from a different application

Can I extract highlighted text from a different application, such as a browser, and send it to my Intel XDK Cordova app in JSON format for utilization? (Potentially utilizing a context menu) ...

Having trouble dynamically displaying the '<' symbol using JavaScript?

Whenever I attempt to show a string with the character '<' in it, the part of the string that comes after the symbol is not displayed. Strangely enough, when I output it to the console, it appears correctly. Take a look at this excerpt showcas ...

Flexslider doesn't adjust the height of the viewport when displaying a shorter image in the slideshow

Is Flexslider not resizing its viewport height according to the current image on your site? The fixed height seems to be causing blank white space under shorter images. Are you inadvertently overriding Flexslider's responsive height function? Is there ...

Discovering intersections between Polylines on Google Maps - a comprehensive guide

I'm currently developing a project involving a unique twist on Google Maps, focusing exclusively on natural hiking paths. My routes are built using GPX files converted into Google Maps polylines. Is there an efficient way to identify the intersection ...

Tips on integrating the createjs library into a ReactJS project

Hey there! I'm currently working on developing a canvas-based app using ReactJS, and I need to integrate the CreateJS library. As a newcomer to ReactJS, I've been struggling to figure out the best approach. I've tried two different methods - ...

emulate clicking on a child component element within the parent component's test file

In my testing scenario, I encountered a challenge in simulating the click event of an element that exists in a child component from the parent test file. let card; const displayCardSection = (cardName) => { card = cardName; }; describe('Parent ...

Developing Action To Execute Node.js within an HTML File

Just starting off with Node.js So I've set up a server using node.js and have the following files: Server.js Client.js Index.html The server is all good to go. But within the HTML file, I want to include a link or action to execute client.js Norm ...

configure Next.js to exclude a specific subPath within the host from being processed

I've encountered an issue with my public_html directory where there is a folder named blog that is unrelated to my NextJs app. After deploying the app on the host, everything works fine until I try to access the blog section using the following URL: w ...

Exploring the functionality of CSS class selectors (.class-name) when used alongside CSS tag selectors (div, etc...)

I have designed my website with three distinct tables, each requiring unique styling. The general approach I take to apply styling to these tables is as follows: import './gameview.css' <div className="game-results"> <h ...

Updates in dropdown events when options data has been modified

Hey there, I'm wondering about dropdown events. Let's say I have two dropdowns. When a selection is made in the first dropdown, all options in the second dropdown are replaced with new ones. For example, let's say the first dropdown has thes ...

Is there a way to extract all the <a> elements from the <ul> element, which acts as the parent node in my HTML code?

Is there a more efficient way to target and select all the <a> tags within a parent tag of <ul>? I attempted using $("ul").children().children(); but I'm looking for alternatives. <ul class="chat-inbox" id="chat-inbox"> <li&g ...

The webpage containing the authRequired meta briefly flashes on the screen, even if there are no active login sessions on Firebase

As a beginner in Vue and web development, I have been enjoying my journey so far but now I find myself stuck. Currently, I am working on creating an admin dashboard with Firebase authentication. Everything seems to be functioning as expected, except for on ...

Issue with JSONP request in jQuery/AJAX

I am currently attempting to make a request to a different site, which requires authentication through a cookie. Oddly enough, when I try making the call like this : $.getJSON(url + '?', function(data){ alert(data); }); The HTTP headers do ...

Utilizing elapsed time in coding to create a function for DOM manipulation

As a new software engineering student, I am facing a challenge that I am struggling to overcome. I am currently working on developing a basic rhythm game similar to Guitar Hero using HTML, CSS, and JavaScript. I have successfully implemented an elapsedTim ...

Seamless transition of lightbox fading in and out

Looking to create a smooth fade in and out effect for my lightbox using CSS and a bit of JavaScript. I've managed to achieve the fading in part, but now I'm wondering how to make it fade out as well. Here is the CSS code: @-webkit-keyframes Fad ...

Is pl/pgsql block code supported by postgres-nodejs?

I am attempting to define a custom UUID variable that can be utilized across multiple queries within a transaction. Initially, I attempted using a JavaScript variable which ultimately defeated the purpose of declaring the variable on the server side. The ...

Padding will not completely fill the <li> and <div> elements

Looking to create a sleek menu bar that's 40px tall and fills up 80% of the browser width. The challenge arises when trying to center the text within the menu items. Despite adjusting padding for alignment, there's still a small gap because of & ...

Effortlessly Transform HTML into Plain Text Using jQuery

If I wanted to create a feature on my website where users can input text into a text area and then convert it to HTML by clicking a button, how could I achieve that? I'm looking for functionality similar to what Wordpress offers, where users can enter ...