skip to Main Content

I want to display some text, split into groups of a given length, but retain the ability to search the page for text that spans multiple groups. With this, I want to display some character above each group.

The solution I’ve ended up with is the following one:

  body {
    margin-top: 1rem;
  }

  span {
    margin-right: 1rem;
    position: relative;
  }

  span::before {
    content: "⇣";
    position: absolute;
    top: -1.2rem;
    left: 0;
    color: red;
  }
 <span>This_is_a_sp</span><span>lit_string</span>

Note: the string and split position are hard-coded here, but are dynamically calculated in the real case.

This makes the visual part of the job is perfectly fine, but the problem is that, in Firefox (tested in Chromium, where it works), searching the page for the text split, which is split between the two groups, gives no results.

Do you have any clue on how to achieve that ? I am not fixed on the way of achieving this (especially the ::before way), so any idea is welcome ! The only requirement is to use the browser’s native Search in page… tool.

Edit:

I’ve found a Bugzilla bug also reporting this problem. So it seems unlikely to solve it using the ::before method 😪. Any other solution still welcome !

2

Answers


  1. If the browser’s native search feature is a strict requirement, and since Firefox doesn’t behave like Chromium does when searching content spanning among adjacent elements, an alternative solution might be to just have the whole content joined upfront, and via JS, just add an arrow at given letter positions. That way the search will be performed correctly and yet you’ll have the arrows.

    Note: the string and split position are hard-coded here, but are
    dynamically calculated in the real case.

    It’s not clear if you can rely on letter positions for those arrows but so far that’s the best I could come up with.

    The trick was finding the coords belonging to a given letter position and use them to position a newly created element, holding the arrow, on top of the .content div.

    I used a monospaced font that better fits this strategy.

    In this demo I also addressed a scenario in which the content will overflow the container so the word wrap will kick in. The question let me understand that your content is a stream of adjacent letters not necessarily belonging to real words and not necessarily having spaces.

    So I enforced word-wrap: break-word; to break words if not containing spaces.

    Since the arrows position rely on how the text was rendered at the time of their creation, every time the container size changes (because of window resizing), there’s an observer redrawing the arrows when the event occurs. You’ll see when resizing the window, the arrows stick to the same letter they were since the beginning.

    A side effect here is not having any space between fragments like you showed in your example. If that’s an important feature to have, I’m afraid this solution is not viable.

    //on page ready
    document.addEventListener('DOMContentLoaded', ()=>{  
      //the content target
      const contentDiv = document.querySelector('.content');
      //draws the arrows inside the target
      redrawArrows(contentDiv);
      
      //sets up an observer that will redraw the arrows when the target container is resized
      const resizeObserver = new ResizeObserver(()=>{redrawArrows(contentDiv);});
      resizeObserver.observe(contentDiv);
    })
    
    //inside element, add an arrow on top of given letter position
    function addArrowAbovePosition(element, position) {
        //get the specified letter's position in the div
        const range = document.createRange();
        range.setStart(element.firstChild, position);
        range.setEnd(element.firstChild, position + 1);
        const rect = range.getBoundingClientRect();
    
        //create the arrow element
        const arrow = document.createElement('div');
        arrow.classList.add('arrow');    
        arrow.innerHTML = '⇣';    
        arrow.style.left = rect.left + 'px';
        arrow.style.top = (rect.top - rect.height) + 'px';    
        
        //append the arrow to the div
        element.appendChild(arrow);
    }
    
    //redraw the arrows in element
    function redrawArrows(element){
      //remove any existing arrows
      element.querySelectorAll('.arrow')
        .forEach(arrow => arrow.remove());
    
      //add arrows at custom positions (2, 10, 87)
      addArrowAbovePosition(element, 2); 
      addArrowAbovePosition(element, 10); 
      addArrowAbovePosition(element, 87);
    }
    body {
      margin-top: 1rem;
      /*monospaced font will work better in this scenario*/
      font-family: monospace;
      font-size: 20px;
    }
    
    .arrow{
      position: absolute;
      color: red;    
    }
    
    .content{
      /*word wraps also content not having spaces*/
      word-wrap: break-word;
      /*allows arrows to have their own space among text lines*/
      line-height: 2em;
    }
    <div class="content">
    This_is_a_very_long_text_to_highlight_a_scenario_where_the_content_will_word_wrap_no_matter_what_even_if_theres_no_space_in_between_words
    </div>
    Login or Signup to reply.
  2. It seems the Firefox issue is caused by the pseudo element, because it places itself between the 2 words. If you use :before on the first span and :after on the second span search works on Firefox too.

    Updated example:

    body {
      margin-top: 1rem;
    }
    
    p {
      display: inline-block;
    }
    
    span {
      margin-right: 1rem;
      position: relative;
    }
    
    span.after::after,
    span.before::before {
      content: "⇣";
      position: absolute;
      top: -1.2rem;
      left: 0;
      color: red;
    }
    <span class="before">This_is_a_sp</span><span class="after">lit_string</span>

    The issue with this solution is that you’re limited to 2 spans, you can’t do something like:

    <span class="before">This_is_a_sp</span><span class="after">lit_stri</span><span class="before">ng</span>
    

    Because the pseudo element again places itself between the 2 words. To make it more clear:

    Pseudo element issue example

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search