2

Is it possible to show a virtual caret between two letter boundaries using HTML/CSS/Javascript for say a regular div without contenteditable=true?

Let's say I have this:

<div>Hello world</div>

And I click between the "w" and "o" in "world", is it possible to show a virtual caret so it would look like:

Hello w][orld

Andrew dh
  • 881
  • 9
  • 19
  • Not using HTML+CSS+JS, sorry. Displaying the caret in the page is a browser setting controlled by the user and cannot be modified by web-pages (assuming the browser supports this feature at all). – Dai May 10 '14 at 08:35
  • Thanks. I don't need the browser caret per se, just getting the index position in the string where the div was clicked would be enough and I could overlay a "virtual" caret using a div and abs positioning – Andrew dh May 10 '14 at 08:37

3 Answers3

3

HTML:

<div id="div">some text</div>

Javascript:

var str=document.getElementById("div").innerHTML;
document.getElementById("div").onclick=function () {
    var index = window.getSelection().focusOffset;
    document.getElementById("div").innerHTML=str.substr(0,index) + "][" + str.substr(index);
}
nicael
  • 18,550
  • 13
  • 57
  • 90
  • 1
    note that the `focusOffset` (as well as the SelectionRange) is supported from IE 9. Anyways this is a so simple solution +1 for that, did not know about `focusOffset` before. – King King May 10 '14 at 14:19
  • I ended up going with a variation of this - using the focusOffset but overlaying the cursor instead gave a pretty clean solution - http://jsfiddle.net/fkpv8/ – Andrew dh May 12 '14 at 05:05
2

At least you have 2 solutions to get a charater (and related position) in some element's text under the mouse cursor. The first is to wrap each character in a <span> or some other element, attaching some click event handler for the <span> element and you've done. The second is to use the methods related to the Range object (which is of course supported only by pure JS). I would like to use the first approach because it's much faster. Using Range approach requires looping each time you click, clicking at the end of a large text may cause some bad performance. For the virtual caret, you can create some inline-block element, render the caret on it (such as by using border in combination with linear-gradient background, ...) even a transparent png image can help. Because it requires a space to render the caret, you can use negative margin (for left and right) to draw text at both sides close together. Here is the code detail:

HTML:

<div class='inter-letters'>Click on one of the characters ...</div>

CSS:

.v-caret {
  width:.5em;
  height:1em;
  border-top:1px solid currentColor;
  border-bottom:1px solid currentColor;
  background:linear-gradient(to right, transparent .23em, currentColor .25em, transparent .27em);
  display:inline-block;    
  vertical-align:middle;    
  margin:0 -.2em;
  display:none;
}
.inter-letters {
  font-size:30px;
  color:green;
}    

JS:

$('.inter-letters').on('click','span.letter', function(){
  virtualCaret.css('display','inline-block');
  $(this).after(virtualCaret);
}).html(function(i,oldhtml){
          return oldhtml.replace(/./g,"<span class='letter'>$&</span>");
        });
var virtualCaret = $('<div>').addClass('v-caret').appendTo('.inter-letters');
//clicking outside the div should hide the virtual caret
$(document).click(function(e){    
  if(!$('.inter-letters').has(e.target).length) {        
     virtualCaret.css('display','none');
  }    
});

Demo.

King King
  • 61,710
  • 16
  • 105
  • 130
2

If you have a monospace script, you could make a line at an absolute point.

Say all characters are 7px width

$('#div').click(function (e) { //Offset mouse Position
    var posX = $(this).offset().left, // get offset of click
    var caretX = Math.floor(posX/7); // take all whole character
    caretX += posX%7 > 7/2 ? 1 : 0; // if not exactly clicked between two chars, decide which way to shift

    // You can now user caretX as X position for a caretline
    // element to be placed absolute
});
Community
  • 1
  • 1
Martijn
  • 15,791
  • 4
  • 36
  • 68