Home > Net >  How to absolutely position an element after the first line of a title
How to absolutely position an element after the first line of a title

Time:11-24

I am trying place an HTML element next to the end of the first line of a heading. For a better understanding, I took screenshots of what it looks like for now and what it should look like.

What it looks like: What it looks like

What it should look like: What it should look like

As you can see, I used the ::first-line pseudo-selector to change the background of the first line of the title in red.

So far I have tried to add a ::after to the ::first-line but there are two issues. First, I did not manage to display a text content in the ::after. Second, you can't add html content to both ::before and ::after so it was pointless.

You should know that the "sunshines" are not an image but an actual html element :


<div className={css.container}>
   <div className={css.shine1}></div>
   <div className={css.shine2}></div>
   <div className={css.shine3}></div>
</div>

.container {
  position: absolute;
  display: inline;
  top: 20px;
  right: 5px;

  div {
    position: absolute;
    height: 5px;
    border-radius: 5px;
    background-color: $mainColor;
    transform-origin: bottom left;
  }
  .shine1 {
    width: 26px;
    transform: rotate(-95deg) translate(20px, 5px);
  }
  .shine2 {
    width: 32px;
    transform: rotate(-45deg) translate(20px);
  }
  .shine3 {
    width: 26px;
    transform: rotate(0deg) translate(15px);
  }
}

This is JSX with scss modules. But basically the same syntax as HTML/CSS.

Do you have any idea on how to do it, I guess it is possible since the css is capable to know where the end of the line is since I can change the background.

CodePudding user response:

you can do it if it's possible to separate the two lines in heading, html:

<header>
        <div>
            <div >
                <p>
                    sample title, pretty long

                <div className={css.container}>
                    <div className={css.shine1}></div>
                    <div className={css.shine2}></div>
                    <div className={css.shine3}></div>
                </div>
                </p>
            </div>
            <p>
                that is wrapping in two lines
            </p>
        </div>
    </header>

css :

header .first-line{
  position: relative;
  display: inline-block;
}
.container {
  position: absolute;
  display: inline;
  top: 20px;
  right: 5px;

  
}
.container div {
  position: absolute;
  height: 5px;
  border-radius: 5px;
  background-color: red;
  transform-origin: bottom left;
}
.shine1 {
  width: 26px;
  transform: rotate(-95deg) translate(20px, 5px);
}
.shine2 {
  width: 32px;
  transform: rotate(-45deg) translate(20px);
}
.shine3 {
  width: 26px;
  transform: rotate(0deg) translate(15px);
}

CodePudding user response:

I have figured it out.

Although my solution is very "hacky", it works fine in any test case I thought about.

My solution is to create a hidden replica of the first line on top of the title with the same css properties. Once that is done I just get the width of the replica and use that value to define the left property of my sunshines.

This is the function I use to get the first line of the text :

const getFirstLine = el => {
  const text = el.innerHTML;

  //set the innerHTML to a character
  el.innerHTML = 'a';
  //get the offsetheight of the single character
  const singleLineHeight = el.offsetHeight;

  //split all innerHTML on spaces
  const arr = text.split(' ');

  //cur is the current value of the text we are testing to see if
  //it exceeds the singleLineHeight when set as innerHTML
  //prev is the previously tested string that did not exceed the singleLineHeight
  //cur and prev start as empty strings
  let cur = '';
  let prev = '';

  //loop through, up to array length
  for (let i = 0; i < arr.length; i  ) {
    //examine the rest of text that is not already in previous string
    const restOfText = text.substring(prev.length, text.length);

    //the next space that is not at index 0
    const nextIndex =
      restOfText.indexOf(' ') === 0
        ? restOfText.substring(1, restOfText.length).indexOf(' ')   1
        : restOfText.indexOf(' ');

    //the next part of the rest of the text
    cur  = restOfText.substring(0, nextIndex);

    //set the innerHTML to the current text
    el.innerHTML = cur;

    //now we can check its offsetHeight
    if (el.offsetHeight > singleLineHeight) {
      //once offsetHeight of cur exceeds singleLineHeight
      //previous is the first line of text
      //set innerHTML = prev so
      el.innerHTML = prev;
      //we can grab the innertext
      const firstLine = el.innerText;
      const indexOfSecondLine = prev.lastIndexOf('<');

      //reset el
      el.innerHTML = text;

      return firstLine;
    }

    //offsetheight did not exceed singleLineHeight
    //so set previous = cur and loop again
    //prev = cur   ' ';
    prev  = cur.substring(prev.length, cur.length);
  }
  el.innerHTML = text;
  return text;
};

If you want to do the same be careful to wait for the fonts of the web page to be loaded or the width you will be getting will be wrong.

document.fonts.ready.then(function () {})

I have also managed to handle when the title has the text-align: center; property by adding a margin-left/right: auto; to the replica that I then get and add to the calculated left using this :

parseInt(window.getComputedStyle(invisibleTitle).marginLeft)

I know it is not perfect, but since there is no easy way to do that in css it is the only working way that I found.

  • Related