Progress bar between given time and current time - javascript

I calculate the time between a given time and current time to get the seconds remaining for the progress bar.
It's working, but not when i refresh the page, it starts again from 0%.
I want I like this:
Current time: 15:30:00
Time to given time: 16:00:00
Progress bar is at 50%.
JS:
var start = (“01-02-2020 15:00:00”)
var end = (“01-02-2020 16:00:00”
var cur = new Date();
var diff = end.getTime() - cur.getTime();
var duration = (diff);
$outer = $("#pbar_outerdiv");
$outer.click(function() {
$('#pbar_innerdiv')
.stop()
.css({ width: 0 })
.animate({ width: "100%" }, duration, "linear", function() { window.location.reload(1); });
})
$outer.trigger("click");
});
}
});
HTML:
<div id="pbar_outerdiv">
<div id="pbar_innerdiv"></div>
</div>
CSS:
#pbar_outerdiv {
margin-top: 50px;
width: 100%;
height: 20px;
background: #ccc;
}
#pbar_innerdiv {
height: 100%;
width: 0;
background: #f00;
}

Consider the following example. You need to get the starting point, the ending point and the current time. Find the delta between the end time and the start time to find out the duration of the progress, we'll keep that in duration. Then find how much time has passed since the start time and divide that by duration, we'll keep that in t. Then use that as the value of a <progress/>.
For the following example, we will set the start time to 15 minutes ago and the end time to 30 minutes from now.
const curr = new Date();
const start = new Date();
const end = new Date();
start.setMinutes(curr.getMinutes() - 15);
end.setMinutes(curr.getMinutes() + 30);
const duration = end.getMinutes() - start.getMinutes();
const t = (curr.getMinutes() - start.getMinutes()) / duration;
console.log(t);
const progress = document.getElementById("progress");
progress.value = t;
<progress id="progress" value="0"></progress>

Related

Lerp from one position to another in a specified duration with javascript

Ok so I am trying to ease a box from one place to another using Linear Interpolation. But It doesn't seem to easing it is just moving without easing. So I have read the post here C# Lerping from position to position and maybe I am misunderstanding the equation. Here is what I have so far.
const lerp = (start, end, speed) => start + (end - start) * speed
const div = document.querySelector('div')
const btn = document.querySelector('button')
let pos = 0
let startTime = 0
const duration = 2000
let aF = null
const animate = () => {
const elapsed = Date.now() - startTime
const t = elapsed / duration
if(elapsed < duration) {
pos = lerp(0, 300, t)
aF = requestAnimationFrame(animate)
} else {
cancelAnimationFrame(aF)
aF = null
pos = 300
}
console.log(pos, 300 * t)
div.style.transform = `translateX(${pos}px)`
}
btn.addEventListener('click', () => {
pos = 0
startTime = Date.now()
aF = requestAnimationFrame(animate)
})
div {
width: 50px;
height: 50px;
background: green;
}
button {
margin-bottom: 10px;
}
<button>Run Animation</button>
<div></div>
So in the example you can see the box is just animating without any easing. In the console log you can se the values are the same even though I am lerping on value and not he other.
I know there is something I am not understanding here but I don't know what that is. Any help would be appreciated. Thanks.
Your example is working (a + (a-b) * t). It is just that the interpolation is missing.
Interpolation is the art of remapping a value (t in your case) between 0-1 to again 0-1 using a different function (so a + (a-b) * t becomes a + (a-b) * interpolate(t)). There are infinite amount of such functions, I chose some popular ones and included them in your example:
let interpolators = {
identity: function(t){
t = Math.max(0,Math.min(1,t));
return t;
},
cubic: function(t){
t = Math.max(0,Math.min(1,t));
if(2*t<<0){
return 4*(t-1)*(t-1)*(t-1)+1;
} else {
return 4*t*t*t;
}
},
elastic: function(t){
t = Math.max(0,Math.min(1,t));
var range = 10.5*Math.PI;
return (range - Math.sin(range*t)/t)/(range - 1);
}
};
the most important change is:
const t = interpolators[selected](elapsed / duration);
https://jsfiddle.net/ibowankenobi/6ctp9a0s/26/

