Range.setStart using only character offset?
I am completely new to Javascript and I have been reading the documentation for the last couple of days trying to figure this out. I finally had to resort to portraying my ignorance here.
I have an integer that is the index of a character in a paragraph. I want to find the bounding rectangle of this character. I am trying to do this by creating a range that contains a character.
So, I tried:
var range = document.createRange();
range.setStart (node, offsetInsideNode);
and for node I tried to pass a paragraph element and for offsetInsideNode I passed an integer characterOffset.
But then I discovered, "If a node element can have child nodes, the offsetInsideNode parameter specifies the position of the child node in the childNodes collection of the node element, otherwise it sets the 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 the child position of the node. I guess I am missing something.
Let's 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 rectangle of the nth character, how would I do that? I am barking the wrong tree, and is there a much easier way I have left out?
Thank.
notes:
- javascript only
- no libraries
- no jquery
- for UIWebview on iOS device
source to share
Hope this is what you are looking for. This function basically takes the content of the element you want to find nth in, breaks it down into characters, finds it nth
without regard to HTML tags, wraps it nth
in a time slot and reads offsetTop
and offsetLeft
replaces it with the original content. Then the x and y offset is 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 made a fiddle so you can test it here .
This script uses this function to position and scale the red rectangle to fit the symbol nth
.
source to share
As I understand it you want
ONLY symbol position
to specify a range to select, not some node-offset. I believe I had the same problem. An offset can be created by addressing nodes or characters. The method Range.setStart
does two things at the same time, it's a pain, but the documentation gives you what you need to do:
If startNode is a Node of type Text, Comment, or CDATASection, then startOffset is the number of characters from the beginning of startNode.
See: https://developer.mozilla.org/en-US/docs/Web/API/Range/setStart
Therefore, you can specify a range with a character index by referring to the element's Node text. Here is the (optimistic) solution that I came up with.
Let's say you have a document and want to add sticky CSS styled yellow lights to a piece of selected text. In Javascript create a range and select an element. Add a range, but select the text index first. The offset then refers to characters, not 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 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.
This item is selected first p
. Then we retrieve the textNode using a function getTextNodeFrom(element)
. Please note that this feature is really optimistic. It assumes that the list of nodes contains a textNode as the first element, which does not have to occur. How you get this Node is up to you, and only a simple approach is shown here. I would suggest that iterating over the list and checking node types for text or CDATA would be sufficient.
The range is created and the start and end are set. When node 'object is text, offset refers to characters. Take a look at this violin to see it in action.
As an example, the code shown takes a selected range and surrounds it with a styled span element to highlight the range from character 50
to 200
.
source to share