So I want to change the width of a span element inside a h1 element that have the textContent changed every 4s with setInterval().I want to do that to add a transition to the width property to fill the space between the h1 and the next word that span will have smoothly.
I tried to get the width of the span with getBoundingClientRect() but that didn't work and when I set the width it remains the width of the first element and it's not changed dynamically as I would want.
Here is the code:
const changingSpan = document.querySelector('.changing-span');
let array = ['best', 'tastiest', 'freshest'];
let count = 0;
setInterval(function() {
count++;
let elementWidth = changingSpan.getBoundingClientRect().width.toString();
if(count === array.length) count = 0;
changingSpan.classList.add('animation-span');
changingSpan.textContent = array[count];
changingSpan.style.width = `${elementWidth}px`;
changingSpan.addEventListener('animationend', function() {
changingSpan.classList.remove('animation-span');
})
}, 4000);
.changing-span {
display: inline-block;
color: #c82929;
transition: width .2s ease;
}
.animation-span {
animation: moveDown .8s ease;
}
#keyframes moveDown {
0% {opacity: 0;
transform: translateY(-50%);
}
100% {
transform: translateY(0);
}
}
<h1 class="first-heading">The <span class="changing-span">best</span> burgers in town.</h1>
consider an animation using max-width:
const changingSpan = document.querySelector('.changing-span');
let array = ['best', 'tastiest', 'freshest'];
let count = 0;
setInterval(function() {
count++;
if (count === array.length) count = 0;
changingSpan.classList.add('animation-span');
changingSpan.textContent = array[count];
changingSpan.addEventListener('animationend', function() {
changingSpan.classList.remove('animation-span');
})
}, 4000);
.changing-span {
display: inline-block;
color: #c82929;
}
.animation-span {
animation: moveDown .8s ease;
}
#keyframes moveDown {
0% {
opacity: 0;
transform: translateY(-50%);
max-width:0;
}
100% {
transform: translateY(0);
max-width:200px; /* a big value here */
}
}
<h1 class="first-heading">The <span class="changing-span">best</span> burgers in town.</h1>
Your code is very good.
But you need to remove 2 rows like below, then you code works well as you want
let elementWidth = changingSpan.getBoundingClientRect().width.toString();
changingSpan.style.width = ${elementWidth}px;
So the answer was posted by Sean above(Thank you). I will put here the code snippet for this in case somebody will search for something similar and will want to do the same thing:
const changingSpan = document.querySelector('.changing-span');
const changingSpanWrapper = document.querySelector('.changing-span-wrapper');
let array = ['best', 'tastiest', 'freshest'];
let count = 0;
changingSpanWrapper.style.width = `${changingSpan.getBoundingClientRect().width.toString()}px`;
setInterval(function() {
count++;
if(count === array.length) count = 0;
changingSpan.classList.add('animation-span');
changingSpan.textContent = array[count];
changingSpan.addEventListener('animationstart', function() {
let elementWidth = changingSpan.getBoundingClientRect().width.toString();
let elementHeight = changingSpan.getBoundingClientRect().height.toString();
changingSpanWrapper.style.width = `${elementWidth}px`;
changingSpanWrapper.style.height = `${elementHeight}px`;
})
changingSpan.addEventListener('animationend', function() {
changingSpan.classList.remove('animation-span');
})
}, 4000);
.changing-span-wrapper {
display: inline-block;
transition: width .2s ease, height .2s ease;
}
.changing-span {
display: inline-block;
color: #c82929;
}
.animation-span {
animation: moveDown 1s ease;
}
#keyframes moveDown {
0% {opacity: 0;
transform: translateY(-50%);
}
100% {
transform: translateY(0);
}
}
<h1 class="first-heading">The <span class="changing-span-wrapper"><span class="changing-span">best</span></span> burgers in town.</h1>
Related
I am trying to change a spans style using JavaScript but cannot seem to do it. Below I have created the span and given it an Id "spanzo", then I have stored the span in a var "elementtl" and tried two ways to add styles to it but its not doing anything. Styles have already been set to the span, I am just trying to override them styles.
const letterEl = document.createElement("span");
letterEl.setAttribute("id", "spanzo");
var elementtl = document.getElementById("spanzo");
elementtl.classList.toggle("spanzo1");
elementtl.style.backgroundColor = "red";
#spanzo{
transform: rotatey(0deg);
}
.spanzo1{
transform: rotatey(180deg);
transition: 2s;
}
const letterEl = document.createElement("span");
letterEl.innerText = "This is a span.";
document.body.appendChild(letterEl);
letterEl.setAttribute("id", "spanzo");
var elementtl = document.getElementById("spanzo");
elementtl.classList.toggle("spanzo1");
elementtl.style.backgroundColor = "red";
#spanzo{
transform: rotatey(0deg);
}
.spanzo1{
transform: rotatey(180deg);
transition: 2s;
}
First of all you need to add a width and height to your #spanzo style for it to actually occupy some space and you need to add it to the <body> element using
document.body.append(letterEl)
All together
const letterEl = document.createElement("span");
letterEl.setAttribute("id", "spanzo");
document.body.append(letterEl)
document.body.appendChild(letterEl);
var elementtl = document.getElementById("spanzo");
elementtl.classList.toggle("spanzo1");
elementtl.style.backgroundColor = "red";
#spanzo{
transform: rotatey(0deg);
width:50px;
height:50px;
}
.spanzo1{
transform: rotatey(180deg);
transition: 2s;
}
const letterEl = document.createElement("span");
letterEl.setAttribute("id", "spanzo");
letterEl.textContent = 'hello world';
document.body.appendChild(letterEl);
var elementtl = document.getElementById("spanzo");
elementtl.classList.toggle("spanzo1");
elementtl.style.backgroundColor = "red";
#spanzo{
transform: rotatey(0deg);
}
.spanzo1{
transform: rotatey(180deg);
transition: 2s;
}
To create and append an element you may want to use insertAdjacentHTML. To add or remove css-classes use classList[add/remove]. For example:
document.body.insertAdjacentHTML(`beforeend`,
`<div id="spanzo">Hi, i am div#spanzo</div>`);
document.addEventListener(`click`, handle);
function demo() {
const demoElem = document.querySelector(`#spanzo`);
demoElem.textContent = `Hi, i am div#spanzo. Wait a sec ...`;
setTimeout(() => {
demoElem.classList.add(`spanzo1`);
setTimeout(() => {
demoElem.classList.remove(`spanzo1`);
demoElem.textContent = `I'm back!`}, 3000);
}, 1000);
}
function handle(evt) {
if (evt.target.nodeName === `BUTTON`) {
return demo();
}
}
#spanzo.spanzo1{
transform: rotatey(180deg);
transition: 2s;
}
<button>Demo</button>
I have a JavaScript function that types out, letter by letter, a message. However, where the current character to be typed is located, I have a blinking css animation. What I need is to stop this animation and make it disappear.
I am using a css with #id::after to put the animation after the text in question. The animation works fine, I need a way to set content: '█'; to content: ''; via JavaScript.
(function type_p(){
let msg = 'This is a message to type out! Spooky!';
let element = document.getElementById('typehere');
typeOut(msg, '', 0, element);
}());
function typeOut(text, letter, index, element){
letter = text.slice(0, ++index);
element.textContent = letter;
if (letter.length == text.length){
stop();
}
setTimeout(typeOut, 100, text, letter, index, element);
}
#typehere {
position: relative;
}
#typehere::after {
content: '█';
position: absolute;
animation: blink 1.5s infinite;
/* animation-iteration-count: 2; */
}
#keyframes blink {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
51% {
opacity: 0;
}
100% {
opacity: 0;
}
}
<p id="typehere">Here</P>
I am aware of CSS animation-iteration-count: however this will stop the animation but it will still be visible (motionless). How do I remove this?
I would just add a class to your element and change the content based on class.
(function type_p(){
let msg = 'This is a message to type out! Spooky!';
let element = document.getElementById('typehere');
typeOut(msg, '', 0, element);
}());
function typeOut(text, letter, index, element){
letter = text.slice(0, ++index);
element.textContent = letter;
if (letter.length == text.length){
element.classList.add('stop');
stop();
}
setTimeout(typeOut, 100, text, letter, index, element);
}
#typehere {
position: relative;
}
#typehere::after {
content: '█';
position: absolute;
animation: blink 1.5s infinite;
/* animation-iteration-count: 2; */
}
#typehere.stop::after {
content: '';
}
#keyframes blink {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
51% {
opacity: 0;
}
100% {
opacity: 0;
}
}
<p id="typehere">Here</P>
This is similar to the other answer, but I find it easier/cleaner than adding multiple classes to something that will no longer be visible.. You can add the animation styling as a class, and then remove that class when you no longer want it to animate.
Change to class in css:
.typehereclass::after {
content: '█';
position: absolute;
animation: blink 1.5s infinite;
}
Add the class to your element in html:
<p id="typehere" class="typehereclass">Here</P>
And then when you want to stop the blinking in JS:
element.classList.remove('typehereclass')
I'm expecting the top paragraph to fade out as the bottom paragraph fades in but the element is getting removed from the DOM before the animation happens.
When there are 5 paragraphs in the collection (querySelectorAll) the 0 index gets removed. The CSS animation works as expected for createElement not for .remove()
What am I missing?
function addPara() {
let r = Math.floor(Math.random() * 9);
let g = Math.floor(Math.random() * 9);
let b = Math.floor(Math.random() * 9);
let bg = `#${r}${g}${b}`
let qsaPara = document.querySelectorAll('p');
const para = document.createElement('p')
para.style.backgroundColor = bg
para.style.height = '3rem'
document.body.appendChild(para)
let paraArray = Array.from(qsaPara);
let l = paraArray.length
let clVal = para.classList.value;
function parRemove() {
paraArray[0].remove();
paraArray[0].removeEventListener('transitionend', parRemove)
}
if (paraArray.length >= 5) {
paraArray[0].classList.add('fall');
// para.innerHTML += `class:${clVal}`
paraArray.forEach((para, i) => {
para.nextSibling.innerHTML = `index ${i}, collection length${l}, class:${clVal}`;
});
//------------------------
paraArray[0].addEventListener('transitionend', parRemove, false);
//----------------------
} else if (paraArray.length <= 5) {
paraArray.forEach((para, i) => {
para.innerHTML = `index ${i}, collection length${l}, class:${clVal}`
})
}
}
const paraTimer = setInterval(addPara, 2000);
p {
color: white;
width: 100%;
animation-name: fade-in;
animation-duration: 1s;
animation-iteration-count: 1;
transition: all ease-in-out 1s;
}
.fade {
animation-name: fade-out;
animation-duration: 1s;
animation-iteration-count: 1;
}
#keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
It seems to be an issue with transitionend expecting CSS transform and NOT animation.
Use webkitAnimationEnd / animationend event listeners for css animations and use webkitTransitionEnd / 'transitionend' event listeners for css transform transitions.
If ya didn't know, well now ya know.
I updated the answer with a fix for my original animation
function addPara() {
let r = Math.floor(Math.random() * 9);
let g = Math.floor(Math.random() * 9);
let b = Math.floor(Math.random() * 9);
let bg = `#${r}${g}${b}`
let qsaPara = document.querySelectorAll('p');
const para = document.createElement('p')
para.style.backgroundColor = bg
para.style.height = '3rem'
document.body.appendChild(para)
let paraArray = Array.from(qsaPara);
let l = paraArray.length
let clVal = para.classList.value;
function parRemove() {
paraArray[0].remove()
// paraArray[0].removeEventListener('transitionend', parRemove)
paraArray[0].removeEventListener("webkitAnimationEnd", parRemove);
paraArray[0].removeEventListener("animationend", parRemove, false);
}
if (paraArray.length >= 5) {
paraArray.forEach((para, i) => {
para.nextSibling.innerHTML = `index ${i}, collection length${l}`;
// for css transition
/* paraArray[0].classList.add('fall');
paraArray[0].addEventListener('transitionend', parRemove, false) */
// for css animation
paraArray[0].classList.add('fade');
paraArray[0].addEventListener('webkitAnimationEnd', parRemove, false); // Code for Chrome, Safari and Opera
paraArray[0].addEventListener("animationend", parRemove, false);
});
} else if (paraArray.length <= 5) {
paraArray.forEach((para, i) => {
para.innerHTML = `index ${i}, collection length${l}`
})
}
}
const paraTimer = setInterval(addPara, 2000);
p {
opacity: 1;
color: white;
width: 100%;
animation-name: fade-in;
animation-duration: 1s;
animation-iteration-count: 1;
/* transition: all ease-in-out 1s; */
}
.fade {
animation-name: fade-out;
animation-duration: 1s;
animation-iteration-count: 1;
}
#keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/*
.fall {
transform: scale(3);
transform: translateY(9rem);
opacity: 0;
}. */
I want to stay with final properties of css animation, but remove animation property itself, exmeple:
#keyframes('foo')
{
0% { opacity: 0; }
to { opacity: 1; }
}
at the begining :
.my_element
{
opacity: 0;
}
I apply animation by Javascript, so I got :
.my_element
{
opacity: 0;
}
element.style
{
animation: foo 1s forwards; #with opacity: 1; at the end.
}
And now I need clean up :
.my_element
{
opacity: 0;
}
element.style
{
opacity: 1;
}
How can I do that?
This does exactly what you're asking. On animationEnd, the cleanUp function is passed an object containing the class to remove after the animation, the property to replace on the element, and the property value to replace on the element. Check out the DOM before and after clicking button the and you'll see that this works properly.
var el = document.querySelector(".my_element"),
cleanUp = function(data) {
el.classList.remove(data.remove);
el.style[data.prop] = data.val;
},
startAnimation = function() {
el.classList.add("animate");
};
el.addEventListener("animationend", function() {
var cleanup_data = {
remove: "animate",
prop: "opacity",
val: 1
};
cleanUp(cleanup_data);
});
#keyframes foo {
from { opacity: 0; }
to { opacity: 1; }
}
.animate {
animation: foo 3s forwards;
}
.my_element {
opacity: 0;
}
<div class="my_element">some text</div>
<button onclick="startAnimation()">Start Animation</button>
I am trying to repeatedly display some sentences letter by letter using some fade in/fade out effect.However, when trying to do that, it seems that words break in the middle as shown below. How can I avoid word breaking?
var quotes = document.getElementsByClassName('quote');
var quoteArray = [];
var currentQuote = 0;
quotes[currentQuote].style.opacity = 0;
for (var i = 0; i < quotes.length; i++) {
splitLetters(quotes[i]);
}
function changeQuote() {
var cw = quoteArray[currentQuote];
var nw = currentQuote == quotes.length-1 ? quoteArray[0] : quoteArray[currentQuote+1];
for (var i = 0; i < cw.length; i++) {
animateLetterOut(cw, i);
}
for (var i = 0; i < nw.length; i++) {
nw[i].className = 'letter behind';
nw[0].parentElement.style.opacity = 1;
animateLetterIn(nw, i);
}
currentQuote = (currentQuote == quoteArray.length-1) ? 0 : currentQuote+1;
}
function animateLetterOut(cw, i) {
setTimeout(function() {
cw[i].className = 'letter out';
}, 0);
}
function animateLetterIn(nw, i) {
setTimeout(function() {
nw[i].className = 'letter in';
}, 340+(i*30));
}
function splitLetters(quote) {
var content = quote.innerHTML;
console.log(quote.innerHTML);
quote.innerHTML = '';
var letters = [];
for (var i = 0; i < content.length; i++) {
var letter = document.createElement('span');
letter.className = 'letter';
letter.innerHTML = content.charAt(i)==' '?' ':content.charAt(i);
quote.appendChild(letter);
letters.push(letter);
}
quoteArray.push(letters);
}
changeQuote();
setInterval(changeQuote, 10000);
body {
font-weight: 600;
font-size: 40px;
}
.text {
position: relative;
}
.quote {
position: absolute;
opacity: 0;
}
.letter {
display: inline-block;
position: relative;
float: left;
-webkit-transform: translateZ(25px);
transform: translateZ(25px);
-webkit-transform-origin: 50% 50% 25px;
transform-origin: 50% 50% 25px;
}
.letter.out {
visibility: hidden;
opacity: 0;
transition: visibility 0s 0.7s, opacity 0.7s linear;
}
.letter.behind {
visibility: hidden;
opacity: 0;
}
.letter.in {
visibility: visible;
opacity: 1;
transition: opacity 0.7s linear;
}
<body>
<div class="text">
<p>
<span class="quote">TEXT ONE(1): For example, if you are designing a brand new website for someone, most times you will have to make sure the prototype looks finished by inserting text or photos or what have you. </span>
<span class="quote">TEXT TWO(2): The purpose of this is so the person viewing the prototype has a chance to actually feel and understand the idea behind what you have created.</span>
</p>
</div>
Your setInterval(changeQuote, 5000) is the source of the effect you have cleverly developed finishing short. Initially I began to play with the 5000ms and changing it to 15000ms down to around 8000~10000ms is what seemed to make it work best.
Change it to setInterval(changeQuote, 9000) and see the difference.
However, thinking of scalability, you will need to figure out a way to make it so that the setInterval waits until the quoteArray has finished pushing the letters.
EDIT
Based on the feedback in the comments, I determined the following:
On the JavaScript side of things, each letter is a <span> meaning that each served as an individual element. What was lacking was to create a word to wrap around each sentence. This is would ensure that each word would wrap around according to its parent container.
On the CSS side of things, the container of the letters meaning quote needed styling which would allow it to better represent its contents. By adding white-space: nowrap, display: block I managed to give its children a container which would adapt depending on the screen width.
See the below snippet fixed from the provided one for reference.
var quotes = document.getElementsByClassName('quote'),
quoteArray = [],
currentQuote = 0;
quotes[currentQuote].style.opacity = 0;
for (var i = 0; i < quotes.length; i++) {
splitLetters(quotes[i]);
}
function changeQuote() {
var cw = quoteArray[currentQuote];
var nw = currentQuote == quotes.length - 1 ? quoteArray[0] : quoteArray[currentQuote + 1];
for (var i = 0; i < cw.length; i++) {
animateLetterOut(cw, i);
}
for (var i = 0; i < nw.length; i++) {
nw[i].className = 'letter behind';
nw[0].parentElement.style.opacity = 1;
animateLetterIn(nw, i);
}
currentQuote = (currentQuote == quoteArray.length - 1) ? 0 : currentQuote + 1;
}
function animateLetterOut(cw, i) {
setTimeout(function() {
cw[i].className = 'letter out';
}, 0);
}
function animateLetterIn(nw, i) {
setTimeout(function() {
nw[i].className = 'letter in';
}, 340 + (i * 30));
}
function splitLetters(quote) {
var content = quote.innerHTML,
words = [],
word = document.createElement('span');
word.className = "word";
word.innerHTML = "";
quote.innerHTML = "";
for (var i = 0; i < content.length; i++) {
var letter = document.createElement('span');
letter.className = 'letter';
if(content.charAt(i) !== " "){
letter.innerHTML = content.charAt(i);
word.innerHTML = word.innerHTML.concat(letter.innerHTML);
}
else {
letter.innerHTML = " ";
word.innerHTML = word.innerHTML.concat(letter.innerHTML);
quote.appendChild(word);
words.push(word);
word = document.createElement('span');
word.className = "word";
}
}
quoteArray.push(words);
}
changeQuote();
setInterval(changeQuote, 10000);
body {
font-weight: 600;
font-size: 40px;
}
.text {
position: relative;
}
.quote {
position: absolute;
display: block;
opacity: 0;
white-space: nowrap;
}
.letter {
display: inline-block;
position: relative;
float: left;
-webkit-transform: translateZ(25px);
transform: translateZ(25px);
-webkit-transform-origin: 50% 50% 25px;
transform-origin: 50% 50% 25px;
}
.letter.out {
visibility: hidden;
opacity: 0;
transition: visibility 0s 0.7s, opacity 0.7s linear;
}
.letter.behind {
visibility: hidden;
opacity: 0;
}
.letter.in {
visibility: visible;
opacity: 1;
transition: opacity 0.7s linear;
}
<div class="text">
<p>
<span class="quote">TEXT ONE(1): For example, if you are designing a brand new website for someone, most times you will have to make sure the prototype looks finished by inserting text or photos or what have you. </span>
<span class="quote">TEXT TWO(2): The purpose of this is so the person viewing the prototype has a chance to actually feel and understand the idea behind what you have created.</span>
</p>
</div>