1

I try to build "read more/less" functionality with fade in/out text.

Since CSS transitions do not work on height: auto;, I am forced to hardcode the value in CSS with my approach. Which could be too small or too big. The fade-in text can vary in length from element to element where the JS will be applied on.

I can read the real height of the <p id="txt"> element which includes the transparent text before any css is applied:

document.addEventListener("DOMContentLoaded", function(event) {...});

So I know about the height I need to transform to before the toggle happens but I can't figure out a way to make it work. Is this even possible with my approach?

Changing the height property right after the toggle with currentText.style.height = realHeight + "px"; does not work since it's too late here.

document.addEventListener("DOMContentLoaded", function(event) {
   // Get the height including the transparent text 
   var realHeight = document.getElementById("txt").scrollHeight;
   var heightDisplay = document.getElementById("realHeight");
   heightDisplay.innerHTML += realHeight + "px";
});


const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
const wrapper = document.querySelector(".wrapper");

wrapper.addEventListener("click", e => {

  const current = e.target;
  
  const isReadMoreBtn = current.className.includes("read-more-btn");
  
  if (!isReadMoreBtn)
    return;
    
  const currentText = e.target.parentNode.querySelector(".read-more");

  currentText.classList.toggle("read-more_open");
  
  // currentText.style.height = realHeight + "px";   

  current.textContent = current.textContent.includes("Read More") ? "Read Less" : "Read More";

});
/* toggle classes */

.read-more {
  display: block;
  opacity: 0;
  height: 0;
  transition: all 0.5s;
}

.read-more_open {
  display: block;
  opacity: 1;
  height: 5em;
}


/* other styles  */
.wrapper {
  width: 20rem;
  background-color: black;
  color: white;
  margin: auto;
}

.contents {
  padding: 0.25rem;
}

.title {
  font-size: 1.7em;
  margin-bottom: 1rem;
  letter-spacing: 1px;
}

p {
  font-size: 0.8rem;
}

.read-more-btn {
  display: inline-block;
  color: white;
  background-color: grey;
  padding: 0.6rem 1.5rem;
  font-size: 1em;
  position: relative;
  z-index: 1;
  transition: all 0.5s;
  cursor: pointer;
}

.read-more-btn:hover {
  background-color: #fff;
  color: grey;
  box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
  transform: translateY(-3px);
}
<span id="realHeight">Real height: </span>

<div class="wrapper">

  <div class="contents">

    <h1 class="title">Some Title</h1>
    <p id="txt">
      ALWAYS VISIBLE CONTENT
      <span class="read-more">
        Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in an unknown height of the paragraph.
      </span>
    </p>

    <p class="read-more-btn">Read More</p>

  </div>

</div>
JustAG33K
  • 1,403
  • 3
  • 13
  • 28

2 Answers2

2

Solution 1

Use max-height instead, and add a value that is bigger than expected to make sure that the content fits.

Edited:

.read-more {
  max-height: 0;
}

.read-more_open {
  max-height: 500px;
}

Check JavaScript

The Code:

const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
const wrapper = document.querySelector(".wrapper");

btn.addEventListener("click", e => {
  // toggle the class 'read-more_open'
  text.classList.toggle("read-more_open");
  // if `text` contains the class 'read-more_open' 
  //set `btn` HTML to 'Read Less', else? set to 'Read More'
  text.classList.contains("read-more_open") ? btn.innerHTML = "Read Less" : btn.innerHTML = "Read More";
});
/* toggle classes */

.read-more {
  display: block;
  opacity: 0;
  max-height: 0;
  transition: all 0.5s;
}

.read-more_open {
  display: block;
  opacity: 1;
  max-height: 500px;
}


/* other styles  */
.wrapper {
  width: 20rem;
  background-color: black;
  color: white;
  margin: auto;
}

.contents {
  padding: 0.25rem;
}

.title {
  font-size: 1.7em;
  margin-bottom: 1rem;
  letter-spacing: 1px;
}

p {
  font-size: 0.8rem;
}

.read-more-btn {
  display: inline-block;
  color: white;
  background-color: grey;
  padding: 0.6rem 1.5rem;
  font-size: 1em;
  position: relative;
  z-index: 1;
  transition: all 0.5s;
  cursor: pointer;
}

.read-more-btn:hover {
  background-color: #fff;
  color: grey;
  box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
  transform: translateY(-3px);
}
<span id="realHeight">Real height: </span>

<div class="wrapper">

  <div class="contents">

    <h1 class="title">Some Title</h1>
    <p id="txt">
      ALWAYS VISIBLE CONTENT
      <span class="read-more">
        Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph.
      </span>
    </p>

    <p class="read-more-btn">Read More</p>

  </div>

</div>

Solution 2

