Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

range.setStart using only character offset?

I'm quite new to Javascript, and I've been reading the documentation for the last couple of days trying to figure this out. I've finally had to resort to parading my ignorance here.

I have an integer, which is the index of a character within a paragraph. I want to find the bounding rect of this character. I have been trying to do this by making a range that contains the character.

So I have tried:

var range = document.createRange();
range.setStart (node, offsetInsideNode);

and for node I tried passing in the paragraph element, and for the offsetInsideNode I passed in the characterOffset integer.

But then I found: "If the node element can have child nodes, then the offsetInsideNode parameter specifies the position of a child node in the childNodes collection of the node element, else it specifies a character position in the text content of the node element."

But I want to ONLY use the character position. And I can't figure out how to do this, as it only seems to want to use child node position. I'm guessing I'm missing something.

Say I have a paragraph:

<p xmlns="http://www.w3.org/1999/xhtml" class="s12" style="color: rgb(0, 0, 0);"><span class="mySpanClass">The</span> quick brown <b>fox</b> jumps over the lazy dog</p>

And I want to find the bounding rect of the nth character, how would I go about this? Am I barking up the wrong tree and there is a much simpler way that I have overlooked?

Thanks.

notes:

  • javascript only
  • no libraries
  • no jquery
  • for UIWebview on an iOS device
like image 901
narco Avatar asked Sep 29 '22 07:09

narco


2 Answers

I hope this is what you're looking for. This function basically takes the content of the element you want to find nth in, splits it into characters, finds nth without counting the HTML tags, wraps nth in a temporary span and reads the offsetTop and offsetLeft before replacing it with the original content. The offset x and y is then returned as an object.

function nthCharOffset(nth, element){
    var orgContent = element.innerHTML;   // Save the original content.
    var arr = orgContent.split('');         // Split every character.

    // Few vars to control the upcoming loop
    var content = '';
    var tag = false;
    var count = 0;

    // Loop through every character creating a new string and wrapping the nth in a temporary span
    for (var i = 0; i < arr.length; i++) {

        // if inside tag, don't count this in the nth count
        if (arr[i] == '<') tag = true
        if (!tag) count++;
        if (arr[i] == '>') tag = false;

        // If this charactar is nth, wrap it in a temporary span
        if (nth == count) content += '<span id="offset-check">' + arr[i] + '</span>';
        else content += arr[i];
    }

    // Set the content with the temporary span.
    element.innerHTML = content;

    // Get the offset of the temporary span.
    var offsetCheck = document.getElementById('offset-check');
    var offset = {x: offsetCheck.offsetLeft , y: offsetCheck.offsetTop }

    // Remove the span.
    element.innerHTML = orgContent;

    // Return the result.
    return offset;
}

Use it like this:

nthCharOffset(10, document.getElementById('element'));

I've made a fiddle so you can test it here.

This fiddle uses the function to position and scale a red rectangle to nth character.

like image 183
ostrgard Avatar answered Oct 03 '22 00:10

ostrgard


As far as I understand you want

ONLY the character position

in order to specify a range for a selection, rather than some node-offset. I believe I just had the same problem. The offset can be created by addressing nodes or characters. The Range.setStart method does two things at once, which is a pain, but the documentation gives away what you have to do:

If the startNode is a Node of type Text, Comment, or CDATASection, then startOffset is the number of characters from the start of startNode.

See: https://developer.mozilla.org/en-US/docs/Web/API/Range/setStart

Therefore you can specify a character-indexed range by addressing the text node of an element. Here is an (optimistic) solution I came up with.

Assume you have a document and you want to add sticky CSS-styled, yellow hightlights to a piece of selected text. In Javascript, create a range and select the element. Add range but select the textnode first. The offset will then refer to the characters, rather than the nodes.

// Magic happens here
function getTextNodeFrom(element) {

  // replace with more sophisticated method
  return element.childNodes[0];

}

// Create the range as you normally would
function createRange(node, start, end) {
  var range = document.createRange();
  range.setStart(node, start);
  range.setEnd(node, end);
  return range;
}

// Give it a nice name 
function createRangeByCharacterOffset(element, start, end) {

  // Rather than passing the element directly to createRange,  
  // extract the node first.
  var textNode = getTextNodeFrom(element)
  var range = createRange(textNode, start, end);

  return range;
}

// To illustrate, let's surround the range with a highlighting span
function highlight () {
    var span = document.createElement('span');
    span.setAttribute('class', 'highlight');
    return span;
}

var p = document.getElementsByTagName('p')[0];

createRangeByCharacterOffset(p, 50, 200)
.surroundContents(highlight()); // This performs the actual highlight. 

First, the given p element is selected. Then, we extract the textNode using the function getTextNodeFrom(element). Note that this function is really optimistic. It assumes that the list of nodes contains a textNode as the first element which doesn't have to be the case. How you obtain this node is up to you and here only a simple approach is shown. I would assume iterating over the list and checking the nodes' types for Text or CDATA would suffice.

The range is created and the start and end are set. When the 'node' object is a textNode, the offset addresses the characters. Take a look at this fiddle to see it in action.

As an example, the shown code takes the selected range and surrounds it with a styled span element to highlight the range from character 50 to 200.

https://jsfiddle.net/cessor/t6sroob1/2/

like image 39
cessor Avatar answered Oct 03 '22 01:10

cessor