Change CSS of selected text using Javascript
Asked Answered
T

7

12

I'm trying to make a JavaScript bookmarklet that will act as a highlighter, changing the background of selected text on a webpage to yellow when the bookmarklet is pressed.

I'm using the following code to get the selected text, and it works fine, returning the correct string

function getSelText() {
    var SelText = '';
    if (window.getSelection) {
        SelText = window.getSelection();
    } else if (document.getSelection) {
        SelText = document.getSelection();
    } else if (document.selection) {
        SelText = document.selection.createRange().text;
    }
    return SelText;
}

However, when I created a similar function to change the CSS of the selected text using jQuery, it isn't working:

function highlightSelText() {
    var SelText;
    if (window.getSelection) {
        SelText = window.getSelection();
    } else if (document.getSelection) {
        SelText = document.getSelection();
    } else if (document.selection) {
        SelText = document.selection.createRange().text;
    }
    $(SelText).css({'background-color' : 'yellow', 'font-weight' : 'bolder'});
}

Any ideas?

Transubstantiate answered 11/7, 2010 at 16:2 Comment(1)
Possible duplicate: #2584801, #2583331 and #1623129Eisegesis
E
19

The easiest way to do this is to use execCommand(), which has a command to change the background colour in all modern browsers.

The following should do what you want on any selection, including ones spanning multiple elements. In non-IE browsers it turns on designMode, applies a background colour and then switches designMode off again.

UPDATE

Fixed in IE 9.

function makeEditableAndHighlight(colour) {
    var range, sel = window.getSelection();
    if (sel.rangeCount && sel.getRangeAt) {
        range = sel.getRangeAt(0);
    }
    document.designMode = "on";
    if (range) {
        sel.removeAllRanges();
        sel.addRange(range);
    }
    // Use HiliteColor since some browsers apply BackColor to the whole block
    if (!document.execCommand("HiliteColor", false, colour)) {
        document.execCommand("BackColor", false, colour);
    }
    document.designMode = "off";
}

function highlight(colour) {
    var range, sel;
    if (window.getSelection) {
        // IE9 and non-IE
        try {
            if (!document.execCommand("BackColor", false, colour)) {
                makeEditableAndHighlight(colour);
            }
        } catch (ex) {
            makeEditableAndHighlight(colour)
        }
    } else if (document.selection && document.selection.createRange) {
        // IE <= 8 case
        range = document.selection.createRange();
        range.execCommand("BackColor", false, colour);
    }
}
Eisegesis answered 11/7, 2010 at 20:25 Comment(0)
A
9

Here is a crude example of how it could work. As Zack points out you'll need to be aware of cases where the selection spans multiple elements. This isn't intended to be used as-is, just something to help get ideas flowing. Tested in Chrome.

var selection = window.getSelection();
var text = selection.toString();
var parent = $(selection.focusNode.parentElement);
var oldHtml = parent.html();
var newHtml = oldHtml.replace(text, "<span class='highlight'>"+text+"</span>");
parent.html( newHtml );
Astrogeology answered 11/7, 2010 at 16:54 Comment(2)
That looks good thanks. In that example, what would the parent element usually be? What if the parent element contained several instances of the string?Transubstantiate
In my case the parent element was the containing <p> tag. You make a good point about repeated instances of the string, especially if they highlight a common word. I'm not sure how to get around that just yet but I'll post if I think of anything.Astrogeology
K
2

To make the highlight stick permanently, I believe you are going to have to wrap the selection in a new DOM element (span should do), to which you can then attach style properties. I don't know if jQuery can do that for you. Keep in mind that selections can span element boundaries, so in the general case you're going to have to inject a whole bunch of new elements

Kovar answered 11/7, 2010 at 16:46 Comment(0)
U
2

Have a look at a little example i made at http://www.jsfiddle.net/hbwEE/3/

It does not take into account selections that span multiple elements.. (IE will do but will mess the html a bit ..)

Upstroke answered 11/7, 2010 at 17:23 Comment(0)
C
1

In Firefox, you can use the ::-moz-selection psuedo-class.
In Webkit, you can use the ::selection pseudo-class.

Chorale answered 11/7, 2010 at 16:3 Comment(1)
thanks, but I'm wanting to permanently change the CSS, so it stays highlighted even when I deselect it. Also needs to work cross-browserTransubstantiate
H
0

I like Tim's answer, it's clean and fast. But it also shuts down the doors to doing any interactions with the highlights.

Inserting inline elements directly around the texts is a bad choice, as they broke the text flow and mess things up in complex situations,

So I suggest a dirty hack that

  1. calculates the absolute layout of each line of selected text (no matter where they are),
  2. then insert colored, semi-transparent inline-block elements in the end of the document body.

This chrome extension is an example of how this can be done.

It uses API from this library to get the absolute layout of each selected line.

Hamitosemitic answered 23/4, 2017 at 23:12 Comment(0)
J
0

The CSS Highlight API, https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API, is designed to do what you want. In JS you can mark ranges as having a named highlight style, and in CSS you define what that highlight is. The browser should do something that looks like selection, but is persistent.

For example:

<style>
  div::highlight(foo) {
    background-color: green;
  }
</style>
<div id="highlighted">This is highlighted text</div>
<script>
  let r = new Range();
  r.setStart(highlighted, 0);
  r.setEnd(highlighted, 1);
  let h = new Highlight(r);
  CSS.highlights.set('foo', h);
</script>
Justis answered 30/1 at 1:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.