Loop through array of elements to display on browser every X seconds - html

I am trying to make an array of text elements to display every X number of seconds. For example "Hello" would display and then after X seconds the text would change and display "I am 2 years old.
I am grabbing a DOM element and using .innerHTML to change the text with the dialog array elements created in javascript. I troubleshot the code and it seems like setTimeout is not working because it is not waiting every X seconds to display each array element (I have it for 5 seconds). I believe this is why I am getting only the last element to display instead of seeing each one display X number of second. Can someone help me out? I am also very new to coding.
Thanks.
Also It would be great if you can help me with creating the effect of fading the text elements in an out each time the text is changed.
var dialog = ['Hello,', 'My name is James', 'I am 2 years old.', 'I have a dog'];
function changeText() {
var timer = 0;
for (var i = 0; i < dialog.length; i++) {
setTimeout(document.getElementById('h1').innerHTML = dialog[i], timer);
timer = timer + 5000;
}
}
changeText();
<div id="h1">Hello</div>

First, you are not using the setTimeout() function correctly. Corrected version would be.
var inside for loop should be converted to let. Read why?
var dialog = ['Hello,', 'My name is James', 'I am 2 years old.', 'I have a dog'];
function changeText() {
var timer = 0;
// Use let instead of var
for (let i = 0; i < dialog.length; i++) {
setTimeout(() => {
document.getElementById('h1').innerHTML = dialog[i];
}, timer);
timer = timer + 5000;
}
}
changeText();
<div id="h1">Hello</div>
Its better to use setInterval() instead of setTimeout. Also initialize let elem = document.getElementById('h1'); at once not each time timer has been called.
var dialog = ['Hello,', 'My name is James', 'I am 2 years old.', 'I have a dog'];
let displayIndex = 0;
let elem = document.getElementById('h1');
let delay = 1000; // 1 second delay
setInterval(() => {
if (elem) {
elem.innerHTML = dialog[displayIndex];
}
// Move to the next item in dialog
displayIndex++;
// If display index goes out of index range, start again
if (displayIndex >= dialog.length) {
displayIndex = 0;
}
}, delay);
<div id="h1"></div>
Fading effect version
To get fading effect you need to change you html structure as well as javascript accordingly.
let displayIndex = 0;
let elems = $('#h1 > span');
let delay = 1000;
setInterval(() => {
elems.removeClass();
elems.addClass('hidden');
// Move to the next item in dialog
displayIndex++;
// If display index goes out of index range, start again
if (displayIndex >= elems.length) {
displayIndex = 0;
}
$(elems[displayIndex]).addClass('visible');
}, delay);
#h1 {
position: relative;
}
#h1 span {
position: absolute;
left: 0;
top: 0;
opacity: 0;
visibility: hidden;
transition: all 0.4s ease-in-out;
transition-delay: 0s;
}
span.visible {
opacity: 1 !important;
visibility: visible !important;
}
span.hidden {
opacity: 0;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="h1">
<span class="visible">Hello,</span>
<span class="hidden">My name is James</span>
<span class="hidden">I am 2 years old.</span>
<span class="hidden">I have a dog</span>
</div>

setTimeout accepts a callback, not plain code statements - pass it a function instead. Also use let instead of var so that each iteration has a separate binding for i:
var dialog = ['Hello,', 'My name is James', 'I am 2 years old.', 'I have a dog'];
function changeText() {
var timer = 0;
for (let i = 0; i < dialog.length; i++) {
setTimeout(() => document.getElementById('h1').innerHTML = dialog[i], timer);
timer = timer + 1000;
}
}
changeText();
<div id="h1">Hello</div>

Check docs for setTimeout. First argument needs to be anonymous function or function. Try to wrap your first argument in setTimeout in 'function() {}' .

Related

data-rotate and data-period attribute in <span> tag [duplicate]

This question already has an answer here:
what are data-* HTML attributes?
(1 answer)
Closed 7 months ago.
I am trying to make a typing animation. I found a tutorial to do it but I am curious what is the purpose of the data-rotate and data-period attribute show in the span tag.
<span
className="font-mono text-3xl sm:text-4xl text-gray-100"
data-period="1000"
data-rotate='[ "Web Developer", "Web Designer", "UI/UX Designer" ]'
>
<span className="flex-wrap">{text}</span>
<span className="box-border inline-block w-1 h-12 -mb-2 bg-gray-500 opacity-75 animate-cursor"></span>
</span>
data-* attributes are usually used to store data in HTML, which can then be fetched inside JavaScript.
Here's an example in where JavaScript reads the data-* attributes and fallbacks (i.e: for speed) to a predefined default value:
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
const typer = (el) => {
const texts = JSON.parse(el.dataset.rotate);
const period = +el.dataset.period || 1000;
const speed = +el.dataset.speed || 200;
const textsTot = texts.length;
let tx = 0;
let ch = 0;
let chTot = 0;
let text = ""
const typeIt = () => {
if (ch > chTot) return setTimeout(typeText, period);
el.textContent = text.substring(0, ch++);
setTimeout(typeIt, rand(Math.min(60, speed - 80), speed + 80));
};
const typeText = () => {
ch = 0;
text = texts[tx];
chTot = text.length;
typeIt();
tx += 1;
tx %= texts.length;
};
typeText();
};
document.querySelectorAll("[data-rotate]").forEach(typer);
/* PULSATING CARET */
[data-rotate]:after {
content: "";
display: inline-block;
vertical-align: middle;
width: 2px;
height: 1em;
background: #000;
animation: caretPulsate 1s linear infinite;
}
#keyframes caretPulsate {
0% { opacity: 1; }
50% { opacity: 1; }
60% { opacity: 0; }
90% { opacity: 0; }
}
<span data-period="1000" data-rotate='[ "Web Developer", "Web Designer", "UI/UX Designer" ]'></span>
<br>
<span data-period="2000" data-speed="300" data-rotate='[ "Welcome to...", "Stack", "Overflow" ]'></span>
in where the above is basically a remake (from jQuery to pure JS) of: Typing animated text
Basically data-* tags are tags that have no effect on the visual representation. You can use it to store extra information about the element or can use it for identification purposes or even for automation purposes
The exact purpose of the data-* tags used in the specific example you have shared cannot be understood unless we understand the context and where this is used in the code.
Refer data-* docs