How to keep the arrow position unchanged when restart it using requestAnimationFrame()?

I tried to understand an example on MDN.
The effect of the current code is a bit confusing to me - after a pause if you wait a while and start again, the position of the arrow is not where it originally stopped.
How to make sure to restart where I left off? I thought of a way as below, but I am not very satisfied with it.
const spinner = document.querySelector('div')
let rotateCount = 0
let startTime = null
let rAF
let spinning = false
let previousRotateCount = 0
let hasStopped = false
let rotateInterval
function draw(timestamp) {
if (hasStopped) {
// The angle of each rotation is constant
rotateCount += rotateInterval
rotateCount %= 360
} else {
if (!startTime) {
startTime = timestamp
}
rotateCount = (timestamp - startTime) / 3
rotateCount %= 360
rotateInterval = rotateCount - previousRotateCount
previousRotateCount = rotateCount
}
console.log(rotateCount)
spinner.style.transform = `rotate(${rotateCount}deg)`
rAF = requestAnimationFrame(draw)
}
document.body.addEventListener('click', () => {
if (spinning) {
hasStopped = true
cancelAnimationFrame(rAF)
} else {
draw()
}
spinning = !spinning
})
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: #f00;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
}
<div>↻</div>
I hope there is a better way. Thank you very much.
This example code is a bit weird, they define a global rotateCount that's never actually used and their use of the timestamp is ... yes, confusing.
Their let rotateCount = (timestamp - startTime) / 3; makes it that the animation will take 1080ms to perform a full revolution (360deg x 3 = 1080ms).
But this is based on the difference between the current time and the start time. So indeed, even if you pause the animation, when restarting it will just like it never did pause.
To make this, you'd need to actually use the global rotateCount (by not redefining a new variable inside draw), and increment it every time by the amount of rotation that was needed since last draw, and not since the overall beginning.
Then you just need to ensure that the last-drawn timestamp gets updated when you resume the animation and you get your animation to actually pause and resume.
const spinner = document.querySelector('div');
const duration = 1080; // ms to perform a full revolution
const speed = 360 / duration;
let rotateCount = 0;
let rAF;
// we'll set this in the starting code
// (in the click event listener)
let lastTime;
let spinning = false;
// Create a draw() function
function draw(timestamp) {
// get the elapsed time since the last time we did fire
// we directly get the remainder from "duration"
// because we're in a looping animation
const elapsed = (timestamp - lastTime) % duration;
// for next time, lastTime is now
lastTime = timestamp;
// add to the previous rotation how much we did rotate
rotateCount += elapsed * speed;
// Set the rotation of the div to be equal to rotateCount degrees
spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
// Call the next frame in the animation
rAF = requestAnimationFrame(draw);
}
// event listener to start and stop spinner when page is clicked
document.body.addEventListener('click', () => {
if(spinning) {
cancelAnimationFrame(rAF);
spinning = false;
} else {
// reset lastTime with either the current frame time
// or JS time if unavailable
// so that in the next draw
// we see only the time that did elapse
// from now to then
lastTime = document.timeline?.currentTime || performance.now();
// schedule the next draw
requestAnimationFrame( draw )
spinning = true;
}
});
html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: red;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
user-select: none;
}
<div>↻</div>

requestAnimationFrame and setInterval animating at a different pace

