JavaScript: Assigning a variable if variable changed - javascript

In JAVASCRIPT:
If I have a variable which value is constantly changing (100+ times a second). How do I 'record' a specific value at a specific point in time?
Added to this, how do I base this point in time off of another variable of which value has changed?
This needs to be strictly in JavaScript. I've looked at the onChange() method, but I'm unsure if I have to use this in conjunction with HTML for it to work. If not, could someone give me an example where this is not the case?
Cheers

I'm not 100% clear on what you're trying to do, but as Ranjith says you can use setTimeout to run arbitrary code at some (approximate) future time.
This example could likely be improved if I had a bit more detail about what you're doing.
If you're in a node environment you might consider using an event emitter to broadcast changes instead of having to have the variable in scope. (This isn't particularly hard to do in a browser either if that's where you are.)
The html/css parts of this are just for displaying the values in the example; not necessary otherwise.
const rand = document.getElementById('rand');
const snapshot = document.getElementById('snapshot');
let volatile = 0;
// update the value every ~100ms
setInterval(() => {
// assign a new random value
volatile = Math.random();
// display it so we can see what's going on
rand.innerText = volatile;
}, 100);
// do whatever you want with the snapshotted value here
const snap = () => snapshot.innerText = volatile;
// grab the value every 2 seconds
setInterval(snap, 2000);
div {
margin: 2rem;
}
<div>
<div id="rand"></div>
<div id="snapshot"></div>
</div>

Ok - well you can poll variable changes ... even though you can use setters...
Lets compare:
Polling:
let previous;
let watched = 0;
let changes = 0;
let snap = () => previous = watched !== previous && ++changes && watched || previous;
let polling = setInterval(snap, 100);
let delta = 1000 * 2
let start = Date.now();
let last = start;
let now;
let dt = 0
while(start + delta > Date.now()){
now = Date.now();
dt += now - last;
last = now;
if(dt > 100){
watched++;
dt = 0;
}
}
document.getElementsByTagName('h1')[0].innerText = (changes === 0 ? 0 : 100 * watched / changes) + "% hit"
if(watched - changes === watched){
throw Error("polling missed 100%");
}
<h1><h1>
emitting:
const dataChangeEvent = new Event("mutate");
const dataAccessEvent = new Event("access");
// set mock context - as it is needed
let ctx = document.createElement('span');
// add watchable variable
add('watched', 0);
//listen for changes
let changes = 0;
ctx.addEventListener('mutate', () => changes++);
let delta = 1000 * 2
let start = Date.now();
let last = start;
let now;
let dt = 0
while(start + delta > Date.now()){
now = Date.now();
dt += now - last;
last = now;
if(dt > 100){
ctx.watched++;
dt = 0;
}
}
document.getElementsByTagName('h1')[0].innerText = (changes === 0 ? 0 : 100 * ctx.watched / changes) + "% hit"
if(ctx.watched - changes === ctx.watched){
throw Error("trigger missed 100%");
}
function add(name, value){
let store = value
Object.defineProperty(ctx, name, {
get(){
ctx.dispatchEvent(dataAccessEvent, store)
return store;
},
set(value){
ctx.dispatchEvent(dataChangeEvent, {
newVal: value,
oldVal: store,
stamp: Date.now()
});
store = value;
}
})
}
<h1></h1>
The usage of a while loop is on purpose.

Related

How to retrieve a value from a variable outside of a condition?

