I'm trying to create a progress bar for a product card track so by any click of the user on the prev and next buttons (which would scroll back or forward) the progress bar would advance or backup.
here's the code I came up with. the problem is the first click doesn't show any result and the prev button acts like the next button for the first time. It's like the code is one step behind.
I'm very new to javaScript and I can't figure out how this could happen.
const productScroll = () => {
rightButton.onclick = function () {
let scrollLeft = document.querySelector('#ProductSlider').scrollLeft;
let scrollPercent = ((scrollLeft - 0) / (5033 - 0)) * (100 - 0) + 0;
document.querySelector('div.progress-bar').style.width = `${scrollPercent}%`;
};
leftButton.onclick = function () {
let scrollLeft = document.querySelector('#ProductSlider').scrollLeft;
let scrollPercent = ((scrollLeft - 0) / (5033 - 0)) * (100 - 0) + 0;
document.querySelector('div.progress-bar').style.width = `${scrollPercent}%`;
};
If youre always 1 step behind, it could be that your percentage calculation is wrong. For example, if you have 5 steps and want to show progress for each step, starting at 1 and ending at 5, your progress bar needs to have 4 steps instead:
1 > 2 > 3 > 4 > 5 = 4 steps (total - 1)
In percentages, it looks like this for a 5 step progress bar:
1: 0%
2: 25%
3: 50%
4: 75%
5: 100%
Notice each increase is 25% (1/4) and not 20% (1/5).
So in abstract shape, your calculation would need to be:
((scroll / max) * (steps - 1)) / (steps - 1) * 100%
Which means your scrollLeft / 5033 needs to be between 0 and 4, divided by 4, then turned into a percentage:
const percentage = ((scrollLeft / 5033) * 4) / 4 * 100;
To create a progress bar, first create two “div” tag elements named id Progress_Status and myprogressbar.
To add a numeric label to indicate how far the user is in the process, the element inside or outside the progress bar is required which will display the progress status which in this case is myprogressbar.
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
</div>
Adding CSS:
#Progress_Status {
width: 50%;
background-color: #ddd;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
}
Adding JavaScript:
Next, Code below creates a dynamic progress bar(animated) using JavaScript functions update and scene.
function update() {
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 100) {
clearInterval(identity);
} else {
width++;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
Firstly, you fetch the element by id and then set starting width to 1. For the purpose of working example, i used setintervel to show the progression of progress bar.
All we are doing here is calling the scene function which checks if width is less than 100. If yes, then stop loader by clearing the interval. If not, then increment the width and add it to the with and progress label div for showing the progress in percentenge.
Complete Code:
function update() {
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 100) {
clearInterval(identity);
} else {
width++;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
#Progress_Status {
width: 50%;
background-color: #ddd;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
}
<!DOCTYPE html>
<html>
<body>
<h3>Example of Progress Bar Using JavaScript</h3>
<p>Download Status of a File:</p>
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
</div>
<br>
<button onclick="update()">Start Download</button>
</body>
</html>
Related
I have some sort of poll where you vote either YES and NO and based on the votes it creates a poll chart (by creating two divs inside another div that has a set width and setting the width of the first two divs the percentage of YES and NO votes out of the total votes). You can see the project for a better understanding by clicking HERE.
I want it to appear animated as if it were in CSS with transition: width 100ms linear; just like here:
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
<style>
.poll{
height: 50px;
width: 300px;
background-color: black;
transition: all 300ms;
}
.poll:hover{
width: 500px;
}
</style>
</head>
<body>
<div class="poll"></div>
</body>
</html>
However, whenever I add something similar to the class of my divs I see no change. The divs in question are created in this function:
function renderPoll(){
container.innerHTML=''; //reset container
let poll1 = document.createElement('div');
let poll2 = document.createElement('div');
poll1.classList.add('poll-attr');
poll2.classList.add('poll-attr');
let innerTextPoll = Math.round(calcPerc()); //calcPerc() calculates the percent of YES votes with the equation percentage = (100*NumberOfYES)/NumberOfVotes
poll1.style.width = calcPerc() + '%';
poll2.style.width = 100-calcPerc() + '%';
poll1.innerText = innerTextPoll + '%';
poll2.innerText = 100-innerTextPoll + '%';
container.appendChild(poll1);
container.appendChild(poll2);
}
I am not nearly experienced enough to figure this out so any input is appreciated!
Bulding on your code and #Noel Maróti answer, indeed all you have to do is set interval for animating the polls after you add them to the container.
function renderPoll() {
container.innerHTML = ''; //reset container
let poll1 = document.createElement('div');
let poll2 = document.createElement('div');
poll1.classList.add('poll-attr');
poll2.classList.add('poll-attr');
let innerTextPoll = Math.round(calcPerc()); //calcPerc() calculates the percent of YES
poll1.innerText = innerTextPoll + '%';
poll2.innerText = 100 - innerTextPoll + '%';
container.appendChild(poll1);
container.appendChild(poll2);
var target_length = 300;
animation(poll1, 0, (calcPerc()) * target_length / 100);
animation(poll2, 0, (100 - calcPerc()) * target_length / 100);
}
function calcPerc() {
return 75;
}
function animation(elem, from, to) {
let id = null;
let width = from || 0;
var speed = 2.5;
requestAnimationFrame(frame);
function frame() {
if (width < to) {
width += speed;
elem.style.width = width + "px";
requestAnimationFrame(frame);
}
}
}
renderPoll();
.poll-attr {
border: 1px solid blue;
height: 50px;
background: lightyellow;
}
.poll {
height: 50px;
width: 300px;
background-color: black;
transition: all 300ms;
}
.poll:hover {
width: 500px;
}
<div class="poll"></div>
<div id="container"></div>
You can do it easily like this:
function animation () {
let id = null;
const elem = document.querySelector(".poll");
let width = 300; // default width
clearInterval(id);
id = setInterval(frame, 5); // changing the number will effect the speed of the animation
function frame() {
if (width == 500) { // if the width is 500px, then finish animation
clearInterval(id); // finish animation
} else {
width++;
elem.style.width = width + "px";
}
}
}
I have 2 boxes / divs. An outer box and an inner box. The inner box is always contained within the outer box. Given the following info:
outer box: width=200 height=100
inner box: aspect ratio=16/9
In JavaScript, how do I calculate the maximum size of the inner box such that its aspect ratio is preserved?
I know you're asking for JavaScript specifically, but this is pretty simple to do in CSS with aspect-ratio, and if you need the dimensions in JS you could just grab the rendered dimensions.
const pre = document.querySelector('pre');
const aspect = document.querySelector('.ratio');
// getboundingClientRect also works.
const dimensions = window.getComputedStyle(aspect);
pre.textContent = `width: ${dimensions.width}, height: ${dimensions.height}`;
.container {
width: 200px;
height: 100px;
}
.ratio {
aspect-ratio: 16 / 9;
/* Just keeping it within the constaints */
max-height: 100px;
border: 1px solid red;
}
.no-ratio {
border: 1px solid red;
}
<div class="container">
<div class="ratio">
16:9
</div>
</div>
<pre></pre>
<div class="container">
<div class="no-ratio">
Not 16:9.
</div>
</div>
<pre></pre>
Get the outer aspect ratio and use that to determine if the inner box needs to be letter-boxed (landscape, shorter) or pillar-boxed (portrait, narrower) relative to the outer box. Calculate the inner dimensions based on that. You can also calculate the offsets needed to center it.
const outerWidth = 200;
const outerHeight = 100;
const aspectRatio = 16/9;
let innerWidth, innerHeight, innerTop, innerLeft;
if (outerWidth / outerHeight > aspectRatio) {
innerWidth = outerHeight * aspectRatio;
innerHeight = outerHeight;
innerLeft = (outerWidth - innerWidth) / 2;
innerTop = 0;
} else {
innerWidth = outerWidth;
innerHeight = outerWidth / aspectRatio;
innerLeft = 0;
innerTop = (outerHeight - innerHeight) / 2;
}
let outerBoxWidth = 200;
let outerBoxHeight = 100;
let maxInnerBoxWidth = ((outerBoxWidth / 16) | 0);
let maxInnerBoxHeight = ((outerBoxHeight / 9) | 0);
let widthLower = maxInnerBoxHeight > maxInnerBoxWidth;
if(widthLower){
let innerBoxHeight = 9 * maxInnerBoxWidth;
let innerBoxWidth = 17 * maxInnerBoxWidth;
}else{
let innerBoxHeight = 9 * maxInnerBoxHeight;
let innerBoxWidth = 17 * maxInnerBoxHeight;
}
Based on the answer from Ouroborus I came up with the following which proves it works.
const getBoxes = (outerWidth, outerHeight, innerAspectRatio) => {
const outer = { width: outerWidth, height: outerHeight, aspectRatio: outerWidth / outerHeight }
const inner = { width: null, height: null, aspectRatio: innerAspectRatio }
const pillarBoxed = outer.aspectRatio > inner.aspectRatio
inner.width = !pillarBoxed ? outer.width : outer.height * inner.aspectRatio
inner.height = pillarBoxed ? outer.height : outer.width / inner.aspectRatio
return { outer, inner }
}
const adjust = 40
const boxes1 = getBoxes(160 + adjust, 90, 16/9)
const boxes2 = getBoxes(160, 90 + adjust, 16/9)
// display purposes only
const displayBoxes = (boxes, outerId, innerId) => {
const outerEl = document.getElementById(outerId)
outerEl.style.width = boxes.outer.width + 'px'
outerEl.style.height = boxes.outer.height + 'px'
const innerEl = document.getElementById(innerId)
innerEl.style.width = boxes.inner.width + 'px'
innerEl.style.height = boxes.inner.height + 'px'
}
displayBoxes(boxes1, 'outer1', 'inner1')
displayBoxes(boxes2, 'outer2', 'inner2')
// console.log('boxes1', boxes1)
// console.log('boxes2', boxes2)
#outer1, #outer2 {
background-color: black;
display: inline-flex;
justify-content: center;
align-items: center;
margin: 10px;
}
#inner1, #inner2 {
background-color: red;
}
<div id="outer1">
<div id="inner1"></div>
</div>
<div id="outer2">
<div id="inner2"></div>
</div>
To make this easy to test, I made the outer box 160x90 BUT 40px wider (first example) and 40px taller (second example). Given that this works nicely with a 16/9 aspect ratio, there should be 20px left and right in the first example and 20px above and below in the second example. Which there is!
Also, the inner box should be 160x90 in both examples, which it is!
Proof that this code works as expected.
How can i set the duration of an transition/animation to pixel per second?
You see the two different wrappers, with a different total height depending on it's colored content. The total speed is the same, given from the css transition attribute, thats okay if you want several animations with the same duration. For a smoother look i want to set this transition/animation effect to pixel per second so it takes as long as many pixels there. More content = more pixel = longer animation.
How can i achieve this with vanilla javascript or even css?
var wrapper1 = document.getElementById('wrapper1');
var wrapper2 = document.getElementById('wrapper2');
var header1 = document.getElementById('header1');
var header2 = document.getElementById('header2');
var wrapper1CmputedHeight = wrapper1.scrollHeight;
var wrapper2CmputedHeight = wrapper2.scrollHeight;
header1.addEventListener('click', function() {
if (wrapper1.style.height === '60px') {
wrapper1.style.height = wrapper1CmputedHeight + 'px';
} else {
wrapper1.style.height = '60px';
}
})
header2.addEventListener('click', function() {
if (wrapper2.style.height === '60px') {
wrapper2.style.height = wrapper2CmputedHeight + 'px';
} else {
wrapper2.style.height = '60px';
}
})
#wrapper1,
#wrapper2 {
background: #fff;
border: 1px solid grey;
overflow: hidden;
transition: height .2s linear
}
#wrapper1 {
margin-bottom: 40px
}
#header1,
#header2 {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer
}
#content1 {
height: 20px;
background: blue
}
#content2 {
height: 600px;
background: green
}
<div id="wrapper1" style="height: 60px">
<div id="header1">
<span>header</span>
</div>
<div id="content1"></div>
</div>
<div id="wrapper2" style="height: 60px">
<div id="header2">
<span>header</span>
</div>
<div id="content2"></div>
</div>
The only way to do this with css transitions, is to dynamically calculate the duration of the transition using a little javascript. So, in your code, I would remove the duration for the transition rule in your css, i,e.
#wrapper1,
#wrapper2 {
background: #fff;
overflow: hidden;
transition: height linear
}
and I would instead set the duration in the click handler as follows:
header1.addEventListener('click', function () {
if(wrapper1.style.height === '60px') {
wrapper1.style.height = wrapper1CmputedHeight + 'px';
wrapper1.style.transitionDuration=(wrapper1CmputedHeight/100)+"s";
} else {
wrapper1.style.height = '60px';
}
})
So in this case, I've used a speed of 100px per second (this is the /100 part in the above code).
I found this example here but it seems to do the trick for you (after some tweaking). In this case it implements a quartic interpolation, however you could adjust this algorithm to linear / other if so desired.
//
// Animate
//
var btn1 = document.querySelector('.animate');
btn1.addEventListener('click', function() {
reset();
animate();
btn1.disabled = true;
});
//
// http://easings.net/#easeInOutQuart
// t: current time
// b: beginning value
// c: change in value
// d: duration
//
function easeInOutQuart(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
function reset() {
document.querySelector('.square').style.width = Math.floor((Math.random() * 500) + 1) + "px";
}
function animate() {
var rect = document.querySelector('.square');
var from = 0;
var to = window.getComputedStyle(rect, null).getPropertyValue("width").split('px')[0];
var duration = to * 10;
var start = new Date().getTime();
var timer = setInterval(function() {
var time = new Date().getTime() - start;
var width = easeInOutQuart(time, from, to - from, duration);
rect.style.width = width + "px";
if (time >= duration) {
clearInterval(timer);
btn1.disabled = false;
}
}, 1000 / 60);
rect.style.width = from;
}
reset();
.square {
position: relative;
display: block;
width: 30px;
height: 30px;
background-color: #f00;
}
<div class="square"></div>
<button class="animate">Animate</button>
I'm using a framework that requires explicit pixels for a modal window that will be used to display messages to the end user at various points in the system. The messages will always be plain text so I want to base the width on the number of chars in the message. A message could be 2 words or 2 paragraphs.
At first I tried straight multiplication, which works great for content.length of 150-300 but anything <150 is too small and >300 gets way too large.
I need help with an algorithm that would effect a smaller number greater than it would effect a large number. I would like it to have the rough effect of:
getWidthForContent("small message") = 120; //13 chars
getWidthForContent("another reasonable length for a modal message") = 300; //45 chars
getWidthForContent("lorem ipsum...") = 900; //600+ chars
Another way you could look at this is trying to find a multiplier value from a curve. Here is a really sloppy approach:
determineBestWidth: function (contentLength) {
var width = 900; //max
if (contentLength < 200) {
width = contentLength * 2;
if (contentLength < 125) {
width = contentLength * 3;
if (contentLength < 50) {
width = contentLength * 5;
if (contentLength < 20) {
width = contentLength * 10;
if (contentLength < 10) {
width = contentLength * 20;
}
}
}
}
}
return width;
},
forgive me for my lack of math skills
You need to create a hidden div with the same content of your modal.
Then with some CSS tricks you will be able to do what you want (if I understood what you wanted) :
var modal = document.getElementById("modal");
var modalHidden = document.getElementById("modalHidden");
modalHidden.innerHTML = modal.innerHTML;
var height = (modalHidden.clientHeight + 1) + "px";
var width = (modalHidden.clientWidth + 1) + "px";
var result = document.getElementById("result");
result.innerHTML = "Height : " + height + "<br/>Width : " + width;
if (parseInt(width.substring(0, width.length - 2)) < 500)
{
modal.style.width = width;
}
#modal {
border: 1px solid darkblue;
border-radius: 4px;
text-align: justify;
background-color: #a4b8ff;
padding: 10px;
width: 500px;
word-wrap :break-word;
margin-bottom: 40px;
}
#modalHidden
{
visibility: hidden;
white-space: nowrap;
width: auto;
height: auto;
position: absolute;
}
#info {
color: grey;
margin-bottom: 40px;
}
<div id="info">Add some text and see that the width changes. Just use the variable in the JS and you're good.</div>
<div id="modal">
Some short text
</div>
<div id="modalHidden"></div>
<div id="result"></div>
I made a jsFiddle, if you wan to "play" with it.
Credits go to Bacon Ipsum.
I have this following animation:
<!DOCTYPE HTML>
<html>
<head>
<style>
.example_path {
position: relative;
overflow: hidden;
width: 530px;
height: 30px;
border: 3px solid #000;
}
.example_path .example_block {
position: absolute;
background-color: blue;
width: 30px;
height: 20px;
padding-top: 10px;
text-align: center;
color: #fff;
font-size: 10px;
white-space: nowrap;
}
</style>
<script>
function move(elem) {
var left = 0
function frame() {
left+=10 // update parameters
elem.style.left = left + 'mm' // show frame
if (left == 10000) // check finish condition
clearInterval(id)
}
var id = setInterval(frame, 1) // draw every 1ms
}
</script>
</head>
<body>
<div onclick="move(this.children[0])" class="example_path">
<div class="example_block"></div>
</div>
</body>
</html>
as you see, the blue block moves out of the rectangle if it crosses it. how do i have the blue block oscillate about the rectangular border to and fro keeping the speed constant throughout ...
(in my case the speed is 10 m/s aka 10 mm/ms)
You need to update code as: Here is working JSfiddle
function move(elem) {
var left = 0
var fwdMove = true;
function frame() {
if (left < 0) {
fwdMove = true;
} else if (left > 520) {
fwdMove = false;
}
fwdMove?left += 10:left -= 10
elem.style.left = left + 'px' // show frame
}
var id = setInterval(frame, 1) // draw every 1ms
}
We begin by adding a variable to track the direction that we're heading in. We don't want to modify how fast you're moving, so we use a positive or negative 1 to affect the position.
var direction = 1; // 1=Right, -1=Left
var left = 0
function frame() {
left+=(10 * direction); // update parameters
Because mm are a print-unit, and we're working in the browser, we'll change it to use px. If you really need to use mm, you'll have to find a way of converting between them for the box to stop at the appropriate spot.
elem.style.left = left + 'px' // show frame
Finally, we check whether we've gone past the bounds of the box, and if so, we put it back in the box and reverse the direction;
if (left <= 0) {
direction = 1; // Begin moving to the left
left = 0; // Put the box back in the path
} else if (left >= (530 - 20)) {
direction = -1; // Begin moving to the right
left = (530 - 20); // Put the box back in the path
}
JSFiddle.