I am translating two divs using setInterval and requestAnimationFrame. Animated using interval, the div translates at a rate of 3px per (1000/60)ms, which equates to 180px per 1000ms. At the same time, the div animated using requestAnimationFrame translates at a rate of 0.18px per 1ms, which equates to 180px per 1000ms.
However, they curiously aren't translating at the speed I want. Look at the example below:
let interval = document.querySelector('.interval')
let raq = document.querySelector('.raq')
function startAnimation() {
let translateValue = 0
setInterval(() => {
translateValue = (translateValue + 3) % 300
interval.style.transform = `translateX(${translateValue}px)`
}, 1000 / 60)
let raqAnimation = (timeElapsed) => {
let translateValue = (timeElapsed * 0.18) % 300
raq.style.transform = `translateX(${translateValue}px)`
window.requestAnimationFrame(raqAnimation)
}
window.requestAnimationFrame(raqAnimation)
}
window.setTimeout(startAnimation, 1000)
.interval,
.raq {
width: 50px;
height: 50px;
border-radius: 5px;
background-color: #121212;
margin: 1rem;
}
<div class="interval"></div>
<div class="raq"></div>
Did I use setInterval or requestAnimationFrame wrong or did I fail at the maths calculation?
There is a absolutely no guarantee that your iterval will run at the requested rate so just adding some constant every callback like the code does for the setInterval case isn't going to match.
you could use performance.now or Date.now as your clock in the setInterval case
let interval = document.querySelector('.interval')
let raq = document.querySelector('.raq')
function startAnimation() {
setInterval(() => {
const translateValue = (performance.now() * 0.18) % 300
interval.style.transform = `translateX(${translateValue}px)`
}, 1000 / 60)
let raqAnimation = (timeElapsed) => {
let translateValue = (timeElapsed * 0.18) % 300
raq.style.transform = `translateX(${translateValue}px)`
window.requestAnimationFrame(raqAnimation)
}
window.requestAnimationFrame(raqAnimation)
}
window.setTimeout(startAnimation, 1000)
.interval,
.raq {
width: 50px;
height: 50px;
border-radius: 5px;
background-color: #121212;
margin: 1rem;
}
<div class="interval"></div>
<div class="raq"></div>
they still may not perfectly align though as (a) they are actually running at different times and so get different time values and (b) the run at different rates. They will be close though since it's effectively the same clock

Calculating time difference with jQuery

