Set specific gradient from percentages - javascript

I have a code that generates color from values ( from 0 to 200% )
Actually the specter is Green-yellow-red.
I would like to change the gradient to Green-Grey-Red : green if percentage < 100% , grey if in 100% field, red if > 100%.
I don't see how to do it using mathematics formula.
window.onload = function() {
let colorShower = document.querySelector('.color-shower')
let colorSlider = document.querySelector('#colorSlider')
let percentage = (200 -colorSlider.value) /2
let buildColor = (capHue) => `hsl(${capHue},90%,60%)`
colorShower.style.backgroundColor = buildColor(percentage)
colorSlider.addEventListener('input', e => {
percentage = (200 -colorSlider.value) /2
colorShower.style.backgroundColor = buildColor(percentage)
})
}
.color-shower {
width: 100%;
height: 50px;
}
.color-slider {
width: 100%;
}
<div class = "color-shower"></div>
<input type = "range" min = "1" max = "200" value = "1" class = "color-slider" id = "colorSlider">

Edit: OK, I updated my response with a dynamic solution. I created wrapper <div> elements around the display/input elements. These wrappers allow you to define both a start-hue and end-hue data attribute. These are used as the gradient start/end for the display.
It's easy to miss (since you have to scroll all the way to the bottom), but with the plugin below; you can call GradientSlider() after your page loads.
The "static" default properties are at the bottom of the function definition. This can easily be re-written into an ES5/6 class.
GradientSlider.defaultOptions = {
selector : '.gradient-slider'
};
Various sliders are created below. I borrowed Infodev's absolute-value trick to adjust the saturation value as the slider approaches and 50%.
function GradientSlider(options) {
let opts = Object.assign({}, GradientSlider.defaultOptions, options);
construct(opts.selector); // Begin...
function construct(selector) {
Array.from(document.querySelectorAll(selector))
.forEach(gradientSlider => initializeSlider(gradientSlider));
}
function initializeSlider(gradientSlider) {
let hueStart = parseInt(gradientSlider.getAttribute('data-start-hue'), 10);
let hueEnd = parseInt(gradientSlider.getAttribute('data-end-hue'), 10);
let display = gradientSlider.querySelector('.gradient-slider-display');
let slider = gradientSlider.querySelector('.gradient-slider-input');
slider.addEventListener('input', onSliderChange);
let percentage = getSliderPercentage(slider);
let hue = percentage < 50 ? hueStart : hueEnd;
display.style.backgroundColor = calculateColor(hue, percentage);
}
function onSliderChange(e) {
let gradientSlider = e.target.parentElement;
let hueStart = parseInt(gradientSlider.getAttribute('data-start-hue'), 10);
let hueEnd = parseInt(gradientSlider.getAttribute('data-end-hue'), 10);
let display = gradientSlider.querySelector('.gradient-slider-display');
let percentage = getSliderPercentage(e.target);
let hue = percentage < 50 ? hueStart : hueEnd;
display.style.backgroundColor = calculateColor(hue, percentage)
}
function calculateColor(hue, percentage) {
return `hsl(${hue}, ${Math.abs(50 - percentage)}%, 50%)`;
}
function getSliderPercentage(slider) {
let value = parseInt(slider.value, 10);
let minValue = parseInt(slider.getAttribute('min'), 10);
let maxValue = parseInt(slider.getAttribute('max'), 10);
return scaleBetween(value, 0, 100, minValue, maxValue);
}
// Source: https://stackoverflow.com/a/60514474/1762224
function scaleBetween(n, tMin, tMax, sMin, sMax) {
return (tMax - tMin) * (n - sMin) / (sMax - sMin) + tMin;
}
}
GradientSlider.defaultOptions = {
selector : '.gradient-slider'
};
GradientSlider(); // Call the plugin...
.gradient-slider,
.gradient-slider > .gradient-slider-display,
.gradient-slider > .gradient-slider-input {
width: 100%;
}
.gradient-slider-display {
height: 50px;
}
<div class="gradient-slider" data-start-hue="120" data-end-hue="0">
<div class="gradient-slider-display"></div>
<input class="gradient-slider-input" type="range" min="1" max="200" value="1" />
</div>
<div class="gradient-slider" data-start-hue="240" data-end-hue="300">
<div class="gradient-slider-display"></div>
<input class="gradient-slider-input" type="range" min="50" max="150" value="75" />
</div>
<div class="gradient-slider" data-start-hue="30" data-end-hue="180">
<div class="gradient-slider-display"></div>
<input class="gradient-slider-input" type="range" min="0" max="10" value="7" />
</div>

