1

I have been searching to implement a solution to calculate the lines of an element in React (writing in Typescript) using React Hooks and Redux.

A jquery implementation I found and liked here from member e-info128 that has 9 votes for the only reason that

"Works in all browsers without rare functions out of standards."

In the current project that I work on, we implement jquery as well (don't ask why we mixed the two, it was not our decision)... So initially I tried to make it work in jquery. And it works!! The code below does the job.

    const calculate = (obj: JQuery<HTMLElement>) => {
        let cloned = obj.clone(true, true);
        cloned.html('a<br>b').hide().appendTo(parentClass);
        let size = cloned?.height()! / 2;
        cloned.remove();
        let noOfLines = obj?.height()! / size;

        return Math.trunc(noOfLines);
    }


   window.addEventListener("resize", () => {      
        let element = $('#calculateLines');
        if (element) {
            let lines = calculate(element);
            console.log(lines);
        }

    });

However I really wanted to try and "translate" this and not use jquery...

So I have the following snippet:

    const calculateNotInJQuery = (obj: HTMLElement) => {
        let cloned = obj.cloneNode(true) as HTMLElement;

        let tempElement = document.createElement('p');
        tempElement.innerHTML = "a<br></br>b";
        tempElement.setAttribute("type", "hidden");

        let parentElement = document.getElementById('someParentClass');
        cloned.appendChild(tempElement)

        parentElement?.appendChild(cloned);

        let size = cloned.clientHeight / 2;
       
        cloned.remove();
        let noOfLines = ((obj.clientHeight / size);
        return Math.trunc(noOfLines);
    }


   window.addEventListener("resize", () => {      
        let element = $('#calculateLines');
        if (element) {
            let lines = calculate(element);
            console.log(lines);
        }

    });

The last line returns always 1. If I remove Math.trunc, it takes values from 0 to 1. I understand that I have trouble "translating" this line:

cloned.html('a<br>b').hide().appendTo(parentClass);

to make it work. What I think it says, is: we create some html for the cloned Node, we hide it and we append it to the parent element.

so I tried to do something similar but I do not know where my logic is wrong here:

   let cloned = obj.cloneNode(true) as HTMLElement;

        let tempElement = document.createElement('p');
        tempElement.innerHTML = "a<br></br>b";
        tempElement.setAttribute("type", "hidden");

        let parentElement = document.getElementById('someParentClass');
        cloned.appendChild(tempElement)

        parentElement?.appendChild(cloned);

Any suggestions will be appreciated as always. Thank you in advance!

[EDIT]

After ori-drori's suggestion, I created the solution and I also created a sandbox because there seems to be sth wrong with it.

The sandbox is here.

What happens is,

  1. when the button is as "Read More", then the lines are calculated correctly -when I resize the window-.The button is hidden or displayed as it should.
  2. Now I click on the Read More, all the text is displayed, and the correct calculated lines are visible. We have the "Read Less" label now.
  3. When I resize the window, with the "Read Less" description and all the text displayed, the calculation is not correct. For instance, I can have 5 lines visually but the calculation gives me 7 lines.

What am I doing wrong now? What is happening between expanding and collapsing the text that gives me a faulty number of lines?

Ahmet Emre Kilinc
  • 5,489
  • 12
  • 30
  • 42
SoftDev30_15
  • 463
  • 7
  • 20

1 Answers1

2

The main idea of the answer you've referenced is:

  1. Clone the original element
  2. Replace it's content with two lines of text
  3. Find the height of the element after the change and divide it by two - this is the supposed line height
  4. Divide the actual height of the element by the supposed line height to find the number of lines

You can achieve the same results by creating a custom hook:

  1. Store a reference to the container with useRef().
  2. In useEffect() calculate the height of the container, and divide by the supposed height of the line (computed in `getLineHeight).
  3. Set the numberOfLines state.

Note: you should probably debounce/throttle the countLines function, since the resize event is called multiple times, and adding, measuring, and then removing elements from the DOM repeatedly would stress the UI thread.

const { useRef, useState, useEffect } = React;

const getLineHeight = el => {
  const cl = el.cloneNode();

  cl.style.visibility = 'hidden';
  cl.style.position = 'absolute';
  cl.innerHTML = 'a<br>b';
  
  document.body.append(cl);  
  
  const { height } = cl.getBoundingClientRect();
  
  cl.remove();
  
  return height / 2;
};

const useCountLines = () => {
  const targetRef = useRef();
  const [numberOfLines, setNumberOfLines] = useState(null);

  useEffect(() => {
    const countLines = () => {
      if(!targetRef.current) return;
    
      const { height } = targetRef.current.getBoundingClientRect();
      const lineHeight = getLineHeight(targetRef.current);
      
      setNumberOfLines(Math.trunc(height / lineHeight));
    };
    
    countLines();
    
    window.addEventListener('resize', countLines);
    
    return () => {
      window.removeEventListener('resize', countLines);
    };
  }, []);
  
  return [numberOfLines, targetRef];
}

const Demo = () => {
  const [numberOfLines, targetRef] = useCountLines();
  
  console.log(numberOfLines);

  return (
    <div ref={targetRef}>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis at odio ultricies tortor aliquet mollis. Praesent iaculis libero ac vestibulum viverra. Maecenas et lorem nulla. Sed porta a erat a interdum. Fusce pharetra eget purus id gravida. Vivamus semper pellentesque massa. Phasellus eget faucibus lacus. Cras eget tellus facilisis, dapibus diam eu, convallis nulla. Nunc facilisis nibh a est dictum egestas. Nulla tempus ante vitae mauris mollis, eget tincidunt felis congue.</div>
  );
};

ReactDOM.render(
  <Demo />,
  root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209