How to Run a loop in jQuery to reread defined array sets?

I'm trying to build a reoccurring logic using jQuery & Arrays, but running into issues with getting the code to rerun. I'd like the code to read the next array within the matrix once the user clicks "Next Button." Currently, the logic isn't progressing past the first array, but I'm not sure why! Any help is appreciated.
<body>
<div id="wordZone"></div>
<ul id="choices">
<li></li>
<li></li>
<li></li>
</ul>
Next Word
<div class="win">
You've Won!
</div>
<div class="lose">
You've Lost!
</div>
</body>
let score = 0;
const dict = {
officeSpeak: ["Hi there!", "Regards", "Per my last email"],
counter: 0,
};
const matrix = [
["Hi there!", "Sup dude", "Salutations"],
["Regards", "Hasta luego", "Byebye"],
["Per my last email","oopsie!","some other option here, you're writing this game"],
];
const wordZone = $("#wordZone");
const choiceButtons = $("#choices li");
function buildOptions() {
let turnChoices = matrix[0];
//hide next word button - DONE
$("#next").hide();
for (let i = 0, ii = turnChoices.length; i < ii; i++) {
let choiceWord = turnChoices[i];
let choiceButton = $(choiceButtons[i]);
let btnClass = "incorrect";
choiceButton.text(choiceWord);
if (dict.officeSpeak.indexOf(choiceWord) != -1) {
btnClass = "correct";
}
choiceButton.addClass(btnClass);
}
}
buildOptions();
function onClickWord(e) {
console.log($(this));
$("#choices li").addClass("active");
$(".correct").css("color", "green");
$(".incorrect").css("color", "red");
if ($(this).hasClass("correct")) {
score++;
console.log(score);
}
$("#next").show();
let turnChoices = matrix[+1];
}
$("#choices li").click(onClickWord);
$("#next").click(buildOptions);
function finalScore() {
$("#wordZone").show(score);
if (finalScore >= 2) {
$("#wordZone").addClass("win");
$("#win").show();
} else {
$("#wordZone").addClass("lose");
$("#lose").show();
}
}
finalScore();
//final score - HELP
I tried creating a for loop where the matrix counter should increment by 1 each time the program is ran, expecting that the code would then move onto the second line of the array.
It tooks a while to find a running way. Here my suggestion:
First: create a variable with global scope
let matrixcounter = 0;
Second: Add an argument to function buildOptions and pass it to your array matrix[]:
function buildOptions(wordCounter) {
let turnChoices = matrix[wordCounter];
...
}
This last change needs another important change, based on How can I pass arguments to event handlers in jQuery? :
So replace $("#next").click(buildOptions); with
$("#next").click(function() {
matrixcounter++; //means matrixcounter = matrixcounter + 1;
buildOptions(matrixcounter);
});
A running example: https://jsfiddle.net/reporter/rtqgveuo/1/

initializing a dynamic background

