Javascript div width countdown - javascript

I'm trying to visualize a countdown via a div's width. This could be used for something like a banner system showing when the next banner will slide in, or for a notification system showing how long the notification will be visible.
So in my example below, I have the .outer div emptied after 5 seconds, but the .timer div's width is not reaching width: 0%; at the same time as the setTimeout() kicks in.
The variable len would represent how long the banner or notification would be shown for.
The calculation in the variable offset is what is throwing me off (I think), I cannot seem to get the calculation correct. I would like it to be dynamic, meaning, no matter what len is and what the width of the outer/parent div is, it will always take len time to reach width: 0%;.
I hope my explanation makes sense. Any help would be greatly appreciated.
const len = 5000;
let outer = document.querySelector('.outer');
let timer = document.querySelector('.timer');
let timerWidth = timer.offsetWidth;
let offset = len / timerWidth;
let init = 100;
let interval = setInterval(() => {
init = init - offset;
timer.style.width = init + '%';
}, 1000);
setTimeout(() => {
outer.innerHTML = '';
clearInterval(interval);
}, len);
* {
box-sizing:border-box;
}
body {
padding: 100px 10px 10px;
}
.outer {
width: 100%;
border: 1px solid slategray;
padding: 10px;
}
.timer {
width: 100%;
height: 10px;
background: red;
transition: all 1000ms linear;
}
<div class="outer">
<div class="timer"></div>
<p>Some Message Here!</p>
</div>

Two problems with the code:
interval doesn't start as soon as the page is loaded so the CSS is late in transition.
offset was wrong indeed.
Here's how I fixed it:
let toElapse = 3000; // modify as you like
let tick = 1000; //if you modify this don't forget to replicate
//in CSS transition prop
let countDownEl = document.querySelector('#countdown');
let outer = document.querySelector('.outer');
let timer = document.querySelector('.timer');
let init = 100;
// we calculate the offset based on the tick and time to elapse
let offset = init / (toElapse/tick);
countDownEl.innerHTML = init;
setTimeout(()=>{
// we need to start the first CSS transition ASAP so it is not late.
init = init - offset;
timer.style.width = init.toFixed(2) + '%';
},0)
let interval = setInterval(() => {
// the same interval.
countDownEl.innerHTML = init;
init = init - offset;
timer.style.width = init.toFixed(2) + '%';
}, tick);
setTimeout(() => {
// the same clearance timeout
outer.innerHTML = '';
clearInterval(interval);
}, toElapse);
* {
box-sizing:border-box;
}
body {
padding: 100px 10px 10px;
}
.outer {
width: 100%;
border: 1px solid slategray;
padding: 10px;
}
.timer {
width: 100%;
height: 10px;
background: red;
transition: width 1s linear;
}
<div class="outer">
<div class="timer"></div><span id="countdown"></span>
<p>Some Message Here!</p>
</div>

If you use percentage in width you don't need to know the width of your box.
you just need to substract and add on offset to timeout.
const len = 5000;
let outer = document.querySelector('.outer');
let timer = document.querySelector('.timer');
let timerWidth = timer.offsetWidth;
let offset = 100 * 1000 / 5000;
let interval = setInterval(() => {
init = init - 20;
timer.style.width = init + '%';
}, 1000);
setTimeout(() => {
outer.innerHTML = '';
clearInterval(interval);
}, len + 1000);

Related

What is causing div.style.right = '50%'; to stop being applied when my variable becomes greater than 0?

