7

I am using the Line-Clamp property (with a backup max-height) to limit the number of lines to show in a React component. I would like to have an optional link afterwards that will expand this content to its full length, but only if the current number of lines is greater than the line-clamp number.

The number of lines is fixed (3) so I guess I could just calculate the current height of the div and then compare it to the expected height of 3 lines at standard text size?

But then if someone decides to put different non text content in it it might not work as intended. Is there a specific way to get the number of lines of text in a container?

 const {useState} = React;

const ClampedDiv = ({ children, showLinkProp }) => {
  const [open, setOpen] = useState(false);

  // This is where I'd do the line number calculation, but it's just
  // using a placeholder instead.
  let showLink = false;
  if (showLinkProp) {
    showLink = true;
  }
  
  let textClass = "text";
  if (open) {
    textClass += " open";
  }

  return <div className="container">
    <span class={textClass}>{children}</span>
    {showLink && !open && (
      <button onClick={() => setOpen(true)}>Open</button>
    )}
  </div>
};

const Component = () => (
  <React.Fragment>
    <ClampedDiv>
      Some content that should not show a read more
    </ClampedDiv>
    <ClampedDiv showLinkProp>
      Some content that should show a read more. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
    </ClampedDiv>
  </React.Fragment>
);

ReactDOM.render(
  <Component />,
  document.body
)
.text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(3 * 1.5 * 14px);
  font-size: 14px;
  line-height: 1.5;
}

.open {
  -webkit-line-clamp: unset;
  max-height: none;
}

.container {
  background-color: crimson;
  color: white;
  margin-bottom: 15px;
  padding: 15px;
}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>
James Paterson
  • 2,652
  • 3
  • 27
  • 40
  • may be get the inenerHtml anf then use string.split(" "), which returns the number of texts? – Shyam Kumar Mar 23 '21 at 13:33
  • 1
    Did you check https://stackoverflow.com/q/783899/2873538 and [this](https://www.geeksforgeeks.org/how-to-count-text-lines-inside-of-dom-element/)? – Ajeet Shah Mar 24 '21 at 14:46
  • 1
    I've seen them but the first is just base JS - I adapted the solution to React, I'll post my version if I get some time later. – James Paterson Mar 24 '21 at 16:40
  • The gist of the solution is a div that can expand to any size inside a max-height container. You then measure the height of that box compared to the container and the expected line-height. If it's bigger you show the button. The issue is that using `line-clamp` automatically limits the size of the box, so you can't use it and have to use max-height on the container instead, which is sad as you don't get the automatic ellipsis. But it does work! – James Paterson Mar 24 '21 at 16:43
  • 1
    @JamesPaterson did ever get a solution to this? – Mateen Kazia Jul 18 '22 at 08:35

1 Answers1

3

If you just want to check for hidden content inside your element, you can use the useRef hook to refer to the specific element.

And then, use scrollHeight to get the height of the content of the element.

The Element.scrollHeight read-only property is a measurement of the height of an element's content, including content not visible on the screen due to overflow.

And compare it to the clientHeight to see if there is hidden content.

 const { useState, useRef, useLayoutEffect } = React;

const ClampedDiv = ({ children }) => {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  // This is where I'd do the line number calculation, but it's just
  // using a placeholder instead.
  const [showLink, setShowLink] = useState(false);
  
  useLayoutEffect(() => {
      if (ref.current && ref.current.clientHeight < ref.current.scrollHeight) {
        setShowLink(true)
      }
  }, [ref])
  
  let textClass = "text";
  if (open) {
    textClass += " open";
  }

  return <div className="container">
    <span class={textClass} ref={ref}>{children}</span>
    {showLink && !open && (
      <button onClick={() => setOpen(true)}>Open</button>
    )}
  </div>
};

const Component = () => (
  <React.Fragment>
    <ClampedDiv>
      Some content that should not show a read more
    </ClampedDiv>
    <ClampedDiv>
      Some content that should show a read more. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop penter code hereublishing software like Aldus PageMaker including versions of Lorem Ipsum.
    </ClampedDiv>
  </React.Fragment>
);

ReactDOM.render(
  <Component />,
  document.body
)
.text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(3 * 1.5 * 14px);
  font-size: 14px;
  line-height: 1.5;
}

.open {
  -webkit-line-clamp: unset;
  max-height: none;
}

.container {
  background-color: crimson;
  color: white;
  margin-bottom: 15px;
  padding: 15px;
}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>

As you can see, now the implementation of the check to show more content is specific to the child component and depends on the content passed by the parent component.

Ricarte
  • 81
  • 6
  • The problem here is that you can't have a button to collapse -- if you check the viability of that button based on the height comparsion, you can either choose to have a collapse button both when the text is expanded _and_ when the original text needs no truncation. – Californian Aug 03 '23 at 23:14