What my code does:
stays blank for 3 seconds
displayes index 1 of my list of image
loops through the list and restarts at index 0
<div id="background" class="text-center background">
<script type = "text/javascript">
var background = document.getElementById("background");
var currentPos = 0;
var imagesb = ["/images/indeximg0.jpg", "/images/indeximg11.jpg", "/images/indeximg33.jpg", "/images/indeximg44.jpg", "/images/indeximg55.jpg"], i = 0;
function changeimage()
{
if (++currentPos >= imagesb.length)
currentPos = 0;
background.style.backgroundImage = "url(" + imagesb[currentPos] + ")";
}
setInterval(changeimage, 3000);
</script>
</div>
What I want it to do:
no 3 second blank background delay
starts with index 0
What I tried:
I set currentPos = 4
this fixed the issue with the first image being displayed but the 3 second delay was still there. I also don't like this way of doing it because I would have to manually change currentPos if i add or remove images to the list
I tried to initialize my background before the first div to fix the 3 second back background with he following code:
<script type = "text/javascript">
background.style.backgoundImage= "url(/images/indeximg0.jpg)";
</script>
nothing changed. still had the 3 second delay
You should
Call the function once before the setInterval() and
When doing ++currentPos you are actually incrementing the variable so the first time you set the background, the value is already 1. The easiest way to change that is to set the background before your if test.
var background = document.getElementById("background");
var currentPos = 0;
var imagesb = ["https://via.placeholder.com/100", "https://via.placeholder.com/200", "https://via.placeholder.com/300"];
function changeimage() {
background.style.backgroundImage = "url(" + imagesb[currentPos] + ")";
if ((++currentPos) >= imagesb.length) {
currentPos = 0;
}
}
changeimage();
setInterval(changeimage, 1000);
body,
html {
height: 100%;
width: 100%;
margin: 0;
}
.background {
height: 100%;
width: 100%;
}
<div id="background" class="text-center background"></div>

How to select all underlined text in a paragraph

I'm trying to create a google apps script that will format certain parts of a paragraph. For example, text that is underlined will become bolded/italicized as well.
One docs add-on I have tried has a similar feature: https://imgur.com/a/5Cw6Irn (this is exactly what I'm trying to achieve)
How can I write a function that will select a certain type of text and format it?
**I managed to write a script that iterates through every single letter in a paragraph and checks if it's underlined, but it becomes extremely slow as the paragraph gets longer, so I'm looking for a faster solution.
function textUnderline() {
var selectedText = DocumentApp.getActiveDocument().getSelection();
if(selectedText) {
var elements = selectedText.getRangeElements();
for (var index = 0; index < elements.length; index++) {
var element = elements[index];
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
var textLength = text.getText().length;
//For every single character, check if it's underlined and then format it
for (var i = 0; i < textLength; i++) {
if(text.isUnderline(i)) {
text.setBold(i, i, true);
text.setBackgroundColor(i,i,'#ffff00');
} else {
text.setFontSize(i, i, 8);
}
}
}
}
}
}
Use getTextAttributeIndices:
There is no need to check each character in the selection. You can use getTextAttributeIndices() to get the indices in which the text formatting changes. This method:
Retrieves the set of text indices that correspond to the start of distinct text formatting runs.
You just need to iterate through these indices (that is, check the indices in which text formatting changes), which are a small fraction of all character indices. This will greatly increase efficiency.
Code sample:
function textUnderline() {
var selectedText = DocumentApp.getActiveDocument().getSelection();
if(selectedText) {
var elements = selectedText.getRangeElements();
for (var index = 0; index < elements.length; index++) {
var element = elements[index];
if(element.getElement().editAsText) {
var text = element.getElement().editAsText();
var textRunIndices = text.getTextAttributeIndices();
var textLength = text.getText().length;
for (let i = 0; i < textRunIndices.length; i++) {
const startOffset = textRunIndices[i];
const endOffset = i + 1 < textRunIndices.length ? textRunIndices[i + 1] - 1 : textLength - 1;
if (text.isUnderline(textRunIndices[i])) {
text.setBold(startOffset, endOffset, true);
text.setBackgroundColor(startOffset, endOffset,'#ffff00');
} else {
text.setFontSize(startOffset, endOffset, 8);
}
}
}
}
}
}
Reference:
getTextAttributeIndices()
Based on the example shown in the animated gif, it seems your procedure needs to
handle a selection
set properties if the selected region is of some format (e.g. underlined)
set properties if the selected region is NOT of some format (e.g. not underlined)
finish as fast as possible
and your example code achieves all these goals expect the last one.
The problem is that you are calling the text.set...() functions at each index position. Each call is synchronous and blocks the code until the document is updated, thus your run time grows linearly with each character in the selection.
My suggestion is to build up a collection of subranges from the selection range and then for each subrange use text.set...(subrange.start, subrange.end) to apply the formatting. Now the run time will be dependent on chunks of characters, rather than single characters. i.e., you will only update when the formatting switches back and forth from, in your example, underlined to not underlined.
Here is some example code that implements this subrange idea. I separated the specific predicate function (text.isUnderline) and specific formatting effects into their own functions so as to separate the general idea from the specific implementation.
// run this function with selection
function transformUnderlinedToBoldAndYellow() {
transformSelection("isUnderline", boldYellowOrSmall);
}
function transformSelection(stylePredicateKey, stylingFunction) {
const selectedText = DocumentApp.getActiveDocument().getSelection();
if (!selectedText) return;
const getStyledSubRanges = makeStyledSubRangeReducer(stylePredicateKey);
selectedText.getRangeElements()
.reduce(getStyledSubRanges, [])
.forEach(stylingFunction);
}
function makeStyledSubRangeReducer(stylePredicateKey) {
return function(ranges, rangeElement) {
const {text, start, end} = unwrapRangeElement(rangeElement);
if (start >= end) return ranges; // filter out empty selections
const range = {
text, start, end,
styled: [], notStyled: [] // we will extend our range with subranges
};
const getKey = (isStyled) => isStyled ? "styled" : "notStyled";
let currentKey = getKey(text[stylePredicateKey](start));
range[currentKey].unshift({start: start});
for (let index = start + 1; index <= end; ++index) {
const isStyled = text[stylePredicateKey](index);
if (getKey(isStyled) !== currentKey) { // we are switching styles
range[currentKey][0].end = index - 1; // note end of this style
currentKey = getKey(isStyled);
range[currentKey].unshift({start: index}); // start new style range
}
}
ranges.push(range);
return ranges;
}
}
// a helper function to unwrap a range selection, deals with isPartial,
// maps RangeElement => {text, start, end}
function unwrapRangeElement(rangeElement) {
const isPartial = rangeElement.isPartial();
const text = rangeElement.getElement().asText();
return {
text: text,
start: isPartial
? rangeElement.getStartOffset()
: 0,
end: isPartial
? rangeElement.getEndOffsetInclusive()
: text.getText().length - 1
};
}
// apply specific formatting to satisfy the example
function boldYellowOrSmall(range) {
const {text, start, end, styled, notStyled} = range;
styled.forEach(function setTextBoldAndYellow(range) {
text.setBold(range.start, range.end || end, true);
text.setBackgroundColor(range.start, range.end || end, '#ffff00');
});
notStyled.forEach(function setTextSmall(range) {
text.setFontSize(range.start, range.end || end, 8);
});
}