I have found the solution but it's not perfect
Using this formulahsl(${hue}, ${Math.abs(100 - perc)}%, 50%);
window.onload = function() {
let colorShower = document.querySelector('.color-shower')
let colorSlider = document.querySelector('#colorSlider')
let percentage = (200 -colorSlider.value) /2
let buildColor = (capHue) => `hsl(${capHue}, ${Math.abs(100 - colorSlider.value)}%, 50%)`
colorShower.style.backgroundColor = buildColor(percentage)
colorSlider.addEventListener('input', e => {
percentage = (200 -colorSlider.value) /2
colorShower.style.backgroundColor = buildColor(percentage)
})
}
.color-shower {
width: 100%;
height: 50px;
}
.color-slider {
width: 100%;
}
<div class = "color-shower"></div>
<input type = "range" min = "1" max = "200" value = "1" class = "color-slider" id = "colorSlider">
But I still have some orange.

Related

Cannot implement shuffle bars feature in sorting algorithm visualizer - setTimeout updates are not rendering

I'm making a sorting algorithm visualizer project and I'm in the process of implementing a shuffle button. The visualization involves bars of different heights getting moved around. I would like the shuffle button to modify the bar array one step at a time, showcasing each swap at a high speed. I've tried tweaking many different things (some of which do move bars around in a strange manner), but I can't seem to get the desired functionality to work. Here's some of the relevant code:
// Swap does not modify the original array, but the implementation details don't really affect the result.
// It basically swaps two elements in the bar array, and then returns the updated array.
const swapBars = (bars, bar1, bar2) => {
if (!bars || !bar1 || !bar2) {
return;
}
const _bars = bars;
let _bar1 = bar1;
let _bar2 = bar2;
const tempLeft = _bar1.left;
_bar1.left = _bar2.left;
_bar2.left = tempLeft;
const temp = _bar1;
_bar1 = _bar2;
_bar2 = temp;
return _bars;
};
// Init bars is basically synchronous shuffle. It takes the array that is created and shuffles it
// because the array should begin in a shuffled state. This is working properly.
const initBars = (bars) => {
let currentIndex = bars.length - 1;
while (currentIndex > 0) {
// randomIndex will always be different from currentIndex, so each bar will always shuffle
const randomIndex = Math.floor(Math.random() * currentIndex);
swapBars(bars, bars[currentIndex], bars[randomIndex]);
currentIndex--;
}
setBarsToRender(bars);
};
// createBarArray is what is used to actually populate an empty array with bars depending on a number passed
// through by a slider. This is also working properly.
const createBarArray = (quantity) => {
let bars = [];
const width = calcWidthPercentage(quantity);
for (let i = 0; i < quantity; i++) {
const height = calcHeightPercentage(quantity, i + 1);
const left = calcLeftPosPercentage(quantity, i + 1);
bars.push({ correctPos: i, height: height, width: width, left: left });
}
return initBars(bars);
};
// shuffleBars seems to be broken. I've tried many different things, and this is just the latest snapshot of it.
// It is being called when the shuffle button is being clicked using `shuffleBars(barsToRender)` where barsToRender is the stateful value that is being rendered.
const shuffleBars = (bars) => {
let currentIndex = bars.length - 1;
while (currentIndex > 0) {
const randomIndex = Math.floor(Math.random() * currentIndex);
setTimeout(() => {
setBarsToRender((prev) => {
return swapBars(prev, prev[currentIndex], prev[randomIndex]);
});
}, 50 * (bars.length - currentIndex));
currentIndex--;
}
};
If I do something like moving the swapBars call inside setBarsToRender outside of it and then
do setBarsToRender[...bars], I can see some of the bars moving, but not with the intended behavior (the smallest bar is the only one that keeps swapping). I'm not sure if I'm misunderstanding how state updates work inside setTimeout, or if it's something else, so I'd greatly appreciate some help.
I removed the setTimeout and used a transition delay to create the staggered effect.
Working demo below:
const swapBars = (bars, bar1, bar2) => {
if (!bars || !bar1 || !bar2) {
return;
}
const _bars = bars;
let _bar1 = bar1;
let _bar2 = bar2;
const tempLeft = _bar1.left;
_bar1.left = _bar2.left;
_bar2.left = tempLeft;
const temp = _bar1;
_bar1 = _bar2;
_bar2 = temp;
return _bars;
};
const initBars = (bars) => {
let currentIndex = bars.length - 1;
while (currentIndex > 0) {
const randomIndex = Math.floor(Math.random() * currentIndex);
swapBars(bars, bars[currentIndex], bars[randomIndex]);
currentIndex--;
}
return bars;
};
const createBarArray = (quantity) => {
let bars = [];
const width = 100 / quantity;
for (let i = 0; i < quantity; i++) {
const height = width * (i + 1);
const left = width * i;
bars.push({ correctPos: i, height: height, width: width, left: left });
}
return initBars(bars);
};
function Bars({ quantity = 10 }) {
const [barsToRender, setBarsToRender] = React.useState([]);
React.useEffect(() => {
const bars = createBarArray(quantity);
setBarsToRender(bars);
}, [quantity]);
const shuffleBars = () => {
const bars = [...barsToRender];
setBarsToRender(initBars(bars));
};
return (
<div>
<ul
style={{
height: "50vh",
display: "flex",
position: "relative"
}}
>
{barsToRender.map((bar, i) => (
<Bar key={bar.correctPos} bar={bar} index={i} />
))}
</ul>
<button onClick={shuffleBars}>Shuffle</button>
</div>
);
}
function Bar({ bar, index: i }) {
return (
<li
style={{
background: "blue",
height: `${bar.height}%`,
width: `${bar.width}%`,
left: `${bar.left}%`,
position: "absolute",
bottom: 0,
transitionProperty: "left",
transitionTimingFunction: "ease-in-out",
transitionDuration: ".25s",
transitionDelay: `${i*50}ms`
}}
>
<p>{bar.correctPos}</p>
</li>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<Bars />)
p {
font-family: sans-serif;
font-weight: 700;
height: 1.5rem;
width: 1.5rem;
display: grid;
place-content: center;
background: white;
border-radius: 50%;
border: 1px solid;
}
ul {
padding: 0;
list-style-type: none;
}
li {
display: grid;
align-content: end;
justify-items: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

progressbar html tag change

I'am working on progressbar that change the level when reaching some point.
The code.
I want to change the text under the progressbar when the progressbar reach 100 or above 90.
I could change it once but I want to go to next level, like this.
silver ==> gold ==> diamond ==> and more
const step = 5;
var content=document.getElementById('mylevel').innerHTML;
const updateProgress = () => {
const currentWidth = Number(document.getElementById("progressvalue").style.width.replace( "%", ""));
if (currentWidth>=100) {
return;
}
else {
document.getElementById("progressvalue").style.width = `${currentWidth+step}%`;
}
if (currentWidth > 90) {
document.getElementById("mylevel").textContent = "gold";
document.getElementById("progressvalue").style.width = "0%";
}
if (currentWidth > 90 && content == "gold") {
document.getElementById("mylevel").textContent = "diamond";
document.getElementById("progressvalue").style.width = "0%";
}
}
const restart = () => {
document.getElementById("progressvalue").style.width = "0%";
}
.progress {
background-color: #ededed;
width: 100%;
overflow: hidden;
}
#progressvalue {
height: 40px;
background-color: lightgreen;
width: 0%;
}
<div class="progress">
<div id="progressvalue"></div>
</div>
<p id="mylevel">silver</p>
<br />
<button type="button" onclick="updateProgress()">
Update Progress
</button>
<button type="button" onclick="restart()">
Restart
</button>
When the updateprogress is above 90 the silver change to gold, but I need to change again to diamond when the updateprogress is again above 90.
Am I putting the if condition in a wrong place, I tried many times.
I don't know what I'am missing and am new with JavaScript
I started the code but got help here to make it much better (80% of the code done by
teresaPap thanks)
Update
After closer inspection it is an issue of content not updating you need to put it inside updateProgress() or it will forever remain the initial value.
const step = 5;
const updateProgress = () => {
var content = document.getElementById('mylevel').innerHTML;
//the rest of the code
I do however recommend you to improve your if statements. You only need one if for this task.
A better solution
A better solution would be something like this:
Add a hidden value to keep your level progress
</div>
<p id="hiddenlevel">0</p>
<p id="mylevel">silver</p>
<br />
and css:
#hiddenlevel {
height: 0px;
visibility: hidden;
width: 0%;
}
now that you have a hidden value you can wrap up all future ifs in a single one.
const levels = ["silver", "gold", "diamond"]
var mylevel = Number(document.getElementById("hiddenlevel").innerHTML);
if(currentWidth > 90 && mylevel < levels.length){
document.getElementById("hiddenlevel").textContent = mylevel + 1;
document.getElementById("mylevel").textContent = levels[mylevel + 1];
document.getElementById("progressvalue").style.width = "0%";
}
and just like that you can just add a new level inside the levels array and it will be added without issues.
Update 2
Just noticed I made a mistake!
You don't need a hidden element for this: you might end up having to use hidden elements when using plugins, but it was completely unnecessary here :)
Updated code:
const step = 5;
var mylevel = 0;
const updateProgress = () => {
const currentWidth = Number(document.getElementById("progressvalue").style.width.replace( "%", ""));
if (currentWidth>=100) {
return;
}
else {
document.getElementById("progressvalue").style.width = `${currentWidth+step}%`;
}
const levels = ["silver", "gold", "diamond"];
if(currentWidth > 90 && mylevel < levels.length){
mylevel = mylevel + 1;
document.getElementById("mylevel").textContent = levels[mylevel];
document.getElementById("progressvalue").style.width = "0%";
}
}
const restart = () => {
document.getElementById("progressvalue").style.width = "0%";
}
.progress {
background-color: #ededed;
width: 100%;
overflow: hidden;
}
#progressvalue {
height: 40px;
background-color: lightgreen;
width: 0%;
}
<div class="progress">
<div id="progressvalue"></div>
</div>
<p id="mylevel">silver</p>
<br />
<button type="button" onclick="updateProgress()">
Update Progress
</button>
<button type="button" onclick="restart()">
Restart
</button>

