11

Is it possible to highlight text in an HTML document using without wrapping it with <span> or any other tag for that matter?

For example, in the HTML code <p>The quick fox</p> I would like to highlight quick but without adding a DOM element around it. Adding a DOM element to a parent element is fine.

Thanks!

1
  • 1
    I would like to highlight (using bounding boxes) MULTIPLE search items. However, imagine the following 2 search items: "The quick" and "quick fox". If I highlight "The quick" by wrapping it, it will become a separate TEXT node and it would make it difficult to find "quick fox". One technique that I'm thinking of is: 1. Temporarily adding a <span /> element. 2. Finding the bounding box of the element (using offset(), width, height). 3. Deleting the above element. 4. Adding a box with the above dimensions to the container. Might work if the text is not wrapped around... Commented Nov 29, 2009 at 19:09

5 Answers 5

10

No, it is not possible in a portable manner across all browsers.

You can't tell the browser to render a piece of text differently without inherently changing the DOM, regardless of whether you do it statically or dynamically (with Javascript, for example, as a post processing step).

Edit: In Chrome starting around 2019, you can use Scroll To Text Fragment, but this is not an HTML or browser standard feature.

Sign up to request clarification or add additional context in comments.

9 Comments

You don't need to change the DOM if your changing pixels in a transparent canvas overlay.
How would you get the absolute position of a piece of text without changing the DOM?
Add the <span> element to the DOM, find out its position, then delete it and normalize().
What if they resize the browser window?
Naturally you will need to recompute on resizes.
|
7

CSS Custom Highlight API

Browser vendors are working towards explicit support for the highlighting pattern - with an incredibly versatile new "Custom Highlight" API.

This has been available in Chrome derivatives since version 105 (2022-09-02), and is currently supported in Firefox Nightly.

https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API

Note my answer here, where I describe this in longer form:

https://stackoverflow.com/a/78051161/16376459

There are now multiple ways of achieving this effect without modifying the element structure of the DOM.

They all, however, come with their own caveats.

This answer is derived from research I performed for my open source browser extension Mark My Search (repository), which highlights text you search for on webpages. The natural destructiveness of the accepted approach led to my consideration of many alternatives. The extension will only use one the below approaches if the advanced option "Use CLASSIC highlighting" is unchecked, which is not default due to the disadvantages I am about to describe.

All of my approaches below only involve adding additional painting to the DOM, so there is no option to style text or otherwise change existing content. However, they are entirely non-destructive operations which can result in efficiency gains, making them a competitive alternative.

Important: The assumption through this answer is that you either apply styling directly to elements, or use a stylesheet and generated attributes to apply it remotely. This type of DOM manipulation is still required.

Approaches

  1. Monolithic

    • Overlay the entire content area with a single element.

    • Draw all highlighting onto the element.

      • Get client rects of the required ranges of text.
      • Compare with the client rects of the overall element, obtaining the relative drawn text positions.*
      • Use one of the implementations below.

    Pros:

    • Simple, obvious.
    • All highlighting managed in one place.
    • Efficient for mutations of many elements at once.

    Cons:

    • Many compromises. If you choose to overlay the highlights, opacity of highlighting is necessarily a compromise between visibility and obscuring text. If you underlay, you must remove all backgrounds to avoid highlights being hidden.
    • Inefficient for mutations of few elements, as everything must be recalculated at once (partially mitigated by caching).
    • Inefficient for implementations that perform best in small, disparate frames.
  2. Composite

    • For all text to be highlighted, look at the lowest-level distinct elements which contain parts of the text ranges.

    • Draw highlighting onto each of these elements.

      • Get client rects of the required ranges of text.
      • Compare with the client rects of the specific containing element, obtaining the relative drawn text positions.*
      • Apply one of the implementations below to the containing element.

    Pros:

    • Removes problem of highlights being hidden by element backgrounds.
    • Layout shifts will to an extent carry highlighting with them, reducing the need for vigilant updates/recalculations.
    • Much more pertinent to underlay instead of overlay, so less compromise between obscuring text and emphasising it.
    • Efficient for updates of few elements, as highlighting is modular and decomposed into a range of distinct areas.

    Cons:

    • Complex, nuanced.
    • Under some implementations, one background (normally background-image) is still potentially lost per highlighted element.
    • Inefficient for mutations of many elements at once
    • Inefficient for implementations that perform best in large, few frames.
  3. Hybrid

    • Combine highlighting responsibility into a few elements (for example, ones with display: block to handle highlights for its display: inline children).