If the height value in px is important, you can get the full height first, and then hide the text and set max-height to 0 and show the wrapper. (The user won't notice the transition since wrapper has no transition property).

CSS Edited:

.wrapper {
    opacity: 0;
}

.wrapper.show {
    opacity: 1;
}

The Code:

const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
const wrapper = document.querySelector(".wrapper");
const realHeightEl = document.getElementById('realHeight');

// Variable to hold the exact height of wrapper
let h;

window.addEventListener('load', () => {
    // get text height
    h = text.getBoundingClientRect().height;
    // show height in HTML `realHeight` element
    realHeightEl.innerHTML += `${h}px`;
    // show the wrapper
    wrapper.classList.add('show');
    // hide the text
    text.style.maxHeight = '0';
});

btn.addEventListener("click", () => {
    // if the height is 0:
    // set it to `h` (text `height`), `opacity` 1, and change `btn` text
    if(text.style.maxHeight == '0px'){
        text.style.maxHeight = h + 'px';
        text.style.opacity = '1';
        btn.innerHTML = "Read Less";
    } else {
        // else ? set the `height` and `opacity` to `0` and change btn text.
        text.style.maxHeight = '0px';
        text.style.opacity = '0';
        btn.innerHTML = "Read More";
    }
});
/* toggle classes */

.read-more {
    display: block;
    opacity: 0;
    max-height: fit-content;
    transition: all 0.5s;
}

.read-more_open {
    display: block;
    opacity: 1;
}


/* other styles  */
.wrapper {
    width: 20rem;
    background-color: black;
    color: white;
    margin: auto;
    opacity: 0;
}

.wrapper.show {
    opacity: 1;
}

.contents {
    padding: 0.25rem;
}

.title {
    font-size: 1.7em;
    margin-bottom: 1rem;
    letter-spacing: 1px;
}

p {
    font-size: 0.8rem;
}

.read-more-btn {
    display: inline-block;
    color: white;
    background-color: grey;
    padding: 0.6rem 1.5rem;
    font-size: 1em;
    position: relative;
    z-index: 1;
    transition: all 0.5s;
    cursor: pointer;
}

.read-more-btn:hover {
    background-color: #fff;
    color: grey;
    box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
    transform: translateY(-3px);
}
<span id="realHeight">Real height: </span>

<div class="wrapper">

    <div class="contents">

        <h1 class="title">Some Title</h1>
        <p id="txt">
            ALWAYS VISIBLE CONTENT
            <span class="read-more">
                Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with
                unknown length which results in unknown height of the paragraph. Fade in text with unknown length which
                results in unknown height of the paragraph. Fade in text with unknown length which results in unknown
                height of the paragraph. Fade in text with unknown length which results in unknown height of the
                paragraph.
            </span>
        </p>

        <p class="read-more-btn">Read More</p>

    </div>

</div>

Note:

the h variable holds the max-height value to set in JavaScript, in short, it's the same functionality in Solution 1.

// This CSS declaration is deleted in Solution2
.read-more_open {
    max-height: 500px;
}

That means, in Solution2:

Replaced:

  • This in JavaScript -> max-height: h;

  • Instead of this in CSS: max-height: 500px;

This way we make sure that the max-height is dynamically declared no matter what is the height value of the text.

001
  • 2,019
  • 4
  • 22
  • Thank you very much. In Solution 1 the shorter JS is a real improvement (learned a lot on this one). I like Solution 2, but I do not understand why the `
    ` height is taken and not the one from `

    ` ?

    –  Sep 19 '21 at 12:15
  • Using max-height, the text overflows at the bottom when resizing to a smaller view. I made a mistake in the `.wrapper` class. It should be `max-width: 20rem` instead of `width: 20rem`. –  Sep 19 '21 at 12:32
  • Updated, please check the **Note** section for an explanation. – 001 Sep 19 '21 at 13:49
  • I understand, but why is not the height of the `

    ` element taken ? Is there a reason for working with the wrappers height instead of the `

    ` ?

    –  Sep 19 '21 at 14:06
  • You are right... Replaced the `h` value to `text.height` instead of `wrapper.height`. – 001 Sep 19 '21 at 14:20
0

Used .contains to check if is contains the class read-more_open :

  • If yes then height is changed to scroll height
  • If no height is changed back to 0px

var heightDisplay = document.getElementById("realHeight");
var realHeight = document.getElementById("txt").scrollHeight;

const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");

btn.addEventListener("click", e => {
  text.classList.toggle("read-more_open");


  if (text.classList.contains("read-more_open")) {
    heightDisplay.innerHTML = "Yes your text height matter to us";
    document.querySelector(".read-more").style.height = realHeight + "px";
  } else {
    heightDisplay.innerHTML = "You are back where you started in height";
    document.querySelector(".read-more").style.height = "0px";
  }

  btn.textContent = btn.textContent.includes("Read More") ? "Read Less" : "Read More";
});
/* toggle classes */

.read-more {
  display: block;
  opacity: 0;
  height: 0;
  transition: all 0.5s;
}

.read-more_open {
  display: block;
  opacity: 1;
}


/* other styles  */

.wrapper {
  width: 20rem;
  background-color: black;
  color: white;
  margin: auto;
}

.contents {
  padding: 0.25rem;
}

.title {
  font-size: 1.7em;
  margin-bottom: 1rem;
  letter-spacing: 1px;
}

p {
  font-size: 0.8rem;
}

.read-more-btn {
  display: inline-block;
  color: white;
  background-color: grey;
  padding: 0.6rem 1.5rem;
  font-size: 1em;
  position: relative;
  z-index: 1;
  transition: all 0.5s;
  cursor: pointer;
}

.read-more-btn:hover {
  background-color: #fff;
  color: grey;
  box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
  transform: translateY(-3px);
}
<span id="realHeight">Click Read more to see more </span>

<div class="wrapper">

  <div class="contents">

    <h1 class="title">Some Title</h1>
    <p id="txt">
      ALWAYS VISIBLE CONTENT
      <span class="read-more">
        Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in an unknown height of the paragraph.
      </span>
    </p>

    <p class="read-more-btn">Read More</p>

  </div>

</div>
Rana
  • 2,500
  • 2
  • 7
  • 28