I have a variable count that triggers a function positiveBar if the value of count is > 0. If the value of count is < 0, it triggers a function negativeBar.
positiveBar changes a div's position using
progressBar.style.left = '50%';
negativeBar changes that same div's position using
progressBar.style.right = '50%';
This gives me the result I want; however, if at any point count becomes greater than 0, the positioning on negativeBar stops working, and it uses the positioning of the positiveBar function instead.
Video to explain:
var count = 0;
// Show count on the page
document.getElementById("countDisplay").innerHTML = count;
// Update count
function updateDisplay() {
countDisplay.innerHTML = count;
};
// Change negative count to an absolute
function absCount() {
return Math.abs(count);
};
function positiveBar() {
progressBar.style.backgroundColor = "#77eb90";
progressBar.style.width = (count * 10) + 'px';
progressBar.style.left = '50%';
};
function negativeBar() {
progressBar.style.backgroundColor = "#ef5c3f";
progressBar.style.width = (absCount() * 10) + 'px';
progressBar.style.right = '50%';
};
// Count up and down when + and - buttons are clicked and edit bar
add1.addEventListener("click", () => {
count++;
updateDisplay();
if (count > 0) {
positiveBar();
} else {
negativeBar();
}
});
subtract1.addEventListener("click", () => {
count--;
updateDisplay();
if (count > 0) {
positiveBar();
} else {
negativeBar();
}
});
.progressBar__Container {
height: 10px;
margin: 20px auto;
border: 1px solid black;
position: relative;
}
#progressBar {
height: 10px;
width: 0;
position: absolute;
}
<div id="countDisplay"></div>
<button id="add1">+</button>
<button id="subtract1">-</button>
<div class="progressBar__Container">
<div id="progressBar"> </div>
</div>
I tried reordering statements. I also tried creating a condition for if count = 0, but that didn't change the result. I'm very confused because it initially works how I intend, but if count becomes greater than 0 at any point, progressBar.style.right = '50%'; stops being applied.
You aren't clearing any previously set left or right styles when you switch from negative to positive and vice versa.
I would use CSS classes to control the position and colour as it's easier to toggle them based on the state of count.
let count = 0;
const countDisplay = document.getElementById("countDisplay");
const progressBar = document.getElementById("progressBar");
// Update count
function updateDisplay() {
countDisplay.textContent = count;
progressBar.style.width = `${absCount() * 10}px`;
progressBar.classList.toggle("positive", count > 0);
progressBar.classList.toggle("negative", count < 0);
};
// Change negative count to an absolute
function absCount() {
return Math.abs(count);
};
// Count up and down when + and - buttons are clicked and edit bar
add1.addEventListener("click", () => {
count++;
updateDisplay();
});
subtract1.addEventListener("click", () => {
count--;
updateDisplay();
});
.progressBar__Container {
height: 10px;
margin: 20px auto;
border: 1px solid black;
position: relative;
}
#progressBar {
height: 10px;
width: 0;
position: absolute;
}
#progressBar.positive {
background-color: #77eb90;
left: 50%;
}
#progressBar.negative {
background-color: #ef5c3f;
right: 50%;
}
<div id="countDisplay">0</div>
<button id="add1">+</button>
<button id="subtract1">-</button>
<div class="progressBar__Container">
<div id="progressBar"> </div>
</div>
See MDN:
When both left and right are defined, if not prevented from doing so by other properties, the element will stretch to satisfy both. If the element cannot stretch to satisfy both — for example, if a width is declared — the position of the element is over-constrained. When this is the case, the left value has precedence when the container is left-to-right; the right value has precedence when the container is right-to-left.
Because you are setting style.left when you then come to set style.right the above applies - i.e. the style.right setting will get overridden.

Delay Intervals After Each Loop in JQuery

I'm looking at setting a 2-3 second delay on a sprite animation. How do I pause the end of each interval and then continue from 0? I used the code below to run only an interval with no delay at the end.
var imgWidth = 48;
var numImgs = 60;
var cont = 0;
var animation = setInterval(function() {
var position = -1 * (cont * imgWidth);
$('#container').find('img').css('margin-left', position);
cont++;
if (cont == numImgs) {
cont = 0;
}
}, 18);
#container {
width: 48px;
height: 48px;
display: block;
overflow: hidden;
}
#container img {
width: 2880px;
height: 48px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<img src="https://visualcraftsman.com/doh/rdWaitAll.png" alt="My super animation" />
</div>
fiddle
One idea is to use setTimeout() recursively and change the delay depending on the animation's frame number.
// define variables
const $img = $('#container').find('img');
const imgWidth = 48;
const numImgs = 60;
var cont = 0;
var delay = 18;
function advance() {
// increment frame number, loop back to zero
cont = cont == numImgs ? 0 : cont + 1;
// calculate positioning margin
let position = -1 * (cont * imgWidth);
// calculate viewing time for this frame
let delay = cont == 0 ? 1000 : 18;
// position the image
$img.css('margin-left', position);
// schedule next frame advance
setTimeout(advance, delay);
}
// initiate the recursive loop
advance();
#container {
width: 48px;
height: 48px;
display: block;
overflow: hidden;
}
#container img {
width: 2880px;
height: 48px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container">
<img src="https://visualcraftsman.com/doh/rdWaitAll.png" alt="My super animation" />
</div>

How to use the counter when the counter appears on the page and not when the page is loaded

