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/
Related
I have a container that is expanded and collapsed on click of chevron icon. The code to collapse/expand the container is in the function transformAnimation. The code of transformAnimation is similar to the code on MDN web docs for requestAnimationFrame. The code to animate (scale) the container has been developed on the guidelines of this article on Building performant expand & collapse animations on Chrome Developers website.
I am not able to figure out how to calculate yScale value (which is nothing but y scale values for collapse/expand animation) as a function of the time elapsed since the start of the animation.
To elaborate what I mean, let's assume that the container is in expanded state. In this state the scaleY value of the container is 6. Now when user clicks on the toggle button, in the transformAnimation function for each animation frame, i.e, execution of the requestAnimationFrame callback step function, the value of scaleY should decrease from 6 (the expanded state) to 1 (the collapsed state) in the exact duration that I want the animation to run for.
In the present state, the code to calculate yScale is not working as expected.
const dragExpandableContainer = document.querySelector('.drag-expandable-container');
const dragExpandableContents = document.querySelector('.drag-expandable__contents');
const resizeableControlEl = document.querySelector('.drag-expandable__resize-control');
const content = document.querySelector(`.content`);
const toggleEl = document.querySelector(`.toggle`);
const collapsedHeight = calculateCollapsedHeight();
/* This height is used as the basis for calculating all the scales for the component.
* It acts as proxy for collapsed state.
*/
dragExpandableContainer.style.height = `${collapsedHeight}px`;
// Apply iniial transform to expand
dragExpandableContainer.style.transformOrigin = 'bottom left';
dragExpandableContainer.style.transform = `scale(1, 10)`;
// Apply iniial reverse transform on the contents
dragExpandableContents.style.transformOrigin = 'bottom left';
dragExpandableContents.style.transform = `scale(1, calc(1/10))`;
let isOpen = true;
const togglePopup = () => {
if (isOpen) {
collapsedAnimation();
toggleEl.classList.remove('toggle-open');
isOpen = false;
} else {
expandAnimation();
toggleEl.classList.add('toggle-open');
isOpen = true
};
};
function calculateCollapsedHeight() {
const collapsedHeight = content.offsetHeight + resizeableControlEl.offsetHeight;
return collapsedHeight;
}
const calculateCollapsedScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = dragExpandableContainer.getBoundingClientRect().height;
return {
/* Since we are not dealing with scaling on X axis, we keep it 1.
* It can be inverse to if required */
x: 1,
y: expandedHeight / collapsedHeight,
};
};
const calculateExpandScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = 100;
return {
x: 1,
y: expandedHeight / collapsedHeight,
};
};
function expandAnimation() {
const {
x,
y
} = calculateExpandScale();
transformAnimation('expand', {
x,
y
});
}
function collapsedAnimation() {
const {
x,
y
} = calculateCollapsedScale();
transformAnimation('collapse', {
x,
y
});
}
function transformAnimation(animationType, scale) {
let start, previousTimeStamp;
let done = false;
function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
const elapsed = timestamp - start;
if (previousTimeStamp !== timestamp) {
const count = Math.min(0.1 * elapsed, 200);
//console.log('count', count);
let yScale;
if (animationType === 'expand') {
yScale = (scale.y / 100) * count;
} else yScale = scale.y - (scale.y / 100) * count;
//console.log('yScale', yScale);
if (yScale < 1) yScale = 1;
dragExpandableContainer.style.transformOrigin = 'bottom left';
dragExpandableContainer.style.transform = `scale(${scale.x}, ${yScale})`;
const inverseXScale = 1;
const inverseYScale = 1 / yScale;
dragExpandableContents.style.transformOrigin = 'bottom left';
dragExpandableContents.style.transform = `scale(${inverseXScale}, ${inverseYScale})`;
if (count === 200) done = true;
//console.log('elapsed', elapsed);
if (elapsed < 1000) {
// Stop the animation after 2 seconds
previousTimeStamp = timestamp;
if (!done) requestAnimationFrame(step);
}
}
}
requestAnimationFrame(step);
}
.drag-expandable-container {
position: absolute;
bottom: 0px;
display: block;
overflow: hidden;
width: 100%;
background-color: #f3f7f7;
}
.drag-expandable__contents {
height: 0;
}
.toggle {
position: absolute;
top: 2px;
right: 15px;
height: 10px;
width: 10px;
transition: transform 0.2s linear;
}
.toggle-open {
transform: rotate(180deg);
}
.drag-expandable__resize-control {
background-color: #e7eeef;
}
.burger-icon {
width: 12px;
margin: 0 auto;
padding: 2px 0;
}
.burger-icon__line {
height: 1px;
background-color: #738F93;
margin: 2px 0;
}
.drag-expandable__resize-control:hover {
border-top: 1px solid #4caf50;
cursor: ns-resize;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css.css">
</head>
<body>
<div class="drag-expandable-container">
<div class="drag-expandable__contents">
<div class="drag-expandable__resize-control">
<div class="burger-icon">
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
</div>
</div>
<div class="content" />
<div>
<div class="toggle toggle-open" onclick="togglePopup()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M416 352c-8.188 0-16.38-3.125-22.62-9.375L224 173.3l-169.4 169.4c-12.5 12.5-32.75 12.5-45.25 0s-12.5-32.75 0-45.25l192-192c12.5-12.5 32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25C432.4 348.9 424.2 352 416 352z"/></svg>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="js.js"></script>
</html>
I want to create a loadingbar/ progressbar that loads as a value (var) increases. I cant figure out how to go around this.I have the tools. e.g. a dynamic chaning value extracted from my Firebase DB that increments on based off of a certain action. however im unsure how to go around to creating this progressbar and how to have it load based off of the dynamic incrementing value.
Any tips?
You can use something like below:
function increaseWidth(percent) {
let containerWidth = document.getElementById('progressBarContainer').clientWidth; // get the container width
let amount = Math.round(containerWidth * percent/100); // get amount of pixels the bars width needs to increase
let barWidth = document.getElementById('bar').offsetWidth; // get the current bar width
// if the bar width + amount of pixels to increase exceeds the container's width, reduce it accordingly
if(barWidth + amount > containerWidth) {
amount = containerWidth - barWidth;
clearInterval(bar); // we reached 100% so clear the interval
}
let totalPercent = Math.round((barWidth + amount) * 100/ containerWidth); // calculate the total percent finished
document.getElementById('bar').style.width = barWidth + amount + "px"; // increase bar width
document.getElementById('text').innerHTML = totalPercent + "%"; // update the percentage text
}
// this is just to mimic generating "work done" percentage
var bar = setInterval(function() {
let percentage = Math.floor(Math.random() * 10 + 1); // generate a percentage of work done
increaseWidth(percentage);
}, 1000)
#progressBarContainer {
position: relative;
float:left;
width: 200px;
height: 20px;
border: 1px solid #09f;
background-color: #000;
}
#bar {
position: relative;
float:left;
width: 0;
height: 20px;
background-color: #f00;
}
#text {
position: absolute;
height: 20px;
width: 200px;
top: 0px;
left: 0px;
color: #fff;
text-align:center;
line-height:20px;
}
<div id="progressBarContainer">
<div id="bar">
<div id="text"></div>
</div>
</div>
If you don't mind using JQuery, try using the JQuery UI Progressbar Widget. You have to first add the JQUERY UI library to your website using a <script> tag in the header:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha256-KM512VNnjElC30ehFwehXjx1YCHPiQkOPmqnrWtpccM=" crossorigin="anonymous"></script>
Then initialize the progress bar with a maximum value; if you want to use percentages, that should be 100.
$("#progressbarElementID").progressbar({
max: 100
});
Then update by writing a percent:
$("#progressbarElementID").progressbar({
value: 74 // Or whatever percent you want...
});
Repeat the update function as necessary to change the progress bar.
For a more in-depth tutorial you can refer to the API docs for this feature.
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
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>
I'm pretty new to javascript and I have a question. I'm currently trying to make a loading bar change color and reset after it has reached 100%, but the main issue is, or at least I think my if statement is invalid. Anyway to test for color and width of an element? Thanks.
function upg_01() {
if (cash >= priceUpg_01) {
var width = 0;
width = width + 20;
a.style.width = width + '%';
priceUpg_01 = priceUpg_01 * 10;
multiplier_01 = multiplier_01 * 3;
}
if (document.getElementById('upg_01c').style.color === "green" &&
document.getElementById('upg_01c') === 100 + '%') {
document.getElementById('upg_01c').style.color = "yellow";
document.getElementById('upg_01c').style.width = 0 + '%';
}
const timer = setInterval( () => {
// select the progress bar element
const progressBar = document.getElementById('progress-bar');
// get the innerHTML of the progess bar
// doubling as our progress state/value
let progress = parseInt( progressBar.innerHTML );
// arbitrary addition/growth of the progress
progress += 3;
// check if progress has met or exceeded 100%
if( progress >= 100 ){
// this is just for the setInterval to stop it
// from continually executing
clearInterval( timer );
// add a new class to the progress bar (keeping
// the CSS separate from the JavaScript -- there's
// a lot of opinions on this, do what you feel
// comfortable with
progressBar.className = 'yellow';
}
// Update the progress value inside the progress
// bar
progressBar.innerHTML = progress;
// Update the width of the progress bar by setting
// the css style value
progressBar.style.width = progress + '%';
// timed loop that will trigger every 100 miliseconds
}, 100 );
#progress-bar{
color: transparent;
font-size: 0px;
width: 0%; height: 3px;
overflow: hidden;
background-color: green;
transition: all 0.5s;
}
#progress-bar.yellow{ background-color: yellow; }
#loader-wrapper{
padding: 5%;
width: 90%;
border: solid 1px black;
border-radius: 5px;
}
<div id="loader-wrapper">
<div id="progress-bar">0</div>
</div>