Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript element.scrollleft can't handle decimals

Apparently nor Element.scrollLeft nor Element.scroll handle scrolling with decimals properly. Is there a work around it ?

const scrRight= () => {
        const parent = document.querySelector('.parent');
        const newLocal = parent.offsetWidth / 6;
            parent.scrollLeft += newLocal;

        console.log('parent.scrollright', parent.offsetWidth , parent.scrollLeft,newLocal)
    };



const scrleft = () => {
        const parent = document.querySelector('.parent');
        
        const newLocal_1 = parent.offsetWidth / 6;            
            parent.scrollLeft -= newLocal_1;

        console.log('parent.scrollLeft', parent.offsetWidth , parent.scrollLeft, newLocal_1)
    };
.parent{
  box-sizing: border-box;
  display: flex;
  overflow-x: hidden;
  width: 500px;
}
.parent > div {
  box-sizing: border-box;
  border: 1px solid black;
  padding: 5px;
  flex: 0 0 calc((1/6) * 100%);
}
<div class="parent">
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div> 
  <div>child </div> 
  <div>child </div> 
  <div>child </div> 
  <div>child </div> 
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div>
  <div>child </div> 
  <div>child </div> 
  <div>child </div> 
  <div>child </div> 
  <div>child </div> 
  <div>child </div>  
</div>
<button type="button"  onclick="scrleft()">&#706;</button>
<button type="button"  onclick="scrRight()">&#707;</button>

As you can see the scroll should update every-time with += 500/6 = 83.3333 but instead it updates with += 82.7272720336914.

Edit:

Yes the decimal value is very important. Just run the example above. As you can see the cells start to be misplaced. As you reach the end of the row the cells become misplaced!

like image 520
Melchia Avatar asked Dec 05 '25 18:12

Melchia


1 Answers

This is my guess at an answer.

When using mathematical operations to calculate position, sometimes there is a fractional component in the result. When the browser renders the border of a box it will draw that border on a pixel line at an integer position on the screen: half pixels do not exist in this context AFAIK. It must therefore use an algorithm to consistently choose which pixel to draw the border on, even if it is slightly inexact.

This means that if you move the viewport successively, applying a fractional amount each time, and neglecting to take into account the positioning of boxes according to the browser's rendering algorithm, eventually your viewport position will fall out of sync with boxes rendered within it: the viewport will then not align exactly with the box you want to scroll into view.

One solution might be to ask the browser where it has rendered the box you want to position in the viewport - this gives you the gross amount to scroll - then apply the same algorithm the browser does to this value if it contains a fractional component (I guessed at Math.ceil below, which works reasonably well); and scroll by this amount instead.

The following seems to work pretty well in Chromium. In FF and Safari it isn't perfect, but it's close.

const parent = document.querySelector(".parent")
let leftMost = 2

const moveRight = () => {
  //grab the box you want to be left-most
  const leftMostEl = parent.querySelector(`:nth-child(${++leftMost})`)
  // ask where it is relative to the viewport
  const { left: toMove } = leftMostEl.getBoundingClientRect()
  // apply a rounding algorithm to the value, and apply the move
  parent.scrollLeft += Math.ceil(toMove)
}

document.querySelector('button').addEventListener('click', moveRight)
* {
  color: gray;
  font-size: 0;
  box-sizing: border-box;
  text-align: center;
  padding: 0;
  margin: 0;
  border: none;
}
.parent {
  display: flex;
  overflow-x: scroll;
  width: 100%;
}
.parent > div {
  font-size: 26px;
  line-height: 50px;
  box-shadow: 0 0 0 1px gray inset;
  flex: 0 0 calc(100% / 6);
}
button {
  font-size: 16px;
  line-height: 50px;
  box-shadow: 0 0 0 1px gray inset;
  width: 160px;
  border-radius: 10px;
  margin: 25px;
}
<div class="parent">
  <script>
    [...Array(20)].forEach((_,i) => document.write(`<div>${String(++i).padStart(2, '0')}</div>`))
  </script>
</div>

<button>Move right</button>
like image 179
Ben Aston Avatar answered Dec 08 '25 07:12

Ben Aston



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!