There is a word-translation card page which includes:
A 3-second countdown that runs immediately after the page loads and when you move to a new word after press the 'Correct' button.
Word and his translate which appears after 'Excellent' button pressed. This button stops and hide the counter.
Button 'Show answer' which appears if 'Excellent' didn't pressed in 3 sec.
Buttons 'Wrong' and 'Correct' which appears if 'Excellent' or 'Show answer' buttons are pressed.
The problem is how the countdown works (unstable). Sometimes it doesn't restart after clicking "Correct". I tried manage countdown in separate function, but this approach provides much more issues. So now there is a timer that is called globally and a timer that is called when you click on the "Correct" button.I think the problem is near timerId. I will be glad to any comments and ideas on the code.
<div class="deck-container">
<div class="words-container">
<h2 id="primary-word"></h2>
<h2 id="secondary-word"></h2>
</div>
<div class="btn-container">
<p id="timer-count">3</p>
<button id="btn-excellent" onClick="excellent()">Excellent!</button>
<button id="btn-show-answer" onClick="showAnswerF()">Show Answer</button>
<button id="btn-wrong">Wrong</button>
<button id="btn-correct" onClick="correctF()">Correct</button>
</div>
</div>
let currentWord = 0
let timerCount = 3
let fetched_data = {}
async function getDeck () {
let response = await fetch('/api/deck_words/2/', {
method: 'get',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
}
}
)
let data = await response.json()
let primaryElement = document.getElementById('primary-word')
primaryElement.innerText = await data[currentWord]['primary_word']
let secondaryElement = document.getElementById('secondary-word')
secondaryElement.innerText = await data[currentWord]['secondary_word']
return data
}
fetched_data = getDeck()
const getData = async () => {
return await getDeck()
}
data = getData();
function countDecrease() {
timerCount --
if (timerCount > 0) {
document.getElementById("timer-count").innerHTML = timerCount
} else {
hideExcellent()
}
}
function hideExcellent() {
showElement(true,'btn-excellent')
showElement(true,'timer-count')
showElement(false,'btn-show-answer')
}
let timerId = setInterval(() => countDecrease(), 1000)
setTimeout(() => {
clearInterval(timerId)
}, 3000)
function showElement(showProperty, elementClass) {
showProperty = !showProperty
let element = document.getElementById(elementClass)
element.style.display = (showProperty === true) ? "inline-block" : "none";
}
function showAnswerF() {
showElement(true,'btn-show-answer')
showElement(false,'secondary-word')
showElement(false,'btn-wrong')
showElement(false,'btn-correct')
}
function excellent() {
showElement(true,'timer-count')
showElement(true,'btn-excellent')
showElement(false,'btn-wrong')
showElement(false,'btn-correct')
showElement(false,'secondary-word')
clearInterval(timerId)
timerCount = 3
}
function correctF() {
currentWord++
const changeWords = () => {
fetched_data.then((data) => {
document.getElementById('primary-word').innerText = data[currentWord]['primary_word']
document.getElementById('secondary-word').innerText = data[currentWord]['secondary_word']
document.getElementById("timer-count").innerText = '3'
timerCount = 3
timerId = setInterval(() => countDecrease(), 1000)
setTimeout(() => {
clearInterval(timerId)
}, 3000)
})
}
changeWords()
let countElement = document.getElementById('timer-count')
countElement.style.display = "block"
showElement(false,'btn-excellent')
showElement(true,'btn-wrong')
showElement(true,'btn-correct')
showElement(true,'secondary-word')
}
I think this would be better handled with an async function, maybe like so.
const timerCount = document.querySelector("#timer-count")
const reset = document.querySelector("button")
function delay(ms) {
return new Promise(res => setTimeout(res, ms))
}
async function countDown(signal) {
const aborted = new Promise(resolve => signal.addEventListener("abort", resolve))
for (let i = 10; i >= 0 && signal?.aborted != true; --i) {
timerCount.textContent = i
await Promise.race([delay(1000), aborted])
}
timerCount.textContent = ""
}
async function startCountdown() {
const ac = new AbortController()
const abort = () => ac.abort()
reset.addEventListener("click", abort, { once: true })
reset.textContent = "Cancel"
await countDown(ac.signal)
reset.removeEventListener("click", abort)
reset.addEventListener("click", startCountdown, { once: true })
reset.textContent = "Start"
}
startCountdown()
<p id="timer-count"></p>
<button>Start</button>
Alternatively, you might want to model the countdown as an object that implements EventTarget.
const timerCount = document.querySelector("#timer-count")
const btn = document.querySelector("button")
class Timer extends EventTarget {
#value; #tick_rate; #enabled; #interval_handle
constructor(tick_rate = 1000) {
super()
this.#value = 0
this.#tick_rate = tick_rate
this.#enabled = false
this.#interval_handle = null
}
get value() {
return this.#value
}
set value(value) {
this.#value = value
this.dispatchEvent(new Event("update"))
}
get tick_rate() {
return this.#tick_rate
}
get enabled() {
return this.#enabled
}
start() {
if (this.#enabled) return
this.#enabled = true
this.#interval_handle = setInterval(Timer.#tick, this.#tick_rate, this)
this.dispatchEvent(new Event("start"))
}
stop() {
if (!this.#enabled) return
this.#enabled = false
clearInterval(this.#interval_handle)
this.dispatchEvent(new Event("stop"))
}
static #tick(timer) {
timer.value = Math.max(0, timer.value - 1)
if (timer.value == 0) timer.stop()
}
}
const timer = new Timer()
timer.addEventListener("start", function() {
btn.textContent = "Stop"
})
timer.addEventListener("stop", function() {
timerCount.textContent = ""
btn.textContent = "Start"
})
timer.addEventListener("update", function() {
timerCount.textContent = timer.value
})
btn.addEventListener("click", function() {
if (timer.enabled == false) {
timer.value = 5
timer.start()
} else {
timer.stop()
}
})
<p id="timer-count"></p>
<button>Start</button>
Here's a countdown. The first count is after 1 second.
let count = 3;
let timer = [];
const start = () => {
new Array(count).fill(true).forEach((_,i) => {
timer.push(setTimeout(() => console.log('count',count - i),(i+1) * 1000))
})
}
const stop = () => timer.forEach(clearTimeout);
<button onclick="stop()">STOP</button>
<button onclick="start()">START</button>
Related
The program runs a loop continuously until I press a button that pauses the loop. When I press the button a second time it should start running continuously. What happens now is on the first press it stops. Then the second press runs the loop once and stops again. When I press a third time, however, it does start running continuously again. How do I fix this?
const timer = ms => new Promise(res => setTimeout(res, ms));
async function program_loop() {
for (word_iterator = 0; word_iterator < some_length; word_iterator++) {
if (stop == true) await pauser();
//mini example
word.innerHTML = text_words[word_iterator]
await timer(200);
}
}
function pauser() {
return new Promise(resolve => {
let playbuttonclick = function () {
if (stop == false) {
stop = true;
pausebutton.innerHTML = 'Start'
}
else if (stop == true) {
stop = false;
pausebutton.innerHTML = 'Stop'
}
resolve('resolved')
}
pausebutton.addEventListener('click', playbuttonclick)
})
}
I've tried removing the if (stop==true) await pauser() but this ruins my whole program. Since then I need to press the button to advance the loop.
Each time you call pauser() , you add a new button click handler. Therefore, after several cycles, several handlers are triggered at once. Install the handler once or remove the old one each time.
Try this example:
const pausebutton = document.getElementById('pauseButton');
let stop = false;
const some_length = 500;
let promiseResolve;
const handleToggleStop = () => {
stop = !stop;
promiseResolve && promiseResolve();
};
pausebutton.addEventListener('click', handleToggleStop);
const timer = ms => new Promise(res => setTimeout(res, ms));
async function program_loop() {
for (word_iterator = 0; word_iterator < some_length; word_iterator++) {
if (stop) {
await new Promise((resolve) => {
promiseResolve = resolve;
});
}
//mini example
await timer(1000);
}
}
program_loop();
You don't really need a pauser function. You can do as follows;
let timer = ms => new Promise(res => setTimeout(res, ms)),
word = document.getElementById("word"),
bttn = document.getElementById("pause-button"),
prms = Promise.resolve(),
rslv;
async function program_loop() {
for (let word_iterator = 0; word_iterator < 1e6; word_iterator++) {
await prms;
word.innerText = word_iterator;
await timer(200);
}
}
bttn.addEventListener("click", _e => {
if (rslv) {
rslv();
rslv = null;
bttn.innerText = "STOP";
} else {
prms = new Promise(v => rslv = v);
bttn.innerText = "START";
}
});
program_loop();
<button id="pause-button">STOP</button>
<div id="word"></div>
I wanted to make a FPS Unlimiter Userscript for gpop.io because the current fps cap is 60 and wanted to increase to 240fps and I don't understand JavaScript well enough to know what I am doing and am requesting help, The Section Of pageScript.js, I have The pageScript.js in the URL and The Code Below
!function pageScript() {
let speedConfig = {
speed: 1.0,
cbSetIntervalChecked: true,
cbSetTimeoutChecked: true,
cbPerformanceNowChecked: true,
cbDateNowChecked: true,
cbRequestAnimationFrameChecked: false,
};
const emptyFunction = () => {};
const originalClearInterval = window.clearInterval;
const originalclearTimeout = window.clearTimeout;
const originalSetInterval = window.setInterval;
const originalSetTimeout = window.setTimeout;
const originalPerformanceNow = window.performance.now.bind(
window.performance
);
const originalDateNow = Date.now;
const originalRequestAnimationFrame = window.requestAnimationFrame;
let timers = [];
const reloadTimers = () => {
console.log(timers);
const newtimers = [];
timers.forEach((timer) => {
originalClearInterval(timer.id);
if (timer.customTimerId) {
originalClearInterval(timer.customTimerId);
}
if (!timer.finished) {
const newTimerId = originalSetInterval(
timer.handler,
speedConfig.cbSetIntervalChecked
? timer.timeout / speedConfig.speed
: timer.timeout,
...timer.args
);
timer.customTimerId = newTimerId;
newtimers.push(timer);
}
});
timers = newtimers;
};
window.addEventListener("message", (e) => {
if (e.data.command === "setSpeedConfig") {
speedConfig = e.data.config;
reloadTimers();
}
});
window.postMessage({ command: "getSpeedConfig" });
window.clearInterval = (id) => {
originalClearInterval(id);
timers.forEach((timer) => {
if (timer.id == id) {
timer.finished = true;
if (timer.customTimerId) {
originalClearInterval(timer.customTimerId);
}
}
});
};
window.clearTimeout = (id) => {
originalclearTimeout(id);
timers.forEach((timer) => {
if (timer.id == id) {
timer.finished = true;
if (timer.customTimerId) {
originalclearTimeout(timer.customTimerId);
}
}
});
};
window.setInterval = (handler, timeout, ...args) => {
console.log("timeout ", timeout);
if (!timeout) timeout = 0;
const id = originalSetInterval(
handler,
speedConfig.cbSetIntervalChecked ? timeout / speedConfig.speed : timeout,
...args
);
timers.push({
id: id,
handler: handler,
timeout: timeout,
args: args,
finished: false,
customTimerId: null,
});
return id;
};
window.setTimeout = (handler, timeout, ...args) => {
if (!timeout) timeout = 0;
return originalSetTimeout(
handler,
speedConfig.cbSetTimeoutChecked ? timeout / speedConfig.speed : timeout,
...args
);
};
// performance.now
(function () {
let performanceNowValue = null;
let previusPerformanceNowValue = null;
window.performance.now = () => {
const originalValue = originalPerformanceNow();
if (performanceNowValue) {
performanceNowValue +=
(originalValue - previusPerformanceNowValue) *
(speedConfig.cbPerformanceNowChecked ? speedConfig.speed : 1);
} else {
performanceNowValue = originalValue;
}
previusPerformanceNowValue = originalValue;
return Math.floor(performanceNowValue);
};
})();
// Date.now
(function () {
let dateNowValue = null;
let previusDateNowValue = null;
Date.now = () => {
const originalValue = originalDateNow();
if (dateNowValue) {
dateNowValue +=
(originalValue - previusDateNowValue) *
(speedConfig.cbDateNowChecked ? speedConfig.speed : 1);
} else {
dateNowValue = originalValue;
}
previusDateNowValue = originalValue;
return Math.floor(dateNowValue);
};
})();
// requestAnimationFrame
(function () {
let dateNowValue = null;
let previusDateNowValue = null;
const callbackFunctions = [];
const callbackTick = [];
const newRequestAnimationFrame = (callback) => {
return originalRequestAnimationFrame((timestamp) => {
const originalValue = originalDateNow();
if (dateNowValue) {
dateNowValue +=
(originalValue - previusDateNowValue) *
(speedConfig.cbRequestAnimationFrameChecked
? speedConfig.speed
: 1);
} else {
dateNowValue = originalValue;
}
previusDateNowValue = originalValue;
const dateNowValue_MathFloor = Math.floor(dateNowValue);
const index = callbackFunctions.indexOf(callback);
let tickFrame = null;
if (index == -1) {
callbackFunctions.push(callback);
callbackTick.push(0);
callback(dateNowValue_MathFloor);
} else if (speedConfig.cbRequestAnimationFrameChecked) {
tickFrame = callbackTick[index];
tickFrame += speedConfig.speed;
if (tickFrame >= 1) {
while (tickFrame >= 1) {
callback(dateNowValue_MathFloor);
window.requestAnimationFrame = emptyFunction;
tickFrame -= 1;
}
window.requestAnimationFrame = newRequestAnimationFrame;
} else {
window.requestAnimationFrame(callback);
}
callbackTick[index] = tickFrame;
} else {
callback(dateNowValue_MathFloor);
}
});
};
window.requestAnimationFrame = newRequestAnimationFrame;
})();
}()
//# sourceURL=pageScript.js
I was trying to read through the code to find what was capping the fps but I wasn't able to find anything that was easy enough for me to understand.
I have a timer, I want to do something when textContent of div element === 0;
JavaScript:
function createTimer() {
const display = document.createElement('div');
display.classList.add('display');
display.id = 'display';
display.textContent = '5';
return display;
};
let intervalID;
function startTimer() {
resetTimer();
intervalID = setInterval(() => {
let displayTimer = document.getElementById('display');
let displayNumber = parseInt(displayTimer.textContent);
if (displayTimer.textContent !== '0') displayTimer.textContent = displayNumber - 1;
}, 1000);
};
function resetTimer() {
clearInterval(intervalID);
};
function someFunc() {
// here is a lot of code stuff and timer is working correctly
const timer = createTimer();
};
This is what i tried:
function someFunc() {
const timer = createTimer();
timer.addEventListener('input', () => {
if (timer.textContent === '0') {
console.log(true);
};
});
};
As far as I understood correctly, by creating input event on timer, I always get timer.textContent when it changes, right? I keep track of all the changes that's happening in this div element.
Nothing happens.
Keep track of your count as a number in JavaScript. This creates clarity in what the count is and how it can be manipulated.
Inside the setInterval callback, check if the count is 0. The interval is already there and will run every second, so it makes sense to check it in that place.
The example below is a modified version of your script with the suggested implementation. Since you're returning the display element from the createTimer function I've decided to reuse it in the startTimer function. That way you don't have to select the element from the DOM, as you already have a reference to the element.
As a bonus an extra argument which can be callback function to do something whenever the timer ends.
let count = 5;
let intervalID;
function createTimer() {
const display = document.createElement('div');
display.classList.add('display');
display.id = 'display';
display.textContent = '5';
return display;
};
function resetTimer() {
clearInterval(intervalID);
};
function startTimer(timer, onFinish) {
resetTimer();
intervalID = setInterval(() => {
if (count !== 0) {
count--;
}
if (count === 0) {
resetTimer();
if (typeof onFinish === 'function') {
onFinish();
}
}
timer.textContent = count;
}, 1000);
};
function someFunc() {
const timer = createTimer();
document.body.append(timer);
startTimer(timer, () => {
console.log('Done');
});
};
someFunc();
The input event fires when the value of an <input>, <select>, or <textarea> element has been changed by user. It does not fire when setting the textContent programmatically.
You can use the MutationObserver API to observe the node change.
const timer = createTimer();
new MutationObserver(() => {
let timer = document.getElementById('display');
if (timer.textContent === '0') {
console.log(true);
};
}).observe(timer, { childList: true });
You could implement the timer with the help of async/await code. It makes the code a lot more cleaner, by having your start and end code in the same function.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
function createTimer(name) {
const element = document.createElement("div");
element.classList.add("timer");
document.body.appendChild(element);
element.textContent = name+": -";
return async function(seconds) {
for (let second = seconds; second >= 0; second--) {
element.textContent = name+": "+second;
if (second > 0) {
await wait(1000);
}
}
};
}
async function someFunc() {
const timer = createTimer("First timer");
console.log("first timer started", new Date());
await timer(10);
console.log("timer ended", new Date());
await wait(2500);
console.log("first timer started again", new Date());
await timer(5);
console.log("first timer ended again", new Date());
}
async function someOtherFunc() {
const timer = createTimer("Second timer");
console.log("second timer started", new Date());
await timer(20);
console.log("second timer ended", new Date());
}
someFunc();
someOtherFunc();
.timer {
text-align: center;
font-size: 2em;
font-family: sans-serif;
}
Okay I simply want to know what I have to add to my Vanilla JavaScript code to load more content once the bottom of the scrolled div/page (wholewrapper) is reached. The code I have included works pretty well as I'm a beginner at vanilla JavaScript. But I need help understanding how to structure onScrollEvents with HttpRequest.
Here is my code:
Vanilla JavaScript:
let page = 1;
const last_page = 10;
const pixel_offset = 200;
const throttle = (callBack, delay) => {
let withinInterval;
return function () {
const args = arguments;
const context = this;
if (!withinInterval) {
callBack.call(context, args);
withinInterval = true;
setTimeout(() => (withinInterval = false), delay);
}
};
};
const httpRequestWrapper = (method, URL) => {
return new Promise((resolve, reject) => {
const xhr_obj = new XMLHttpRequest();
xhr_obj.responseType = "json";
xhr_obj.open(method, URL);
xhr_obj.onload = () => {
const data = xhr_obj.response;
resolve(data);
};
xhr_obj.onerror = () => {
reject("failed");
};
xhr_obj.send();
});
};
const getData = async (page_no = 1) => {
const data = await httpRequestWrapper("GET", `myXML.json`);
const { results } = data;
populateUI(results);
};
let handleLoad;
let trottleHandler = () => {
throttle(handleLoad.call(this), 1000);
};
document.addEventListener("DOMContentLoaded", () => {
getData(1);
window.addEventListener("scroll", trottleHandler);
});
handleLoad = () => {
if (
window.innerHeight + window.scrollY >=
document.body.offsetHeight - pixel_offset
) {
page = page + 1;
if (page <= last_page) {
window.removeEventListener("scroll", trottleHandler);
getData(page).then((res) => {
window.addEventListener("scroll", trottleHandler);
});
}
}
};
const populateUI = (data) => {
const container = document.querySelector(".whole_wrapper");
data &&
data.length &&
data.map((each, index) => {
const { photoVar } = image;
const { textVar } = text;
const { noteVar } = note;
container.innerHTML += `
<div class="each_bubble">
<div class="imageContainer">
<img src="${photoVar}" alt="" />
</div>
<div class="right_contents_container">
<div class="text_field">${textVar}</div>
<div class="note_field">${noteVar}</div>
</div>
</div>
`;
});
};
I have a slider and I want to add auto play every 3 seconds. I tried to use SetInterval but nothing happened.
Also I would like to remove the code for removing elements from the slider. I want to remove all the controls also and have only the slider changing image every 3 seconds.
this is the slider code
const galleryContainer = document.querySelector('.gallery-container');
const galleryControlsContainer = document.querySelector('.gallery-controls');
const galleryControls = ['Предидущий', '', 'Следующий'];
const galleryItems = document.querySelectorAll('.gallery-item');
class Carousel {
constructor(container, items, controls) {
this.carouselContainer = container;
this.carouselControls = controls;
this.carouselArray = [...items];
}
updateGallery() {
this.carouselArray.forEach(el => {
el.classList.remove('gallery-item-1');
el.classList.remove('gallery-item-2');
el.classList.remove('gallery-item-3');
el.classList.remove('gallery-item-4');
el.classList.remove('gallery-item-5');
});
this.carouselArray.slice(0, 5).forEach((el, i) => {
el.classList.add(`gallery-item-${i+1}`);
});
}
setCurrentState(direction) {
if (direction.className == 'gallery-controls-previous') {
this.carouselArray.unshift(this.carouselArray.pop());
} else {
this.carouselArray.push(this.carouselArray.shift());
}
this.updateGallery();
}
setControls() {
this.carouselControls.forEach(control => {
galleryControlsContainer.appendChild(document.createElement('button')).className = `gallery-controls-${control}`;
document.querySelector(`.gallery-controls-${control}`).innerText = control;
});
}
useControls() {
const triggers = [...galleryControlsContainer.childNodes];
triggers.forEach(control => {
control.addEventListener('click', e => {
e.preventDefault();
if (control.className == 'gallery-controls-add') {
const newItem = document.createElement('img');
const latestItem = this.carouselArray.length;
const latestIndex = this.carouselArray.findIndex(item => item.getAttribute('data-index') == this.carouselArray.length)+1;
Object.assign(newItem,{
className: 'gallery-item',
src: `http://fakeimg.pl/300/?text=${this.carouselArray.length+1}`
});
newItem.setAttribute('data-index', this.carouselArray.length+1);
this.carouselArray.splice(latestIndex, 0, newItem);
document.querySelector(`[data-index="${latestItem}"]`).after(newItem);
this.updateGallery();
} else {
this.setCurrentState(control);
}
});
});
}
}
const exampleCarousel = new Carousel(galleryContainer, galleryItems, galleryControls);
exampleCarousel.setControls();
exampleCarousel.useControls();
See the below code, I have added auto slide functionality with interval time 5 seconds.
If you want to change the interval, Please update the setInterval time in the "constructor" and "setCurrentState" functions.
I have removed the controls. To remove the controls, we need to comment the last 2 lines
//exampleCarousel.setControls();
//exampleCarousel.useControls();
const galleryContainer = document.querySelector('.gallery-container');
const galleryControlsContainer = document.querySelector('.gallery-controls');
const galleryControls = ['Предидущий', '', 'Следующий'];
const galleryItems = document.querySelectorAll('.gallery-item');
class Carousel {
constructor(container, items, controls) {
this.carouselContainer = container;
this.carouselControls = controls;
this.carouselArray = [...items];
this.mySlideInterval = null;
this.mySlideInterval = setInterval(
this.autoSlide.bind(this),
5000
);
}
autoSlide() {
this.carouselArray.push(this.carouselArray.shift());
this.updateGallery();
}
updateGallery() {
this.carouselArray.forEach(el => {
el.classList.remove('gallery-item-1');
el.classList.remove('gallery-item-2');
el.classList.remove('gallery-item-3');
el.classList.remove('gallery-item-4');
el.classList.remove('gallery-item-5');
});
this.carouselArray.slice(0, 5).forEach((el, i) => {
el.classList.add(`gallery-item-${i+1}`);
});
}
setCurrentState(direction) {
if (direction.className == 'gallery-controls-previous') {
this.carouselArray.unshift(this.carouselArray.pop());
} else {
this.carouselArray.push(this.carouselArray.shift());
}
clearInterval(this.mySlideInterval);
this.updateGallery();
this.mySlideInterval = setInterval(
this.autoSlide.bind(this),
5000
);
}
setControls() {
this.carouselControls.forEach(control => {
galleryControlsContainer.appendChild(document.createElement('button')).className = `gallery-controls-${control}`;
document.querySelector(`.gallery-controls-${control}`).innerText = control;
});
}
useControls() {
const triggers = [...galleryControlsContainer.childNodes];
triggers.forEach(control => {
control.addEventListener('click', e => {
e.preventDefault();
if (control.className == 'gallery-controls-add') {
const newItem = document.createElement('img');
const latestItem = this.carouselArray.length;
const latestIndex = this.carouselArray.findIndex(item => item.getAttribute('data-index') == this.carouselArray.length)+1;
Object.assign(newItem,{
className: 'gallery-item',
src: `http://fakeimg.pl/300/?text=${this.carouselArray.length+1}`
});
newItem.setAttribute('data-index', this.carouselArray.length+1);
this.carouselArray.splice(latestIndex, 0, newItem);
document.querySelector(`[data-index="${latestItem}"]`).after(newItem);
this.updateGallery();
} else {
this.setCurrentState(control);
}
});
});
}
}
const exampleCarousel = new Carousel(galleryContainer, galleryItems, galleryControls);
//exampleCarousel.setControls();
//exampleCarousel.useControls();