Как сделать кнопки "Еще" на обрезание текста js

По клику на кноки показывать скрытый текст. Нужно реализовать через js . Обрезать текста через js . И чтобы кнопка 'еще' сменялась на 'cкрыть'

Есть пример кода, но сейчас работает не корректно

let textHolder = document.querySelector('.demo');
let fullText = textHolder.innerHTML;
let btn = document.querySelectorAll('.btns');
let textStatus = 'full';

function Truancate(textHolder, limit) {
  let txt = textHolder.innerHTML;
  if (txt.length > limit) {
    let newText = txt.substr(0, limit) + ' ...';
    textHolder.innerHTML = newText;
    textStatus = 'truncated';
  }
}

Truancate(textHolder, textHolder.offsetWidth / 3.39);

function toggleText() {
  if (textStatus === 'truncated') {
    textHolder.innerHTML = fullText;
    textStatus = 'full';
  } else {
    Truancate(textHolder, textHolder.offsetWidth / 3.32);
  }
}

btn.forEach((btns) => {
  btns.addEventListener('click', toggleText);
});
 <div class="box">
   <span class="demo" id="demo">Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу..Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу</span>
    <span class="btns">Еще</span>
</div>
<br>

 <div class="box">
   <span class="demo" id="demo">Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу..Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу</span>
    <span class="btns">Еще</span>
</div>


Ответы (2 шт):

Автор решения: BlackStar1991

Все элементально решаеться при помощи пары строчек JS

document.querySelectorAll('.box').forEach(box => {
  let textHolder = box.querySelector('.demo');
  let fullText = textHolder.innerHTML;
  let btn = box.querySelector('.btns');

  function toggleText() {
    box.classList.toggle('active');
  }

  btn.addEventListener('click', toggleText);
});
.demo {

  height: calc(20px * 2);
  /* не стал писать 40px, чтобы показать, что максимум по высоте на 2 строки выделяю блок */

  overflow: hidden;
  line-height: 20px;
  /* каждая строка будет по высоте 15px, чтобы я точно мог установить высоту блока (это чисто визуально, ни на что не влияет) */
  text-align: justify;

  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}


.box.active .btns>span {
  display: none;
}

.box.active .btns:after {
  content: "Скрыть текст";
}

.box.active .demo {
  height: initial;
  display: block;
  overflow: initial;
}
 <div class="box">
   <span class="demo">Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу..Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу</span>
   <button class="btns"><span>Еще</span></button>
 </div>
 <br>

 <div class="box">
   <span class="demo">Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу..Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу</span>
   <button class="btns"><span>Еще</span></button>
 </div>

→ Ссылка
Автор решения: Arbery

Если считая размер, а не стилями обрезать, то придется предварительно запоминать текст, чтобы восстанавливать при открытии полного размера.

Основная функция toggleText - в ней при 1 раз сохранить текст, а затем - менять при наличии/отсутствии класса active. Либо исходный текст из запомненного, либо порезанный функцией truncate

const truncate = (text, limit) => {
  return text.substr(0, limit) + '...';
}

const toggleText = (e, item) => {
  const text = item.querySelector('.demo');
  let newText = '';
    
  if (!item.dataset.text) {
    item.dataset.text = text.innerHTML;
  }
  
  newText = (item.classList.contains('active'))
    ? item.dataset.text
    : truncate(item.dataset.text, item.offsetWidth / 3.32);
  
  text.innerHTML = newText;
  item.classList.toggle('active'); 
}

document.querySelectorAll('.box').forEach(item => {
  const btn = item.querySelector('.btns');
  toggleText(null, item);
  
  btn.addEventListener('click', e => toggleText(e, item));
});
.btns {
  display: inline-block;
  color: green;
  margin: 0 10px;
 }

.btns::after {
  content: 'Скрыть'
}

.active .btns::after {
  content: 'Показать'
}
<div class="box">
   <span class="demo" id="demo1">Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу..Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу</span>
    <span class="btns"></span>
</div>
<br>

 <div class="box">
   <span class="demo" id="demo2">Еще текст какой-то. Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. Я не ожидала такого результата. Отдельно хочу Я в восторге от качества работы Веры. О такой тщательной уборке можно было только мечтать. </span>
    <span class="btns"></span>
</div>

→ Ссылка