I am stuck fixing my issue here with an hp bar

So I am doing an hp bar using js, I make an hp bar with 3000/3000 hp, and I have an input where I can type my damage... But When ever I type 2999, it should be 1/3000 right, and the width of the current Hp bar is 0%, the problem is, when I do 2999 again, it remains 1/3000, and it should be 0/3000, I don't know why. Heres my code:
let damage = 0;
let width = 100;
let minWidth = 0;
let text = '';
let hp = document.getElementById('hp');
let hpText = document.getElementById('hpText');
let currentHp = 3000;
let maxHp = 3000;
hpText.innerText = currentHp + '/' + maxHp;
let setUp = () => {
damage = parseInt(document.getElementById('text').value);
text = document.getElementById('text').value;
if(text.length > 0){
currentHp -= damage;
}
if(currentHp <= 0) {
currentHp = 0;
}
minWidth = (currentHp / maxHp) * 100;
let interval = setInterval(() => {
if(!(width <= minWidth)) {
if(width <= 0) {
currentHp = 0;
hpText.innerText = currentHp + '/' + maxHp;
clearInterval(interval);
alert('ha')
return;
}
width--;
hp.style.width = width + '%';
hpText.innerText = currentHp + '/' + maxHp;
if(width <= minWidth) {
alert(minWidth + " " + width)
clearInterval(interval);
return;
}
}
}, 15);
}
Why make it simple when you can make it complicated? ;)
see also for styling : Custom styling progress bar in CSS
const hp =
{ bar: document.getElementById('hp-bar')
, txt: document.getElementById('hp-bar').nextElementSibling
, itv: null
}
hp.itv = setInterval(()=>
{
hp.bar.value += 10
hp.txt.textContent = `${hp.bar.value} / ${hp.bar.max}`
if ( hp.bar.value >= hp.bar.max) clearInterval( hp.itv )
},200)
progress {
--bar-color : #0c1c499d;
width : 20em;
height : .6em;
color : var(--bar-color);
transition : All 0.2s linear;
}
progress::-moz-progress-bar {
background-color: var(--bar-color);
}
progress ~ span {
display : inline-block;
width : 6.6em;
margin-left : 1em;
padding : .1em .5em;
text-align : right;
border : 1px solid cadetblue;
border-radius : .4em;
transform : translateY(.2em);
}
<progress id="hp-bar" min="0" max="3000" value=0></progress><span>0 / 3000</span>

