Just make a very very simple animation:
p {
animation: appear 1s linear 1;
font-family:monospace;
}
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<p>Thank you for any helps!</p>
It works, but I am just thinking is it possible to make the first letter appear first, after the first letter appearing, then the second, and finally the last letter (now the whole content of p
will appear at the same time)
Does CSS have any selectors to select every characters in the element and is it possible to achieve the effect use CSS only?
I only know first-letter
and last-letter
selector in css, but not sure if css has selector for every letters
If not only with CSS, I would be also Ok with JS solution?
Appreciate for any helps provided~
CodePudding user response:
Here's a vanilla JS approach. First we extract the text, empty the element, then rebuild it one character at a time.
let p = document.querySelector('.anim');
// store value
let t = p.innerText.split(''),
counter = 0
p.innerHTML = '';
let inter = setInterval(() => {
if (counter == t.length) clearInterval(inter)
else p.insertAdjacentHTML('beforeend', `<span>${t[counter ]}</span>`);
}, 100)
p.anim span {
animation: appear 1s linear 1;
}
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<p class='anim'>Thank you for any helps!</p>
CodePudding user response:
If a JS-based solution is acceptable, then you can easily achieve this using a combination of DOM manipulation (wrapping each non-space character in a <span>
element), and then sequentially toggling a class that runs the animation.
The sequential part can be achieved by simply looping through the generated <span>
element, and then awaiting for a promise to be resolved.
In your question it is not clear how you want to be a delay to be calculated: I simply assume you want an arbitrary/customizable delay. In this case, a simple promise-based sleep
function can get the job done:
function sleep(ms = 0) {
return new Promise(r => setTimeout(r, ms));
}
async function fadeInCharacters(el) {
// NOTE: Use a custom data- attribute to ensure we only target the <span> elements we generate
el.innerHTML = el.textContent.replace(/([^\s])/g, '<span data-animate>$1</span>');
for (const span of el.querySelectorAll('span[data-animate]')) {
span.classList.add('animate');
await sleep(100);
}
}
fadeInCharacters(document.querySelector('p'));
.animate {
animation: appear 1s ease-in-out 1 forwards;
}
span {
opacity: 0;
}
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<p>Thank you for any help!</p>
If you want to wait for each character before fading in the next, then change the await function into something that hooks into the animationend
event, although I am inclined to believe this is not what you intend. Just putting it out here for the sake of completeness:
function onAnimationEnd(el) {
return new Promise(r => el.addEventListener('animationend', () => r()));
}
async function fadeInCharacters(el) {
// NOTE: Use a custom data- attribute to ensure we only target the <span> elements we generate
el.innerHTML = el.textContent.replace(/([^\s])/g, '<span data-animate>$1</span>');
for (const span of el.querySelectorAll('span[data-animate]')) {
span.classList.add('animate');
await onAnimationEnd(span);
}
}
fadeInCharacters(document.querySelector('p'));
.animate {
animation: appear 1s ease-in-out 1 forwards;
}
span {
opacity: 0;
}
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<p>Thank you for any help!</p>