4

I'm developing a book reader for iOS using a UIWebView. At the moment I'm working with some basic HTML files but will eventually work with ePubs. I am looking for a suitable way of styling ranges of text. My ranges are a little bit special in that they usually encompass three ranges - a keyrange and a range immediately before and range immediately after. The keyrange may span multiple nodes and may start or end for example within a selection of bold text etc. The styling should not be written to file.

At the moment I have a working solution as follows:

document.designMode = "on";

// Color the first section
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range1);

if (!selection.isCollapsed){ 
document.execCommand("foreColor", false, color1);    
}

// Color the middle section
selection.removeAllRanges();
selection.addRange(range2);

if (!selection.isCollapsed){   
document.execCommand("backColor", false, color2);
document.execCommand("foreColor", false, color3);
}

// Color the last section
selection.removeAllRanges();
selection.addRange(range3);

if (!selection.isCollapsed){ 
document.execCommand("foreColor", false, color1);
}

document.designMode = "off";
selection.removeAllRanges();

This works fine but is noticeably slow (on an iPad2), even if I modify it to highlight a single range in a short HTML document. There is always a very noticeable delay before the text is stylised. Looking at ebook readers on iPad such as kindle or iBooks there is no discernible delay. How might they implement their highlighting function? Might they be reading the geographical location on the selected text and applying some kind of overlay?

I have searched for a better solution than I'm already using but with no luck so if anyone has an idea I would be very grateful.

Thank you!

Simple99
  • 1,038
  • 11
  • 20

2 Answers2

9

I have made an alternative method to above which appears to be much faster. It is mainly based on mikeB's excellent code given in this question.

How to get nodes lying inside a range with javascript?

  1. I first split the start and end nodes creating new text nodes with only the text which falls with the given range. I adjust the range for the new nodes.

  2. I use a slightly modified version of the code at the above link to return an array of text nodes contained within the range.

  3. I loop through the array and put spans with styles around each text node.

As far as I've tested the code works well. When applying styles to a number of ranges simultaneously there is still a delay but its much better than before. I'd be interested to know if anyone has any improvements.

My code:

function styleRange(range, style) // the style is a string of css styles. eg."background-color:darkblue; color:blue;"
{    
    // Get the start and end nodes and split them to give new start and end nodes with only text that falls inside the range.
    var startNode = range.startContainer.splitText(range.startOffset);
    var endNode = range.endContainer.splitText(range.endOffset).previousSibling;

    // Adjust the range to contain the new start and end nodes
    // The offsets are not really important anymore but might as well set them correctly
    range.setStart(startNode,0);
    range.setEnd(endNode,endNode.length);

    // Get an array of all text nodes within the range
    var nodes = getNodesInRange(range);

    // Place span tags with style around each textnode
    for (i = 0; i < nodes.length; i++)
    {
        var span = document.createElement('span');
        span.setAttribute("style", style);
        span.appendChild( document.createTextNode(nodes[i].nodeValue));
        nodes[i].parentNode.replaceChild( span, nodes[i] );
    }
}

Modified code from mikeB's code:

function getNodesInRange(range)
{
    var start = range.startContainer;
    var end = range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode)
    {
        if (node.nodeType == 3) //modified to only add text nodes to the array
            nodes.push(node);
        if (node == commonAncestor)
            break;
    }
    nodes.reverse();

    // walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node))
    {
        if (node.nodeType == 3) //modified to only add text nodes to the array
            nodes.push(node);
        if (node == end)
            break;
    }

    return nodes;
}


function getNextNode(node, end)
{
    if (node.firstChild)
        return node.firstChild;
    while (node)
    {
        if (node.nextSibling)
            return node.nextSibling;
        node = node.parentNode;
    }
}
Community
  • 1
  • 1
Simple99
  • 1,038
  • 11
  • 20
  • Here I am creating the epub reader app for ipad app,Here i just unzip and load epub in the uiwebview and i tried to highlight and add note in uiwebview using uimenuviewcontroller, i delected the text and getting the text either using execommand or window but my doubt is how can i store and reterive the note and highlighting the text in webview could you please help me – dineshprasanna Mar 15 '13 at 12:19
  • Is there a way to toggle? So when the user styles a range "bold" the second time it actually removes the bold styling? – Apollo May 02 '15 at 18:42
0

Apollo in order to succeed that, you have to set an onclick listener when you create the span, by passing a callback function like

span.addEventListener("click",callbackwhenclickeventtrigger,false);
function callbackwhenclickeventtrigger(e){//pass as param the event target, inside this function create the un-bold function
}
iParianos
  • 51
  • 5