Yaekiou
Yaekiou

Reputation: 17

Increase or decrease value continuously using mousedown JavaScript

I copied some code from here.

The following code has some errors when the value reaches the minFontSize or maxFontSize.

I don't know how to fix this...

const decreaseFontSizeBtn = document.querySelector(".fontsize-via-btn #decrease");
const increaseFontSizeBtn = document.querySelector(".fontsize-via-btn #increase");
const fontSizeDisplay = document.querySelector(".fontsize-via-btn .current-fontsize");

const defaultFontSize = 20;
const minFontSize = 16;
const maxFontSize = 40;
let currentFontSize = defaultFontSize;

var timeout, interval;

[decreaseFontSizeBtn, increaseFontSizeBtn].forEach(btn => {
    btn.addEventListener("mousedown", () => {
        if (btn.id === "decrease") {
            decreaseFontSize();
            hold(decreaseFontSize);
        }

        if (btn.id === "increase") {
            increaseFontSize();
            hold(increaseFontSize);
        }

        saveFontSize();
    })

    btn.addEventListener("mouseup", clearTimers);
    btn.addEventListener("mouseleave", clearTimers);

    function clearTimers() {
        clearTimeout(timeout);
        clearInterval(interval);
    }
})

function hold(func) {
    timeout = setTimeout(() => {
        interval = setInterval(() => {
            func();
            saveFontSize();
        }, 50)
    }, 300)
}

function decreaseFontSize() {
    if (currentFontSize > minFontSize) {
        currentFontSize -= 2;
    }
    if (currentFontSize === minFontSize) {
        decreaseFontSizeBtn.disabled = true;
    } else {
        increaseFontSizeBtn.disabled = false;
    }
}

function increaseFontSize() {
    if (currentFontSize < maxFontSize) {
        currentFontSize += 2;
    }
    if (currentFontSize === maxFontSize) {
        increaseFontSizeBtn.disabled = true;
    } else {
        decreaseFontSizeBtn.disabled = false;
    }
}

function saveFontSize() {
    fontSizeDisplay.textContent = currentFontSize;
    // localStorage ...
}
.fontsize-via-btn {
    width: 100px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    font-size: 2rem;
}
<div class="fontsize-via-btn">
    <button id="decrease">A-</button>
    <div class="current-fontsize">20</div>
    <button id="increase">A+</button>
</div>

Upvotes: 0

Views: 120

Answers (3)

zer00ne
zer00ne

Reputation: 43990

Discrepancies

"mouseup" and "mouseleave" needs to be registered on each button or the events must be delegated to the buttons. Right now the mouse events only affect the <div> which doesn't have any functions firing off on it. So that's why the buttons get stuck -- they are consantly repeating themselves even after the user releases the mousebutton.

The example below has separate event handlers for each button. Normally I prefer to use one event handler to control everything through event delegation, but because of the need to use set\clearInterval() as event handlers, the event object gets lost. I could solve that problem with a closure, but your code was close enough that a radical change wouldn't be to your benifet.

Saving with localStorage (I know it's not part of the question) is omitted from this version since this site blocks it's use. I wrote another version that saves the current font-size and loads it as well, to review that one go to this Plunker.

const io = document.forms.UI.elements;

let current = 1.5;

let repeatInc = null;
let repeatDec = null;

function holdInc(e) {
  if (repeatInc === null) {
    repeatInc = setInterval(incFontSize, 100);
  }
};

function releaseInc(e) {
  if (repeatInc != null) {
    clearInterval(repeatInc);
    repeatInc = null;
  }
};

function incFontSize() {
  const io = document.forms.UI.elements;
  const max = 4;

  if (current < max) {
    current += 0.25;
    io.dec.disabled = false;
  }
  if (current == max) {
    io.inc.disabled = true;
    releaseInc();
  }

  io.fSize.value = current.toFixed(2);
  io.text.style.fontSize = `${current}rem`
};

function holdDec(e) {
  if (repeatDec == null) {
    repeatDec = setInterval(decFontSize, 100);
  }
};

function releaseDec(e) {
  if (repeatDec != null) {
    clearInterval(repeatDec);
    repeatDec = null;
  }
};

function decFontSize() {
  const io = document.forms.UI.elements;
  const min = 1;

  if (current > min) {
    current -= 0.25;
    io.inc.disabled = false;
  }
  if (current == min) {
    io.dec.disabled = true;
    releaseDec()
  }

  io.fSize.value = current.toFixed(2);
  io.text.style.fontSize = `${current}rem`
};

io.inc.addEventListener('mousedown', holdInc);
io.inc.addEventListener('mouseup', releaseInc);
io.inc.addEventListener('mousemove', releaseInc);

io.dec.addEventListener('mousedown', holdDec);
io.dec.addEventListener('mouseup', releaseDec);
io.dec.addEventListener('mouseleave', releaseDec);
:root {
  font: 2ch/1 'Segoe UI'
}

body {
  font-size: 1ch;
}

.ctrl {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  font-size: 1rem;
  padding: 5px;
}

#fSize {
  display: block;
  width: 5ch;
  margin: 0 4px;
  font-family: Consolas;
  text-align: center;
}

button {
  display: block;
  width: max-content;
  font: inherit;
  cursor: pointer;
}

label {
  display: block;
  width: max-content;
  margin: 0 8px;
  font-size: 1rem;
}

