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>
Related
I have a simple animation code, looks like a console input.
Originally from: https://codepen.io/atunnecliffe/pen/BaZyLR
I modified the splash screen intro into just a console input in my website:
Code:
<script>
//console
var textarea = $('.term');
var text = 'ping life';
var i = 0;
runner();
function runner() {
textarea.append(text.charAt(i));
i++;
setTimeout(
function () {
runner();
}, Math.floor(Math.random() * 1000) + 50);
}
</script>
Now the effect that I want is a bit complex, for me at least, as my knowledge about JQuery is limited. I wanted the code to enter ping life, then backspace completely, repeat infinitely. I looked up on how to simulate backspace in JQuery, using escape sequence of (8), but I am not sure how to use the escape sequence, nor implement the function into the existing recursive function, for it to repeat infinitely.
Any help would be wonderful :)
Like this?
Counting like this will give a zigzag like counting pattern. I added buffers for start and end of input, and a fixed timeout for deleting letters.
textarea.text(text.substr(0, i)) selects a substring of your text (treated as an array of letters - selecting everything between index 0 and i)
Easier than appending and deleting letters
var direction = 1;
var i = 0;
var textarea = $('.term');
var text = 'ping life';
// NOTE:
// I added the "#dev:~$ " as css:before elem, easier to write the code
function count() {
i += direction;
direction *= (((i % text.length) == 0) ? -1 : 1);
textarea.text(text.substr(0, i));
clearInterval(time);
// direction is 1 if counting up
if (direction === 1) {
if (i === 0) {
// buffer for start
time = setInterval(count, 1000);
} else {
time = setInterval(count, Math.floor(Math.random() * 1000) + 50);
}
} else {
// direction is -1 if counting down
if (i === text.length) {
time = setInterval(count, 1500);
} else {
// buffer for end
time = setInterval(count, 100);
}
}
}
// inital interval
// setTimeout doesn't work well here
var time = setInterval(count, 1000)
html,
body {
margin: 0 auto;
height: 100%;
}
pre {
padding: 0;
margin: 0;
}
pre::before {
content: "#dev:~$ ";
color: white;
}
.load {
margin: 0 auto;
min-height: 100%;
width: 100%;
background: black;
}
.term {
font-family: monospace;
color: #fff;
opacity: 0.8;
font-size: 2em;
overflow-y: auto;
overflow-x: hidden;
padding-top: 10px;
padding-left: 20px;
}
.term:after {
content: "_";
opacity: 1;
animation: cursor 1s infinite;
}
#keyframes cursor {
0% {
opacity: 0;
}
40% {
opacity: 0;
}
50% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="load">
<pre class="term"></pre>
</div>
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>
I'm trying to create a number of divs and iterate through them on click.
The iteration only works on every other click.
Strangely, I'm getting no errors.
I tried moving the divs variable declaration outside of the for loop.
for (let i = 0; i <= 5; i++) {
let div = document.body.insertAdjacentHTML('beforeend', `<div class="div">${i}</div>`)
let divs = document.getElementsByClassName('div');
divs[i].style.backgroundColor = randomColor();
divs[i].onclick = () => {
close(divs[i])
open(divs[i + 1])
}
}
function randomColor() {
let e = Math.floor(Math.random() * 16777215).toString(16);
return `#${e}`;
}
function close(t) {
t.style.transform = 'scale(0.5)'
t.style.opacity = '0'
setTimeout(function() {
t.style.display = 'none'
}, 500)
}
function open(t) {
t.style.display = 'block'
setTimeout(function() {
t.style.transform = 'scale(1)'
t.style.opacity = '1'
}, 5)
}
div {
height: 100%;
width: 100%;
position: fixed;
top: 0;
bottom: 0;
right: 0;
transition: 0.5s;
opacity: 0;
transform: scale(0.5);
}
you need to
open(divs[(i + 1) % 6]) so it loop correctly
hide all divs at initial (except one)
there still some minor issue with the initial state (need to add opacity and transform, I leave it for simplicity), I think you should use proper class instead of inline css to make it easier.
for (let i = 0; i <= 5; i++) {
let div = document.body.insertAdjacentHTML('beforeend',
`<div class="div" style="display:${i==0?'block':'none'}">${i}</div>`
)
}
let divs = document.getElementsByClassName('div');
for (let i = 0; i <= 5; i++) {
divs[i].style.backgroundColor = randomColor();
divs[i].onclick = () => {
close(divs[i])
open(divs[(i + 1) % 6])
}
}
function randomColor() {
let e = Math.floor(Math.random() * 16777215).toString(16);
return `#${e}`;
}
function close(t) {
t.style.transform = 'scale(0.5)'
t.style.opacity = '0'
setTimeout(function() {
t.style.display = 'none'
}, 500)
}
function open(t) {
t.style.display = 'block'
setTimeout(function() {
t.style.transform = 'scale(1)'
t.style.opacity = '1'
}, 5)
}
div {
height: 100%;
width: 100%;
position: fixed;
top: 0;
bottom: 0;
right: 0;
transition: 0.5s;
opacity: 0;
transform: scale(0.5);
}
I trying to do equalizer animation. I did function for start animation, but i cant do function for stop animation, becouse clearInterval not working.
my codepen
https://codepen.io/naraxiss/pen/qyMamy
var spans = document.querySelectorAll('span');
function getRandom() {
return Math.random();
}
function scale(el){
el.style.transform = 'scaleY(' + getRandom() +')';
}
var myInterval = null;
function startMusic (spans){
var el = spans;
for(var i = 0; i < el.length; i++){
(function(i) {
myInterval = setInterval(function(){
scale(el[i]);
}, 100);
})(i);
}
}
function stopMusic (interval,els){
clearInterval(interval);
//console.log(els)
for(var i = 0; i < els.length; i++){
els[i].style.stransform = 'scaleY(0.05)'
}
}
document.querySelector('.start').addEventListener('click', function(){
startMusic(spans);
})
document.querySelector('.finish').addEventListener('click', function(){
stopMusic(myInterval,spans);
})
body{
margin: 0;
}
span{
display: inline-block;
width: 50px;
height: 300px;
background-color: #000;
margin-right: 10px;
transition: 0.1s linear;
transform: scaleY(0.005);
transform-origin: bottom;
}
.equalizer{
position: absolute;
top: 50%;
left: 40%;
transform: translate(-50%,-50%);
}
<div class="equalizer">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<button class="start">START</button>
<button class="finish">FINISH</button>
Thanks!
You need to save reference of each interval in an array like this
var myInterval = [];
function startMusic (spans){
var el = spans;
for(var i = 0; i < el.length; i++){
(function(i) {
let interval = setInterval(function(){
scale(el[i]);
}, 100);
myInterval.push(interval);
})(i);
}
}
and then in stopMusic function clear all of those intervals like this one.
function stopMusic (interval,els){
interval.forEach(inter => clearInterval(inter));
console.log(els)
for(var i = 0; i < els.length; i++){
els[i].style.stransform = 'scaleY(0.05)'
}
}
Because you create 5 intervals inside your for loop you need to stop them seperately.
var spans = document.querySelectorAll('span');
function getRandom() {
return Math.random();
}
function scale(el) {
el.style.transform = 'scaleY(' + getRandom() + ')';
}
var myInterval = []; // <------------------------ Make it an array
function startMusic(spans) {
var el = spans;
for (var i = 0; i < el.length; i++) {
(function(i) {
myInterval.push(setInterval(function() { // <------------- push every item
scale(el[i]);
}, 100));
console.log(myInterval)
})(i);
}
}
function stopMusic(els) {
for (var x = 0; x < myInterval.length; x++) {
clearInterval(myInterval[x]); // <--------------------- Access every item to clear
}
console.log(els)
for (var i = 0; i < els.length; i++) {
els[i].style.stransform = 'scaleY(0.05)'
}
}
The reason it doesn't work is because you start 5 intervals but only stop 1. Put the intervals in a array so you can loop them and stop them all. See change below.
var spans = document.querySelectorAll('span');
function getRandom() {
return Math.random();
}
function scale(el){
el.style.transform = 'scaleY(' + getRandom() +')';
}
var myIntervals = [];
function startMusic (spans){
var el = spans;
for(var i = 0; i < el.length; i++){
(function(i) {
myIntervals[i] = setInterval(function(){
scale(el[i]);
}, 100);
})(i);
}
}
function stopMusic (intervals,els){
//clearInterval(interval);
//console.log(els)
for(var i = 0; i < els.length; i++){
clearInterval(intervals[i]);
els[i].style.stransform = 'scaleY(0.05)'
}
}
document.querySelector('.start').addEventListener('click', function(){
startMusic(spans);
})
document.querySelector('.finish').addEventListener('click', function(){
stopMusic(myIntervals,spans);
})
body{
margin: 0;
}
span{
display: inline-block;
width: 50px;
height: 300px;
background-color: #000;
margin-right: 10px;
transition: 0.1s linear;
transform: scaleY(0.005);
transform-origin: bottom;
}
.equalizer{
position: absolute;
top: 50%;
left: 40%;
transform: translate(-50%,-50%);
}
<div class="equalizer">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<button class="start">START</button>
<button class="finish">FINISH</button>
I am learning JS and have created a carousel with a caption underneath.
How do I get the Prev/Next buttons to affect the caption as well as the image? I've tried combining the if statements in several ways but have failed miserably.
Relevant HTML:
<span id="prev" class="arrow">❮</span>
<div class="karussell" id="karussell">
<img class="karu" name="esislaid">
</div>
<span id="next" class="arrow">❯</span>
<div class="caption">
<h3 id="esikiri"></h3>
</div>
JS:
var p = 0;
var s = 0;
var esileht = [];
var aeg = 5000;
var kiri = [];
//Image List
esileht[0] = 'img/tooted/raamat/graafvanalinn2016.jpg';
esileht[1] = 'img/tooted/kaart/kaart_taskus_esipool.jpg';
esileht[2] = 'img/tooted/kaart/graafkaart_esikylg.jpg';
//Captions
kiri[0] = 'Raamat "Tallinn. Graafiline vanalinn"';
kiri[1] = 'Tallinna vanalinna graafiline kaart (suur formaat)';
kiri[2] = 'Tallinna vanalinna graafiline kaart (väike formaat)';
// Left and Right arrows
//Eelmine
function eelmine(){
if (p === 0){
p = esileht.length;
}
p = p - 1;
return esileht[p];
}
//Jargmine
function jargmine(){
p = p + 1;
p = p % esileht.length;
return esileht[p];
}
document.getElementById('prev').addEventListener('click', function (e){
document.querySelector('#karussell img').src = eelmine();
}
);
document.getElementById('next').addEventListener('click', function (e) {
document.querySelector('#karussell img').src = jargmine();
}
);
//Change Image
function changePilt (){
document.esislaid.src = esileht[p];
if(p < esileht.length -1){
p++;
} else {
p = 0;
}
setTimeout("changePilt()", aeg);
}
//Change Caption
function changeKiri(){
document.getElementById('esikiri').innerHTML = kiri[s];
if(s < kiri.length - 1){
s++;
}
else {
s = 0;
}
setTimeout('changeKiri()', aeg);
}
document.body.onload = function(){
changePilt();
changeKiri();
};
CSS, just in case:
.karussell {
position: relative;
width: 100%;
max-height: 600px;
overflow: hidden;
}
.arrow {
cursor: pointer;
position: absolute;
top: 40%;
width: auto;
color: #00A7E0;
padding: 16px;
font-weight: bold;
font-size: 18px;
border-radius: 3px;
transition: 0.6s ease;
}
#next {
right: 0;
}
#prev {
left: 0;
}
.arrow:hover {
background-color: rgba(0,0,0,0.8);
}
.caption {
text-align: center;
color: #00A7E0;
padding: 2px 16px;
}
.karu {
max-width: 75%;
animation-name: fade;
animation-duration: 2s;
}
#keyframes fade {
from {opacity: 0.4}
to {opacity: 1}
}
#media (max-width:767px){.karu{max-width: 95%;}}
I made a fiddle to try to illustrate (had to paste the js into the html tab to gt it to work for some reason): Fiddle
Really you just need to use the .innerHTML() feature and do exactly what you already have. Either create a eelmine2() function (or something like that) and call it again, grabbing the content from kiri[] or instead just return the p and use it in two places:
document.getElementById('prev').addEventListener('click', function (e){
document.querySelector('#karussell img').src = eelmine();
document.querySelector('#esikiri').innerHTML = eelmine2();
});
function eelmine2(){
if (p === 0){
p = kiri.length;
}
p = p - 1;
return kiri[p];
}
or
document.getElementById('prev').addEventListener('click', function (e){
var change = eelmine();
document.querySelector('#karussell img').src = esileht[change];
document.querySelector('#esikiri').innerHTML = kiri[change];
});
function eelmine(){
if (p === 0){
p = kiri.length;
}
p = p - 1;
return p;
}
This assumes your code is using the same global vars inside public functions that you have set up in your Fiddle. You should fix that to have variables passed into the functions before going live with all of this, but I'm not addressing that any further here.