In my project, there should be a counter.
I want the counter to start when scrolling is done, not when the screen is loading.
I used "reveal" for this, but still the counter starts when the screen loads.
In the project in question, I like when scrolling the page from top to bottom and from bottom to top "reveal" arrives, the counter starts.
How can I solve this problem? Thanks in advance for your guidance.
//counter
const counter = (EL) => {
const duration = 4000; // Animate all counters equally for a better UX
const start = parseInt(EL.textContent, 10); // Get start and end values
const end = parseInt(EL.dataset.counter, 10); // PS: Use always the radix 10!
if (start === end) return; // If equal values, stop here.
const range = end - start; // Get the range
let curr = start; // Set current at start position
const loop = (raf) => {
if (raf > duration) raf = duration; // Stop the loop
const frac = raf / duration; // Get the time fraction
const step = frac * range; // Calculate the value step
curr = start + step; // Increment or Decrement current value
EL.textContent = parseInt(curr, 10); // Apply to UI as integer
if (raf < duration) requestAnimationFrame(loop); // Loop
};
requestAnimationFrame(loop); // Start the loop!
};
document.querySelectorAll("[data-counter]").forEach(counter);
//counter
// reveal
function reveal() {
var reveals = document.querySelectorAll(".reveal");
for (var i = 0; i < reveals.length; i++) {
var windowHeight = window.innerHeight;
var elementTop = reveals[i].getBoundingClientRect().top;
var elementVisible = 10;
if (elementTop < windowHeight - elementVisible) {
reveals[i].classList.add("active");
} else {
reveals[i].classList.remove("active");
}
}
}
window.addEventListener("scroll", reveal);
// reveal
.container{
overflow-y: scroll;
}
.pre-reveal{
width: 100%;
height: 80vh;
border: 1px solid red;
}
/* reveal */
.reveal {
border: 1px solid green;
position: relative;
opacity: 0;
margin-top: 1rem;
}
.reveal.active {
opacity: 1;
}
/* reveal */
<div class="container">
<div class="pre-reveal"></div>
<div class="reveal">
<span>A: </span>
<span data-counter="10">0</span>
<br>
<span>B: </span>
<span data-counter="2022">1000</span>
<br>
<span>C: </span>
<span data-counter="0">9999</span>
<br>
<span>D: </span>
<span data-counter="-1000">1000</span>
<br>
<span>E: </span>
<span data-counter="666">0</span>
<br>
</div>
</div>

How can I add 1% to the width of a div inside of a loop using only JS?