jQuery UI Slider Missing BG

I'm using a range slider with jQuery UI but when I move the slider, the background line does not show up so it's hard to tell where it is at. How do I highlight the background line only for the selected portion?
jsfiddle is below.
https://jsfiddle.net/zbmt5qrn/
/* INTEREST RATE SLIDER */
$("#interestRange").slider({
min: 0,
max: 100,
step: 1,
values: [10],
slide: function(event, ui) {
for (var i = 0; i < ui.values.length; ++i) {
$("input.interestValue[data-index=" + i + "]").val(ui.values[i]);
}
}
});
$("input.interestValue").change(function() {
var $this = $(this);
$("#interestValue").slider("values", $this.data("index"), $this.val());
});
function handleInterestChange(input) {
if (input.value < 0) input.value = 0;
if (input.value > 24) input.value = 24;
}
var items =[ '8%','24%'];
var oneBig = 100 / (items.length - 1);
$.each(items, function(key,value){
var w = oneBig;
if(key === 0 || key === items.length-1)
w = oneBig/2;
$("#interestLabel").append("<label style='width: "+w+"%'>"+value+"</laben>");
});
I am removing the code about '8%/24%' because it does not seem to be relevant to this question.
There are a couple ways to set the background. You could insert a new element inside #interestRange and change its width based on the slider value. But if you do not need to worry about supporting outdated browsers, it would be much easier to use apply a linear-gradient to the background of #interestRange.
const sliderMin = 0
const sliderMax = 100
const sliderDefaultValue = 10
function setSliderBackground(value){
const sliderRange = sliderMax - sliderMin
const sliderPercent = Math.floor(100 * (value / sliderRange))
$("#interestRange").css({
background: `linear-gradient(90deg, #0000ff ${sliderPercent}%, #ffffff ${sliderPercent}%)`
})
}
function setSliderValue(value){
$("#interestRange").slider("value", value);
}
function setInputValue(value){
$("#interestValue").val(value)
}
$(document).ready(()=>{
$("#interestRange").slider({
min: sliderMin,
max: sliderMax,
step: 1,
// Handle when the user moves the slider
slide: (event, ui)=>{
const sliderValue = ui.value
setInputValue(sliderValue)
setSliderBackground(sliderValue)
}
})
// Handle when the user types in a value
$("#interestValue").change(()=>{
const typedValue = $("#interestValue").val()
setSliderValue(typedValue)
setSliderBackground(typedValue)
})
// Stuff to do when the page loads
setSliderValue(sliderDefaultValue)
setInputValue(sliderDefaultValue)
setSliderBackground(sliderDefaultValue)
})
<link href="https://code.jquery.com/ui/1.12.0-rc.2/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<p>Interest Rate:</p>
<div>
<div id="interestRange"></div>
<div id="interestLabel"></div>
</div>
<div>
<input type="number" id="interestValue" />
</div>

