javascripthtmlfontsmonospace

Why doesn't the custom caret get consistently positioned corectly in this case?


I am using the distance of the the span from the top to position the caret vertically, that works fine, and I am using the index of the string multiplied by the letter width to position the caret horizontally but that sometimes works, sometimes it doesn't.

Look for example at the second row, and click in the first "this", it doesn't work properly but if you click inside the last "this" the caret gets positioned correctly.

I can't figure out why, doesn't it mean that a mono-space font has the exact same with for each letter which I eyeballed here to be 10.1? Something doesn't work here and I can't figure out what. JsFiddle

let writeDiv = document.querySelector('#write');
let caret = document.querySelector('#caret')

writeDiv.addEventListener('click', (e) => {
    if(e.target.tagName == 'SPAN'){
        moveCaretOnClick(e)
    }

})

function moveCaretOnClick(e) {
    let y = window.getSelection().focusOffset * 10.1
    caret.style = `left: ${y}px; top: ${e.target.offsetTop + 3}px`; 
}
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap');
body, html{
    margin: 0;
  height: 100%;
  width: 100%;
}
#write {
  font-family: 'Roboto Mono';
    height: 100%;
    width: 100%;
    background: #1f2227;
    padding: 10px;
    color: white;
  box-sizing: border-box;
}
#caret{
    height: 15px;
    width: 3px;
    background: #80ff00;
    display: inline-block;
    position: absolute;
}



.RowSpan{
    width: 100%;
    display: inline-block;
    box-sizing: border-box;
    height: 20px;
}
<div id='write'>
    <span class='RowSpan'>This is some text</span>
    <span class='RowSpan'>this is some text this</span>
</div>
<div id='caret'></div>

Edit: My guess is that I need the exact dimension of the letters, otherwise, as you go further the line the distance increases more and more, because of multiplying for each letter. If you add more text the distance gets more and more further from the click.

So, somehow either get the exact dimension of the letters that's always consistent or find some other way.


Solution

  • A bit awkward but I fount the solution, it turns out I just had to add a 1 to the focusOffset before multiplying with each letter, otherwise it would basically skip a letter. I think because focusOffset starts with a 0

    let writeDiv = document.querySelector('#write');
    let caret = document.querySelector('#caret')
    
    
    
    writeDiv.addEventListener('click', (e) => {
        if(e.target.tagName == 'SPAN'){
            moveCaretOnClick(e)
        }
    
    })
    
    function moveCaretOnClick(e) {
        let y = (window.getSelection().focusOffset + 1) * 9.6
    
        caret.style = `left: ${y}px; top: ${e.target.offsetTop + 3}px`; 
    }
    @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap');
    body{
        margin: 0;
    }
    
    #write {
      font-family: 'Roboto Mono';
        height: 300px;
        width: 1200px;
        background: #1f2227;
        padding: 10px;
        color: white;
    }
    
    #caret{
        height: 15px;
        width: 2px;
        background: #80ff00;
        display: inline-block;
        position: absolute;
    }
    
    
    
    .RowSpan{
        width: 100%;
        display: inline-block;
        box-sizing: border-box;
        height: 20px;
    }
    <div id='write'>
        <span class='RowSpan'>This is some text</span>
        <span class='RowSpan'>this is some text this more more text BIGGERT TEXT and some small text and some stuff -- __ !%^ () {} and some more text even </span>
    </div>
    <div id='caret'></div>