I'm learning JS, but I don't know if it's possible to do what I want to achieve.
I have a variable named btcVariationTotal which is in a condition, and I want to retrieve the value of this variable in another variable called tmp, but this variable is not included in the condition.
My problem is that tmp always shows me 0. I don't understand why? And how can I solve this problem, please?
I really want to retrieve the value outside the condition.
console.clear();
let wsBtc = new WebSocket('wss://stream.binance.com:9443/ws/btcusdt#trade');
let btcStockPriceElement1 = document.getElementById('btcValue1');
let btcStockPriceElement2 = document.getElementById('btcValue2');
let btcLastPrice = null;
let btcStockObject = null;
wsBtc.onmessage = (event) => {
btcStockObject = JSON.parse(event.data);
};
let btc1 = 0, btc2 = 0;
let btcVariation_1_2 = 0;
let btcVariationTotal = 0;
let tmp = 0;
let btcRunTimers = setInterval(() => {
let minutes = new Date().getMinutes();
if (minutes === 51) {
let val1 = parseFloat(btcStockObject.p).toFixed(1);
let price = parseFloat(btcStockObject.p).toFixed(1);
btcStockPriceElement1.innerText = price;
btcStockPriceElement1.style.color =
!btcLastPrice || btcLastPrice === price
? 'black'
: price > btcLastPrice
? '#AAFF00'
: 'red';
btcLastPrice = price;
btcStockObject = null;
btc1 = val1;
}
if (minutes === 52) {
let val2 = parseFloat(btcStockObject.p).toFixed(1);
let price = parseFloat(btcStockObject.p).toFixed(1);
btcStockPriceElement2.innerText = price;
btcStockPriceElement2.style.color =
!btcLastPrice || btcLastPrice === price
? 'black'
: price > btcLastPrice
? '#AAFF00'
: 'red';
btcLastPrice = price;
btcStockObject = null;
btc2 = val2;
btcVariation_1_2 = ( (parseFloat(btc2) - parseFloat(btc1)) / btc1 * 100);
document.getElementById("btcResult1").innerHTML = btcVariation_1_2.toFixed(2);
}
btcVariationTotal = (parseFloat(btcVariation_1_2));
console.log("btc variation => " + btcVariationTotal);
document.getElementById("result").innerHTML = btcVariationTotal.toFixed(2);
tmp = btcVariationTotal;
}, 60000);
console.log("tmp => " + tmp);
The good news is that you are in fact doing what you want: you are retrieving
the value of the btcVariationTotal variable, and storing in tmp, which is
defined in the outer scope, outside of your setInterval callback.
The only problem you have is that you can't display a modified tmp, and
that's because you only call console.log before setting tmp, you never
call it after it has been changed. User Ivar has tried to explain that in
the comments, maybe I can detail it a bit more:
At time t=0, you set tmp = 0, you set your timers with setInterval, associating
a callback function (which does NOT run at this point), and then you call
console.log to display tmp (it's 0, because no callback has ever run).
At time t=60s, your callback runs, sets btcVariationTotal to some value, and
assigns that to tmp. No attempt is made to display the tmp value. Then this
gets repeated every 60s.
So what's missing is for you to write some code that displays the tmp value
after it has been changed. One way to do that, is to put that code inside
some other callback and arrange for it to be called. I suggest a simple
button. Add the following somewhere in your html page:
<button id="show-tmp">Show tmp</button>
Add the following lines at the end of your JS code:
let btn = document.getElementById('show-tmp');
btn.onclick = function() {
console.log(`tmp: ${tmp}`);
}
Now clicking on the button will show you the value inside tmp; if you do it
before the first 60 seconds, it will show 0; if you do it afterwards, it will
show whatever value was in btcVariationTotal.

Best way to initialize html elements in JavaScript for reusability?

The way I did this was by getting the html elements and declaring them as global const variables inside a function() {} that encapsulates the whole program. It works, but I'm unsure if this is good practice. Is there a way to allow reusability of an element without using an omnipresent function() {} or will this be fine?
My Code:
document.addEventListener('DOMContentLoaded', function() {
//The global const elements from HTML doc
const square = document.getElementsByClassName("square");
const mole = document.getElementsByClassName("mole");
const timeLeft = document.getElementsByClassName("time-left")[0]; //# for id elements
//Initialize score, result, and time
let score = document.getElementsByClassName("score")[0];
let result = 0;
let currentTime = timeLeft.textContent;
//Call countDown function every 1000 milliseconds or 1 second
let timerId = setInterval(countDown, 1000);
function main() {
//Add an event listener for each square called mouseup
for (let i = 0; i < square.length; i++) {
square[i].addEventListener("mouseup", checkHit);
}
moveMole()
}
//Once the event triggers, check if val of current square equals to hitPosition
//If it is, update result and hitPosition
function checkHit(event) {
if (this.id === hitPosition) {
result = result + 1;
score.textContent = result;
hitPosition = null;
}
}
//Move mole to a random square every 1000 milliseconds
function moveMole() {
let timerId = null;
timerId = setInterval(randomSquare, 1000);
}
//Choose a randomSquare to put the mole
function randomSquare() {
//Remove mole from the class name to make sure there isn't any forgotten divs that were used for styling
for (let i = 0; i < square.length; i++) {
square[i].classList.remove("mole");
}
//Math.floor rounds down
//math.random() * 9 uses a position from 1 to 9
let randomPosition = square[Math.floor(Math.random() * 9)];
//Set the mole to the randomPostion
randomPosition.classList.add("mole");
//Assign the id of the randomPositon to hitPosition for comparing later
hitPosition = randomPosition.id;
}
//Counts our timer down
function countDown() {
//Decrement currentTime and show currentTime
currentTime--;
timeLeft.textContent = currentTime;
//If currentTime is 0
//Clear the timer set by the setInterval method
//Stops the execution of randomSquare
//Alert user of final score
//Reset time and result
if (currentTime === 0) {
alert("GAME OVER! Your final score is " + result);
timeLeft.textContent = 30;
currentTime = timeLeft.textContent;
result = 0;
score.textContent = result;
}
}
main();
})
As far as I'm concerned, the code looks fine but make sure to look at other people and see what they're doing.

set variable by using localstorage

i am trying to create the driver performance assistant.
when i start up the page it needs to read the variable from the last time and use that to count up.
(i have created a dashboard that reads data through a plugin from a game. i am trying to create the driver performance assistant from DAF (truck brand). the way it should work is when rolling out the vehicle it counts up a variable accordingly to the time it is rolling out (this part i have created and it works) now my problem is. i am also trying to save it in the localstorage so it wont get lost. but the variable is doing weird things when i tries to read the localstorage data.
this code only needs to be in javascript.
// variables for driver score
var startBrake = 0;
var endBrake = 0;
var timeDiffBrake = 0;
var countBrake = localStorage.getItem('brake');
var startRollout = 0;
var endRollout = 0;
var timeDiffRollout = 0;
var countRollout = localStorage.getItem('rollout');
var speed = Math.abs(data.truck.speed > 0 ? Math.floor(data.truck.speed) : Math.round(data.truck.speed));
var throttle = utils.formatFloat(data.truck.gameThrottle, 2);
var serviceBrake = utils.formatFloat(data.truck.userBrake, 2);
var retarder = data.truck.retarderBrake;
var engineBrake = data.truck.motorBrakeOn;
var cruiseControl = data.truck.cruiseControlOn;
var fuelConsumption = data.truck.fuelAverageConsumption * 100;
// ANTICIPATE
// Braking to long
if (speed >= 50) {
if (serviceBrake < 0.1) {
startBrake = new Date();
} else if (serviceBrake > 0.25) {
endBrake = new Date();
timeDiffBrake = (endBrake - startBrake) / 1000;
if (timeDiffBrake > 5) {
countBrake -= 0.01;
// add "te lang remmen" display
}
}
}
localStorage.setItem('brake', countBrake);
// Rolling out
if (speed > 30 && speed < 89) {
if (throttle > 0) {
startRollout = new Date();
} else if (throttle < 0.05 && serviceBrake == 0) {
endRollout = new Date();
timeDiffRollout = (endRollout - startRollout) / 1000;
if (timeDiffRollout > 1) {
countRollout += 0.01;
// maybe add display
}
}
}
localStorage.setItem('rollout', countRollout);
// EFFICIENT BRAKING
var brake = localStorage.getItem('brake');
var rollout = localStorage.getItem('rollout');
var anticipate = brake + rollout;
var efficientBraking = 0; // haven't done this yet
var driverScore = Math.round((efficientBraking + anticipate) / 2);
data.drivingScorePercent = driverScore <= 0 ? 0 : driverScore >= 100 ? 100 : driverScore;
the main problem is this variable
var countRollout = localStorage.getItem('rollout');
it keeps saying my data is NAN (i think undefined)
i changed the lines to what "mister jojo" suggested but somehow i get some wierd data from the localstorage. i assumed that a "var += 0.01" would count 0.01 up but somehow it goes like this "0.010.010.010.010.01" instead of 0.01, 0.02, 0.03.
When you are declaring the countBrake and countRollout variables, the value are undefined because you didn't set the values to localStorage yet. So, you can check whether there is already a value set in localStorage and set default value incase the value isn't set yet:
var countBrake = localStorage.getItem('brake') !== undefined ? localStorage.getItem('brake') : 0;
var countRollout = localStorage.getItem('rollout') !== undefined localStorage.getItem('rollout') : 0;
more simple for Shuvo answer is
with the use of the Nullish coalescing operator (??)
var countBrake = localStorage.getItem('brake') ?? 0
, countRollout = localStorage.getItem('rollout') ?? 0
;

How am I able to add timing to javascript that alters text every second?

I would like this code to count up from 0 to 940 (very fast) and alter the text every time it updates
Here's my code (inside my head tag):
<script type="text/javascript">
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
function onLoad(){
var x = document.getElementById("numberID");
var n = 940;
var text = "";
for(i = 0;i < n + 1;i++){
text = i;
x.innerHTML = text;
sleep(1);
}
}
</script>
At the moment, it just waits a second then displays '940' on screen and doesn't display it counting up.
Any help would be appreciated, thanks!
Here's the code I recently put in, still doesn't work:
const x = document.getElementById("numberID");
function newFrame(duration, start = performance.now()) {
requestAnimationFrame((now) => {
const elapsed = now - start;
x.innerText = Math.max(0, Math.min(duration,
Math.round(elapsed)));
if(elapsed < duration)
newFrame(duration, start);
})
}
}
newFrame(940);
Using a while loop to "sleep" is going to block the page's thread and nothing else can happen in the meantime. This is considered bad practice.
setTimeout guarantees that at least the defined time has passed, but can take (much) longer. This is imprecise, and especially bad for shorter intervals. Same with setInterval. They're also not recommended for callbacks that involve updating the DOM.
What you need to do is use a requestAnimationFrame.
function newFrame(duration, start = performance.now()) {
requestAnimationFrame((now) => {
const elapsed = now - start
console.log(`time passed: ${elapsed} ms`)
if(elapsed < duration)
newFrame(duration, start)
})
}
newFrame(940)
In your specific case, I'd replace the console.log statement put there for didactic purposes, with something along the lines of:
x.innerText = Math.max(0, Math.min(duration, Math.round(elapsed)))
Here's what that would look like:
const x = document.getElementById("numberID")
function newFrame(duration, start = performance.now()) {
requestAnimationFrame((now) => {
const elapsed = now - start
x.innerText = Math.max(0, Math.min(duration, Math.round(elapsed)))
if(elapsed < duration)
newFrame(duration, start)
})
}
newFrame(940)
<span id="numberID"></span>
The sleep function is not doing anything, what you need is a setTimeout to display the text at every x milliseconds.
Something like the below will work.
let x = null;
let timeout = null;
const changeText = (text) => {
x.innerHTML = text;
clearTimeout(timeout);
}
function onLoad() {
x = document.getElementById("numberID");
const n = 940;
const t = .01; // in seconds
for( let i = 0; i <= n; i++) {
timeout = setTimeout( () => changeText((i+1).toString()), (t * i) * 1000);
}
}
onLoad();
<span id="numberID"></span>

Vue.js timing calculations are not matching plain JavaScript version

I'm trying to create a 'beats per minute' (BPM) calculator, identical (for now) to the one you can find here. But for some reason, when I use the BPM calculator at that link on a test song, it gets within 1 BPM of the actual value of 85.94 within of 7 keypresses and just gets more accurate from there, ending within 0.05 of the actual BPM, whereas with my (essentially identically-coded) Vue.js version, it starts much higher (182-->126-->110) and goes down from there, but even after 60 keypresses it's still off by ~2 BPM, and after a full song, it was still off by about 0.37 BPM.
Here's the code for the plain-JavaScript version at that link:
var count = 0;
var msecsFirst = 0;
var msecsPrevious = 0;
function ResetCount()
{
count = 0;
document.TAP_DISPLAY.T_AVG.value = "";
document.TAP_DISPLAY.T_TAP.value = "";
document.TAP_DISPLAY.T_RESET.blur();
}
function TapForBPM(e)
{
document.TAP_DISPLAY.T_WAIT.blur();
timeSeconds = new Date;
msecs = timeSeconds.getTime();
if ((msecs - msecsPrevious) > 1000 * document.TAP_DISPLAY.T_WAIT.value)
{
count = 0;
}
if (count == 0)
{
document.TAP_DISPLAY.T_AVG.value = "First Beat";
document.TAP_DISPLAY.T_TAP.value = "First Beat";
msecsFirst = msecs;
count = 1;
}
else
{
bpmAvg = 60000 * count / (msecs - msecsFirst);
document.TAP_DISPLAY.T_AVG.value = Math.round(bpmAvg * 100) / 100;
document.TAP_DISPLAY.T_WHOLE.value = Math.round(bpmAvg);
count++;
document.TAP_DISPLAY.T_TAP.value = count;
}
msecsPrevious = msecs;
return true;
}
document.onkeypress = TapForBPM;
// End -->
And here's my version:
computed: {
tappedOutBpm: function() {
let totalElapsedSeconds = (this.timeOfLastBpmKeypress - this.timeOfFirstBpmKeypress) / 1000.0
let bpm = (this.numberOfTapsForBpm / totalElapsedSeconds) * 60.0
return Math.round(100*bpm)/100;
},
},
methods: {
tapForBPM: function() {
let now = new Date;
now = now.getTime();
// let now = window.performance.now()
if (this.timeOfFirstBpmKeypress === 0 || now - this.timeOfLastBpmKeypress > 5000) {
this.timeOfFirstBpmKeypress = now
this.timeOfLastBpmKeypress = now
this.numberOfTapsForBpm = 1
} else {
this.timeOfLastBpmKeypress = now
this.numberOfTapsForBpm++
}
}
}
I figured it out by stepping through both of our code.
The problem was that I was setting the number of taps to 1 as soon as the user tapped the key the first time, when in reality it's not taps that I want to count, but beats, and the first beat requires not one tap, but two: the start and the end of that beat. So what I should do is rename the variable to numberOfTappedOutBeats and set it to 0 after the first tap rather than 1.

Categories

Resources