Why isn't it possible to change max-height with % in javascript?

I'm trying to build a responsive menu, with a hamburger icon. I want the menu list to slide in and out, no jquery - pure javascript only.
HTML :
<div id="animation">
</div>
<button id="toggle">Toggle</button>
CSS :
div {
width: 300px;
height: 300px;
background-color: blue;
}
Javascript :
var but = document.getElementById('toggle');
var div = document.getElementById('animation');
var animate = function(type, callback){
var inter = -1, start = 100, end = 0;
if(type==true){
inter = 1;
start = 0;
end = 100;
}
var si = setInterval(function(){
console.log('maxheight');
div.style.maxHeight = (start + inter) + '%';
if(start == end){
clearInterval(si);
}
}, 10);
}
var hidden = false;
but.onclick = function(){
animate(hidden, function(){
hidden = (hidden == false) ? true : false;
});
}
div.style.maxHeight = "50%";
The problem is that proportional height in an element needs a fixed height on the parent, and you didn't provided any parent with a fixed height because for the maxHeight property too the % Defines the maximum height in % of the parent element.
You have to put your div in a parent container with a fixed height, this is your working code:
var but = document.getElementById('toggle');
var div = document.getElementById('animation');
var animate = function(type, callback) {
var inter = -1,
start = 100,
end = 0;
if (type) {
inter = 1;
start = 0;
end = 100;
}
var si = setInterval(function() {
console.log('maxheight');
div.style.maxHeight = (start + inter) + '%';
if (start == end) {
clearInterval(si);
}
}, 10);
}
var hidden = false;
but.onclick = function() {
animate(hidden, function() {
hidden = !hidden ;
});
}
div.style.maxHeight = "50%";
#animation {
width: 300px;
height: 300px;
background-color: blue;
}
#parent {
width: 500px;
height: 500px;
}
<div id="parent">
<div id="animation">
</div>
<button id="toggle">Toggle</button>
</div>
Note:
As stated in comments there are some statements in your JavaScript code that need to be adjusted:
if(type==true) can be written as if(type).
hidden = (hidden == false) ? true : false; can be shortened to hidden = !hidden
There seems to be a few errors with your code. I have fixed the js and added comments to what I have changed
var but = document.getElementById('toggle');
var div = document.getElementById('animation');
var animate = function (type, callback) {
var start = 100,
end = 0;
if (type) {
start = 0;
end = 100;
}
var si = setInterval(function () {
if (type) { // check whether to open or close animation
start++;
} else {
start--
}
div.style.maxHeight = start + '%';
if (start == end) {
clearInterval(si);
}
}, 10);
callback.call(this); // do the callback function
}
var hidden = false;
but.onclick = function () {
animate(hidden, function () {
hidden = !hidden; // set hidden to opposite
});
}
/*make sure parent container has a height set or max height won't work*/
html, body {
height:100%;
margin:0;
padding:0;
}
div {
width: 300px;
height: 300px;
background-color: blue;
}
<div id="animation"></div>
<button id="toggle">Toggle</button>
Example Fiddle

Categories