Implementations

  1. Elements

    • Create highlighting elements of appropriate sizes.
    • Absolutely position them such that they cover the intended areas.

    Pros:

    • Faultless browser support.
    • Infinite effects are available.

    Cons:

    • Heavyweight, slow, DOM-reliant.
    • Adds many superfluous elements to the main structure of the page, disruptive.
  2. SVG

    • Percent-encode an SVG drawing the laid out highlighting elements.
    • Use url() on the background-image of the ancestor element.

    Pros:

    • Pretty fast.
    • Compatible with modern browsers.

    Cons:

    • Page CSPs will (rarely) block the use of url(), expecting it to extract remote content.
  3. Canvas

    • Percent-encode a canvas drawing the laid out highlighting elements, or use a <canvas> element.
    • Use url() on the background-image of the ancestor element.

    Pros:

    • Compatible with modern browsers.

    Cons:

    • Extremely slow after only a few canvases (especially large ones), presumably due to each pixel being explicitly painted. Seems to be poorly optimised, particularly for transparent pixels.
    • If url() is used: Page CSPs will (rarely) block the use of url(), expecting it to extract remote content.
  4. Houdini

    • Register a Paint worklet that draws highlights based on sizing and positioning CSS variables, with whatever additional info you deem necessary (see details of Houdini Paint API).
    • Use background-image: paint({worklet name}) as appropriate on elements to display highlighting.
    • Define CSS variables (the ones consumed by the worklet) on the same element - consider using a stringified JSON object for this which will be unpacked within the worklet, it's surprisingly efficient.
  5. element()

    • Create highlighting elements of appropriate sizes.
    • Absolutely position them such that they cover the intended areas, in a dedicated portion of the DOM (offscreen).
    • Use background: element({ID of highlighting element}) as appropriate on elements to display highlighting.

    Pros:

    • Infinite effects are possible.
    • Much more viable than the direct DOM alternative.

    Cons:

    • Specific to Firefox, on which it is experimental but has been supported for years.
    • For some reason requires application to background instead of just background-image, resulting in more overriding of element backgrounds. This seems to be an oversight.
    • Heavyweight, a little slow, DOM-reliant.

Dynamically Updating

I won't go into much detail about this, as it is very specific to your needs. However, to obtain fully up-to-date highlighting when applied to an entire page, I needed:

  • A MutationObserver observing subtree, childList, and characterData for the document body, in order to detect all changes that may affect matching. My case is very extreme, which is why I must observe the entire document; I optimised using various methods, including filtering out events that did not require attention.

  • A ResizeObserver observing highlighted elements currently in view, in order to recalculate highlighting when layout shifts occur.

  • An IntersectionObserver observing all highlighted elements for when they appear in the viewport, in order to draw highlighting and the ResizeObserver only when needed. This is an optimisation measure only.

Information

Here is the PR in which I achieved this, using multiple of the above methods which could be swapped out. I used a highly structured approach - involving caching across the DOM and other optimisations, such as decomposing it into multiple stages - to make it efficient even for cases where all of an average page has been highlighted in various colours. An overview is presented in the PR.

This being one of the most extreme possible cases, it should be entirely possible for others to achieve good results without significant performance loss.

I am happy to answer any questions regarding my implementation, or getting started with these technologies.

Articles

Notes

*Due to convoluted browser behaviour when it comes to the association of client rects with actual layout+position, it is very difficult to make sure an overlay-based highlighting algorithm will position highlight-boxes correctly in all cases. I have still not managed to correctly account for the effect of border (which disrupts calculations) or strange cases of flow content, but it is technically possible using only the results of Range.getClientRects() and Element.getClientRects().

1 Comment

Can you strip out the simplest implementation to a function or lib? Thank you.
4

It is possible if you use an absolutely positioned element with a transparent repeating background image or a transparent background color (using rgba or hsla) and position it over the selected area.

Another way to do it would be to have an absolutely positioned canvas element without a background that takes up the whole browser viewport and draw a transparent rectangle over the selection.

2 Comments

How would you get the absolute position of a piece of text without changing the DOM?
Add the <span> element to the DOM, find out its position, then delete it and normalize().
0

It's not possible.

If you just want no tags in the original source code, it might be possible by adding tags later using Javascript magic. You could do something like

<p highlight="quick">The quick fox</p> 

and write a JQuery/Prototype/plain JS function to highlight it on the fly, but what for and why? If you elaborate a bit, someone may come up with an idea.

1 Comment

I wonder how is this kind of highlighting possible.
0

The only way to do this than I can imagine would be to use the <canvas> element, and render absolutely everything by hand.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.