.text {
  min-height: 50px;
  font-size: 1.25rem;
}
<form id='UI'>
  <fieldset name='ctrl' class='ctrl'>
    <button id="dec" type='button'>&#128475;-</button>
    <output id="fSize">1.50</output>
    <button id="inc" type='button'>&#128474;+</button>
    <label>Scale: 1ch => 1rem</label>
  </fieldset>
  <fieldset name='text' class='text' contenteditable>
    This test area has editable content
  </fieldset>
</form>

Upvotes: 0

derpirscher
derpirscher

Reputation: 17400

Your timers are not cleared when you hit the minimum or maximum because (as @timmmmmb already said) disabled buttons don't trigger mouse events. Thus, when you try to go in the other direction, the original timer is again executed.

The simplest would probably be, calling clearTimers also when you hit the end of the range

const decreaseFontSizeBtn = document.querySelector(".fontsize-via-btn #decrease");
const increaseFontSizeBtn = document.querySelector(".fontsize-via-btn #increase");
const fontSizeDisplay = document.querySelector(".fontsize-via-btn .current-fontsize");

const defaultFontSize = 20;
const minFontSize = 16;
const maxFontSize = 40;
let currentFontSize = defaultFontSize;

var timeout, interval;

function clearTimers() {
    clearTimeout(timeout);
    clearInterval(interval);
}

[decreaseFontSizeBtn, increaseFontSizeBtn].forEach(btn => {
    btn.addEventListener("mousedown", () => {
        if (btn.id === "decrease") {
            decreaseFontSize();
            hold(decreaseFontSize);
        }

        if (btn.id === "increase") {
            increaseFontSize();
            hold(increaseFontSize);
        }

        saveFontSize();
    })

    btn.addEventListener("mouseup", clearTimers);
    btn.addEventListener("mouseleave", clearTimers);

})

function hold(func) {
    timeout = setTimeout(() => {
        interval = setInterval(() => {
            func();
            saveFontSize();
        }, 50)
    }, 300)
}

function decreaseFontSize() {
    if (currentFontSize > minFontSize) {
        currentFontSize -= 2;
    }
    if (currentFontSize === minFontSize) {
        decreaseFontSizeBtn.disabled = true;
        clearTimers();
    } else {
        increaseFontSizeBtn.disabled = false;
    }
}

function increaseFontSize() {
    if (currentFontSize < maxFontSize) {
        currentFontSize += 2;
    }
    if (currentFontSize === maxFontSize) {
        clearTimers();
        increaseFontSizeBtn.disabled = true;
    } else {
        decreaseFontSizeBtn.disabled = false;
    }
}

function saveFontSize() {
    fontSizeDisplay.textContent = currentFontSize;
    // localStorage ...
}
.fontsize-via-btn {
    width: 100px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    font-size: 2rem;
}
<div class="fontsize-via-btn">
    <button id="decrease">A-</button>
    <div class="current-fontsize">20</div>
    <button id="increase">A+</button>
</div>

Upvotes: 2

timmmmmb
timmmmmb

Reputation: 786

I think the problem is, that when you reach the max Fontsize you disable the button and that means your mouseup event will not be triggerd. I moved the mouseup event away from the buttons and on the container and i think it works now.

const decreaseFontSizeBtn = document.querySelector(".fontsize-via-btn #decrease");
const increaseFontSizeBtn = document.querySelector(".fontsize-via-btn #increase");
const fontSizeDisplay = document.querySelector(".fontsize-via-btn .current-fontsize");
const fontSizeContainer = document.querySelector(".fontsize-via-btn");

const defaultFontSize = 20;
const minFontSize = 16;
const maxFontSize = 40;
let currentFontSize = defaultFontSize;

var timeout, interval;



fontSizeContainer.addEventListener("mouseup", clearTimers);
fontSizeContainer.addEventListener("mouseleave", clearTimers);

function clearTimers() {
    clearTimeout(timeout);
    clearInterval(interval);
}

[decreaseFontSizeBtn, increaseFontSizeBtn].forEach(btn => {
    btn.addEventListener("mousedown", () => {
        if (btn.id === "decrease") {
            decreaseFontSize();
            hold(decreaseFontSize);
        }

        if (btn.id === "increase") {
            increaseFontSize();
            hold(increaseFontSize);
        }

        saveFontSize();
    })
})

function hold(func) {
    timeout = setTimeout(() => {
        interval = setInterval(() => {
            func();
            saveFontSize();
        }, 50)
    }, 300)
}

function decreaseFontSize() {
    if (currentFontSize > minFontSize) {
        currentFontSize -= 2;
    }
    if (currentFontSize === minFontSize) {
        decreaseFontSizeBtn.disabled = true;
    } else {
        increaseFontSizeBtn.disabled = false;
    }
}

function increaseFontSize() {
    if (currentFontSize < maxFontSize) {
        currentFontSize += 2;
    }
    if (currentFontSize === maxFontSize) {
        increaseFontSizeBtn.disabled = true;
    } else {
        decreaseFontSizeBtn.disabled = false;
    }
}

function saveFontSize() {
    fontSizeDisplay.textContent = currentFontSize;
    // localStorage ...
}
.fontsize-via-btn {
    width: 100px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    font-size: 2rem;
}
<div class="fontsize-via-btn">
    <button id="decrease">A-</button>
    <div class="current-fontsize">20</div>
    <button id="increase">A+</button>
</div>

Upvotes: 0

Related Questions