Animate css progress bar without jumping between updates?

I'm using this on my site …
<progress id='video-progress' min='0' max='100' value=''></progress>
This is the entire styling of the element …
#video-progress {
-webkit-appearance: none;
border: none;
position:fixed;
bottom:0;
left:0;
height:3px;
width:100%;
z-index:1;
}
So all it does is move from 0 to 100% screen width on the bottom of the page.
The progress bar is updated via Javascript.
However since my video is only 30 seconds long, the single steps of updates are executed as "jumps" for the progress bar. So there is no smooth motion of the bar.
Any idea how I could animate the progress bar or smooth it between the updated steps?
UPDATE:
JavaScript … 
function updateProgressBar() {
var progressBar = document.getElementById('video-progress');
var percentage = Math.floor((100 / fmVideo.duration) * fmVideo.currentTime);
progressBar.value = percentage;
}
You could animate it by incrementing its value every 15 millisecond using setInterval and stop incrementing if the value is greater than percentage using clearInterval.
This code extracts the current value and increments it until it reaches the percentage value.
Note: percentage is manually set to 70.
var progressBar = document.getElementById('video-progress');
function updateProgressBar() {
var percentage = 70;
var curr = progressBar.value;
var update = setInterval(function() {
if (curr > percentage) {
clearInterval(update);
}
progressBar.value = curr++;
}, 15)
}
updateProgressBar();
#video-progress {
-webkit-appearance: none;
border: none;
position: fixed;
bottom: 0;
left: 0;
height: 3px;
width: 100%;
z-index: 1;
}
<progress id='video-progress' min='0' max='100' value=''></progress>
This works perfectly for me!
function smoothProgress(e) {
var id = $("#"+e.data.id),
dur = 5000,
seq = 100,
max = parseInt( id.attr("max"), 10),
chunk = max / dur * seq,
loop = setInterval(function() {
if( id.val() < max )
id.val( id.val() + chunk );
else
clearInterval(loop);
}
}, seq);
}
$(document).ready(function() {
$("#launch").on("click", {id: $("progress").attr("id")}, smoothProgress);
});
Of course, you can adjust the dur parameter or retrieve it dynamically based on your video's duration, as well as the seq parameter (the lower, the smoother).
Here is a fiddle for demo.