I'm trying to create a simple app that displays the name of the TV show and the duration of the show. What I need to do is change the width of a span according to the time. If the show has already started the user should see a colored progress bar and its width should be of a certain percentage of the timeline.
// this is a very simplified version of the json I'm getting
var data = { "showName":"Batman Begins", "duration":"141", "startTime":"22 January 2016 17:30:00" };
function showProgress(){
var timeline = $('.timeline');
var duration = data.duration; // time in minutes. Getting this from a json
var timelineSection = 100 / duration;
var maxWidth = 100;
var increment = timelineSection;
var now = Math.floor($.now() / 1000); // UNIX timestamp for current date and time
var startTime = Date.parse(data.startTime) / 1000; // UNIX timestamp for showtime
var timer = setInterval(function(){
$('.timeline').css({ 'width' : timelineSection + '%' });
timelineSection = timelineSection + increment; // doing this to keep the incrementation same everytime
if (timelineSection > maxWidth) {
clearInterval(timer);
$('.timeline').css({ 'width' : timelineSection + '%' });
}
}, 1000); // running every second instead of minute for demo purposes
}
$(document).ready(function(){
$('.showName').html(data.showName);
$('.startTime').html(data.startTime);
showProgress();
});
.wrapper {
margin: auto;
width: 500px;
}
.timeline-container {
background: #bbb;
}
.timeline {
background: green;
height: 20px;
margin: 0 0 10px;
max-width: 100%;
transition: all 200ms linear;
width: 0%;
}
.example-timeline {
background: green;
height: 20px;
margin: 0 0 10px;
max-width: 100%;
transition: all 200ms linear;
}
.example-timeline.half { width: 50%; }
.example-timeline.twothirds { width: 66.6666%; }
.example-timeline.onethird { width: 33.3333%; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<h1 class="showName"></h1>
<p class="description">Show starting at: <span class="startTime"></span></p>
<div class="timeline-container">
<div class="timeline"></div>
<div class="example-timeline half"></div>
<div class="example-timeline twothirds"></div>
<div class="example-timeline onethird"></div>
</div>
</div>
I can't seem to figure out how to display the timeline correctly if the show has already started. See the example timelines in snippet. I have a countdown clock to the start of the show and it hides when the show starts revealing the timeline and it works perfectly fine.
I really hope someone can help me with this :)
Just perform some calculation to initialize the timeline
// this is a very simplified version of the json I'm getting
var data = { "showName":"Batman Begins", "duration":"141", "startTime":"22 January 2016 17:30:00" };
function showProgress(){
var timeline = $('.timeline');
var duration = data.duration; // time in minutes. Getting this from a json
var timelineSection = 100 / duration;
var maxWidth = 100;
var increment = timelineSection;
//var now = Math.floor($.now() / 1000); // UNIX timestamp for current date and time
var now = Math.floor(Date.parse("22 January 2016 17:39:00") / 1000);
var startTime = Date.parse(data.startTime) / 1000; // UNIX timestamp for showtime
if (now > startTime) {
var elapsed = Math.floor((now-startTime)/60); //timespan converted to minutes
timelineSection = Math.min(maxWidth, elapsed*timelineSection); //set the start width to match the time elapsed but only reach maximum of 100
$('.timeline').css({ 'width' : timelineSection + '%' }); //set the initial width first
timelineSection += increment; //to be used as next width
}
var timer = setInterval(function(){
$('.timeline').css({ 'width' : timelineSection + '%' });
timelineSection = timelineSection + increment; // doing this to keep the incrementation same everytime
if (timelineSection > maxWidth) {
clearInterval(timer);
$('.timeline').css({ 'width' : timelineSection + '%' });
}
}, 1000); // running every second instead of minute for demo purposes
}
$(document).ready(function(){
$('.showName').html(data.showName);
$('.startTime').html(data.startTime);
showProgress();
});
.wrapper {
margin: auto;
width: 500px;
}
.timeline-container {
background: #bbb;
}
.timeline {
background: green;
height: 20px;
margin: 0 0 10px;
max-width: 100%;
transition: all 200ms linear;
width: 0%;
}
.example-timeline {
background: green;
height: 20px;
margin: 0 0 10px;
max-width: 100%;
transition: all 200ms linear;
}
.example-timeline.half { width: 50%; }
.example-timeline.twothirds { width: 66.6666%; }
.example-timeline.onethird { width: 33.3333%; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<h1 class="showName"></h1>
<p class="description">Show starting at: <span class="startTime"></span></p>
<div class="timeline-container">
<div class="timeline"></div>
<div class="example-timeline half"></div>
<div class="example-timeline twothirds"></div>
<div class="example-timeline onethird"></div>
</div>
</div>
HTML - I updated your HTML to show you a different way of handling the UX presentation by using CSS against a content wide state provided in the .showInfo class. When the show times are calculated, a data-attr is provided. This allows CSS to show or hide certain text, and will later allow you to build in other state dependant styles (such as a different progress bar colour).
<div class="wrapper showInfo">
<h1 class="showName"></h1>
<p class="description">
<span class="status past">The show has finished</span>
<span class="status present">It started on </span>
<span class="status future">It will start on</span>
<span class="showTimeInfo"></span></p>
<div class="timeline-container">
<div class="timeline"></div>
</div>
<div class="showStatus">
<div class="status past">Oh man, it's over!</div>
<div class="status present">It is ON!</div>
<div class="status future">Dude, the show is on later.</div>
</div>
</div>
Additional CSS - your current CSS should pretty much just fit, and the additional styles are used to update the functionality described.
.showInfo > .showStatus > .status {display: none;}
.showInfo > .description > .status {display: none;}
.showInfo[data-state="past"] .showStatus .past, .showInfo[data-state="past"] .description .past {display: inline-block;}
.showInfo[data-state="present"] .showStatus .present, .showInfo[data-state="present"] .description .present {display: inline-block;}
.showInfo[data-state="future"] .showStatus .future, .showInfo[data-state="future"] .description .future {display: inline-block;}
JavaScript - date/time can be a little tricky in JavaScript so I've not added lots of clever bits or any measurable intelligence to it. Consider Moment.js (or similar) as a library for your project.
// this is a very simplified version of the json I'm getting
var data = { "showName":"Batman Begins", "duration":"5", "startTime":"17 January 2016 20:09:00" };
// Make some jQuery object vars so you only have to call them once
var timeline = $('.timeline');
var showInfo = $('.showInfo');
var showTimeInfo = $('.showTimeInfo');
// Always make a timeout into a global var so you can cancel it later
var progressTimeout = {};
var tickDuration = 1000;
// Self calling function
function showProgress () {
// Set some time related vars
var now = Math.floor($.now() / 1000);
var startTime = Date.parse(data.startTime) / 1000;
// Conver 'duration' into seconds
var durationSec = parseInt(data.duration, 10) * 60
var endTime = (Date.parse(data.startTime) / 1000) + durationSec;
// Help for our conditional statments
var showStart = startTime - now;
var showEnd = + endTime - now;
// Events that are in the past will be minus - thus false
// If both are grater than zero, then the show is on now
if (showStart>0 && showEnd>0) {
// Set a data-attr at the top of our content so we can use CSS as an aide
showInfo.attr('data-state', 'future');
showTimeInfo.html(data.startTime)
} else if (showEnd>-1) {
// If showEnd is zero or more then the show is on, but not over
// Now we can make the progress bar work
// Work out the progress of the show as a percentage
var progress = now - startTime;
var percent = Math.ceil(progress / (durationSec / 1000) / 10);
// Use the percentage to push progress bar
timeline.css('width', percent + "%")
// Set the state to say that the show is on in the 'present'
showInfo.attr('data-state', 'present');
showTimeInfo.html(data.startTime)
} else {
// Both startTime and endTime are now in the past
// Make the bar full
timeline.css('width', '100%')
showInfo.attr('data-state', 'past');
// Clear the time text
showTimeInfo.html("");
// The timeout is no longer needed
clearTimeout(progressTimeout);
}
progressTimeout = setTimeout(showProgress, tickDuration)
}
$('.showName').html(data.showName);
showProgress();
jsFiddle example: https://jsfiddle.net/likestothink/0d7hf2fq/3/

Start and stop with keypress from keyboard

I have this JavaScript http://jsfiddle.net/ZDsMa/433/ to pick a random number. I put this on html div and I want to start scrambling the numbers with keypress instead of onClick, also to stop again with keypress:
var output, started, duration, desired;
// Constants
duration = 5000;
desired = '5';
// Initial setup
output = $('#output');
started = new Date().getTime();
// Animate!
animationTimer = setInterval(function() {
// If the value is what we want, stop animating
// or if the duration has been exceeded, stop animating
if (output.text().trim() === desired || new Date().getTime() - started > duration) {
clearInterval(animationTimer);
} else {
console.log('animating');
// Generate a random string to use for the next animation step
output.text(
''+
Math.floor(Math.random() * 10)+
Math.floor(Math.random() * 10)
);
}
}, 100);
You can wrap the animation in a function and then call it on keypress... something like this:
var output, started, duration, desired;
var sel = [];
// Constants
duration = 5000;
desired = '5';
// Initial setup
output = $('#output');
var keyPressed = false;
$(document).keypress(function(){
if (!keyPressed){
started = new Date().getTime();
scramble();
keyPressed = true;
}
else{
keyPressed = false;
}
});
// Animate!
var scramble = function(){
animationTimer = setInterval(function() {
// If the value is what we want, stop animating
// or if the duration has been exceeded, stop animating
if (output.text().trim() === desired || new Date().getTime() - started > duration || !keyPressed) {
while($.inArray(output.text().trim(),sel)>-1){
output.text(
''+
Math.floor(Math.random() * 10)+
Math.floor(Math.random() * 10)
);
}
clearInterval(animationTimer);
keyPressed = false;
sel.push(output.text());
$("#selected").text(sel);
} else {
// Generate a random string to use for the next animation step
output.text(
''+
Math.floor(Math.random() * 10)+
Math.floor(Math.random() * 10)
);
}
}, 100);
}
#output {
margin: 20px;
padding: 20px;
background: gray;
border-radius: 10px;
font-size: 80px;
width: 80px;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output">--</div>
<div id="selected"></div>

Categories