I've been trying to add 1% to the width of the div using JS. I want to make it so that the bar's width increases in a smooth way ( I thought about using animations but it didn't work cause I need to specify conditions).
window.onload = function(){
for (let i = 0; i < 5; i++) {
const bar = document.querySelectorAll(".child-bar")[i];
for (let j = 0; j < 82; j++) {
//alert("j" + j);
console.log("bar width: "+ bar.style.width)
bar.style.width += '1%';
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="skill">
<label for="HTML">HTML</label>
<div class="parent-bar">
<span class="child-bar"></span>
<h4>82%</h4>
</div>
</div>
Maybe this example will help you?
window.onload = function() {
document.querySelectorAll(".parent-bar").forEach((el) => {
const barNode = el.querySelector(".child-bar");
const valueNode = el.querySelector("h4");
const max = 82;
const duration = 100;
let value = 0;
const tick = () => {
barNode.style.width = `${value}%`;
valueNode.innerHTML = `${value}%`;
if (value++ < max) setTimeout(tick, duration);
}
tick();
})
}
.child-bar {
display: block;
background: black;
height: 1rem;
width: 0;
transition: 0.1s 0s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="skill">
<label for="HTML">HTML</label>
<div class="parent-bar">
<span class="child-bar"></span>
<h4></h4>
</div>
<div class="parent-bar">
<span class="child-bar"></span>
<h4></h4>
</div>
<div class="parent-bar">
<span class="child-bar"></span>
<h4></h4>
</div>
</div>
Here's a solution with verbose code and commentary for clarity, using setInterval.
(Note that span elements have display: inline by default, which is why setting their width has no effect.)
// Sets options and calls main function
const myOptions = {
MAX_WIDTH_PERCENT: 82,
FRAME_COUNT: 100,
FRAME_DURATION: 20
};
animateBars(myOptions);
// Defines main function
function animateBars(options){
const
// Destructures options object to make local variables
{ MAX_WIDTH_PERCENT, FRAME_COUNT, FRAME_DURATION } = options,
// Defines function to increment width
increment = (value) => value += MAX_WIDTH_PERCENT / FRAME_COUNT,
// Defines function to update bar (after incrementing width)
incrementAndupdateBar = (bar, oldWidth, timerId) => {
newWidth = Math.min(increment(oldWidth), MAX_WIDTH_PERCENT);
bar.style.width = newWidth + "%";
bar.nextElementSibling.textContent = Math.floor(newWidth) + "%"; // (For demo)
// Stops repeating the setInterval's function
if(newWidth == MAX_WIDTH_PERCENT){
clearInterval(timerId);
}
return newWidth; // Returns updated value
};
// Loops through bars
for(let bar of document.querySelectorAll(".child-bar")){
// Repeatedly updates current bar, keeping track of width as we go
let
width = 0, // Initializes width for the current bar
timerId; // ID for use by setInterval & clearInterval
timerId = setInterval( // (Returns an ID for later use)
// Takes 2 args: a func and a duration (in ms) to delay inbetween
function(){width = incrementAndupdateBar(bar, width, timerId);},
FRAME_DURATION
);
}
}
.parent-bar{ width: 300px; border: 1px dotted grey; }
/* spans ignore "width" by default; "inline-block" solves this */
.child-bar{ display: inline-block; height: 1em; background-color: lightgreen; }
h4{ margin: 0; text-align: center; }
<div class="parent-bar">
<span class="child-bar"></span>
<h4></h4>
</div>

Creating a volume bar javascript

I need to create an audio player, and I can't see how to convert it to javascript.
This is what I have so far...
HTML:
<audio id="music" src="http://www.sousound.com/music/healing/healing_01.mp3"
data-title="Title of the audio" data-author="Author's name"></audio>
<span class="volumeScaleHolder">
<span id="progressBar">
<span id="myVolume"></span>
</span>
</span>
CSS:
#progressBar {
width: 100%;
height: 8px;
background-color: #666666;
display: inline-block;
}
#myVolume {
width: 10%;
height: 100%;
background-color: #B4BB6B;
display:block;
}
border: 0;
}
.volumeScaleHolder {
padding:0;
margin: 3px 0;
}
JAVASCRIPT:
var audio = document.getElementById("music");
var audioVolumeBar = document.getElementById("progressBar");
var myVolumeBar = document.getElementById("myVolume");
var audioVolume = audio.volume;
function volumeAsProgressBar(volume) {
var audioVolumeProgressBar = document.getElementById("progressBar");
var audioVolumeMyBar = document.getElementById("myVolume");
var volume = audioVolume;
var totalVolume = 1;
var minVolume = 0;
var maxVolume = 1;
}
alert(audio.volume);
myVolumeBar.innerHTML = myVolumeBar.innerHTML + volumeAsProgressBar(audioVolume);
I know the javascript is doing nothing. It is because I don't know how to proceed. So what I am trying to do is to check the value of the audio's volume, and then reflect it in the horizontal bar.... It is just some training, so I am not allowed to use any plugin, <progress> or input type range.
I have added width:10% to #myVolume, just to make sure it is there.
Then... I don't know how can I pass the value of the volume (from 0 to 1) to somehow a percentage, because I guess the bar works as a percentage, right?
I don't need anyone giving me the solution code....but some help about what should I think of...
I have made this jsfiddle, although it is quite useless... :(
https://jsfiddle.net/zuL6umjo/1/
To create a bar capable of changing the volume of a audio element here is a code that will help.
var e = document.querySelector('.volume-slider-con');
var eInner = document.querySelector('.volume-slider');
var audio = document.querySelector('audio');
var drag = false;
e.addEventListener('mousedown',function(ev){
drag = true;
updateBar(ev.clientX);
});
document.addEventListener('mousemove',function(ev){
if(drag){
updateBar(ev.clientX);
}
});
document.addEventListener('mouseup',function(ev){
drag = false;
});
var updateBar = function (x, vol) {
var volume = e;
var percentage;
//if only volume have specificed
//then direct update volume
if (vol) {
percentage = vol * 100;
} else {
var position = x - volume.offsetLeft;
percentage = 100 * position / volume.clientWidth;
}
if (percentage > 100) {
percentage = 100;
}
if (percentage < 0) {
percentage = 0;
}
//update volume bar and video volume
eInner.style.width = percentage +'%';
audio.volume = percentage / 100;
};
.volume-slider-con{
height:10px;
width:50%;
position:relative;
background-color:#ddd;
}
.volume-slider{
height:100%;
width:100%;
position:relative;
background-color:red;
}
<audio src="http://www.sousound.com/music/healing/healing_01.mp3" controls></audio>
<div class="volume-slider-con">
<div class="volume-slider"></div>
</div>
Your almost there. Here's an updated fiddle: https://jsfiddle.net/zuL6umjo/8/
Here is a way to expand/shrink your volume bar based off of the volume from the audio element.
function updateVolume () {
myVolumeBar.style.width = audioVolume * 100 + '%';
}
updateVolume();

Categories

Resources