Обрезка текста по длине на чистом JavaScript без JQ

171
25 ноября 2018, 18:40

Помогите с решением вопроса, никак не получается реализовать обрезку текста по ширине родительского контейнера на определенной величине с добавлением многоточия в конце. Есть решение на CSS, но оно не кросбраузерно, также text-overflow: ellipsis также не подходит - это работает для одной строки без переносов.

Допустим есть текст, который должен обрезаться по одному символу начиная с определенной ширины блока-родителя, например после 400px. И на оборот, дополняться по одному символу назад при увеличении вплоть до 400px с исчезновением многоточия (...)

В сети есть решение с подключением JQ-плагинов, но этот вариант пе подходит, необходим нативный JS

Answer 1

Набросал несколько вариантов. У всех есть плюсы и минусы. Все комментарии непосредственно в коде и в блоках на предпросмотре. (Для просмотра желательно развернуть на весь экран.)

/* 
Динамическая подрезка текста, на основе сравнения  
высот оригинального и подставного контейнера  
*/ 
function fCutterText() { 
  document.querySelectorAll('.dynamic').forEach(function(element) { 
    // Создаём фейковый элемент из блока (без текста) 
    let oFakeElem = element.cloneNode(false); 
    // Назначаем стили и прячем фейк 
    oFakeElem.style = ` 
        height: auto; 
        position: absolute; 
        text-align-last: justify; 
        /*visibility: hidden; 
        z-index: -100;*/ 
        width: ` + getComputedStyle(element).width; 
    // Добавляем фейк к документу 
    document.body.appendChild(oFakeElem); 
    // Получаем актуальную высоту основного блока 
    let nHeightMain = parseFloat(getComputedStyle(element).height); 
    // Если у блока нет свойства "fakeText", тогда... 
    if (!element.fakeText) { 
      // Добавляем это свойство и запоминаем в нём исходный текст блока 
      element.fakeText = element.textContent; 
    } 
 
    //console.log('textAlignLast',oFakeElem.style.textAlignLast); 
    oFakeElem.innerText = element.fakeText; 
    // Если высота фейка больше оригинала, тогда... 
    if (parseFloat(getComputedStyle(oFakeElem).height) > nHeightMain) { 
      finish: 
 
        // Приращиваем строку большими подстроками... 
        for (let i = 0; i < element.fakeText.length; i += 20) { 
          // Заносим постепенно текст в фейк  
          oFakeElem.innerText = element.fakeText.substring(0, i); 
 
          // Если высота фейка стала больше оригинала, тогда... 
          if (parseFloat(getComputedStyle(oFakeElem).height) > nHeightMain) { 
 
            // Посимвольно сокращаем строку в фейке 
            for (let d = 0; d < element.fakeText.length; d++) { 
              // Заносим укороченный текст в фейк  
              oFakeElem.innerText = element.fakeText.substring(0, i - d); 
 
              // Если высоты уравнялись, тогда... 
              if (parseFloat(getComputedStyle(oFakeElem).height) <= nHeightMain) { 
                // Отрезаем у фейкового текста ещё 3 символа (под многоточие) и добавляем "…" 
                oFakeElem.innerText = oFakeElem.textContent.slice(0, -3) + '…'; 
                // Выходим из всех циклов разом 
                break finish; 
              } 
            } 
          } 
        } 
      // Копируем текст из фейка в основной блок 
      element.innerText = oFakeElem.textContent; 
    } 
    else { 
      // Копируем текст из фейка без изменений 
      element.innerText = element.fakeText; 
    } 
    // Удаляем фейк из документа 
    document.body.removeChild(oFakeElem); 
  }); 
} 
fCutterText(); 
 
/* 
Динамическое появление дополнительного элемента,  
на основе наличия полосы прокрутки 
*/ 
function fShowMore() { 
  document.querySelectorAll('.full').forEach(function(element) { 
    element.querySelector('a').style.display = (element.scrollHeight > element.offsetHeight) ? 'block' : 'none'; 
  }); 
} 
fShowMore(); 
 
/* Слушатели событий DOM (в качестве примера, для интерактивности блоков) */ 
var observer1 = new MutationObserver(function(mutations) { 
  fCutterText(); 
}); 
document.querySelectorAll('.dynamic').forEach(function(target) { 
  observer1.observe(target, { 
    attributes: true 
  }); 
}); 
 
var observer2 = new MutationObserver(function(mutations) { 
  fShowMore(); 
}); 
document.querySelectorAll('.full').forEach(function(target) { 
  observer2.observe(target, { 
    attributes: true 
  }); 
});
* { 
  box-sizing: border-box; 
} 
 
.cutter { 
  box-shadow: 1px 3px 9px rgba(0, 0, 0, 0.6); 
  font: 16px monospace; 
  margin: 15px; 
  max-width: 100%; 
  overflow: hidden; 
  padding: 0em 1em 0em 4em; 
  resize: horizontal; 
  text-align: justify; 
  width: 400px; 
} 
 
.singlerow { 
  height: 1.4em; 
  text-overflow: ellipsis; 
  white-space: nowrap; 
} 
 
.multirow { 
  height: 3.6em; 
  position: relative; 
} 
 
.pseudo::before { 
  background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff 54%); 
  bottom: 0; 
  content: '• • •'; 
  font: 1em sans-serif; 
  height: 1.3em; 
  line-height: 1.5em; 
  position: absolute; 
  right: 1em; 
  text-align: right; 
  width: 6em; 
} 
 
.adjacent>a { 
  background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff 54%); 
  bottom: 0; 
  color: #aaa; 
  font: .8em sans-serif; 
  height: 1.4em; 
  line-height: 1.4em; 
  padding-right: .3em; 
  position: absolute; 
  right: 1em; 
  text-align: right; 
  text-decoration: none; 
  transition: all 0.5s ease-out 0.1s; 
  width: 13em; 
} 
 
.adjacent>a:hover { 
  color: #a00; 
  font-weight: 700; 
  letter-spacing: .3em; 
  width: 21em; 
} 
 
.dynamic, 
.full { 
  text-align-last: justify; 
} 
 
.full>a { 
  background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff 4%); 
  bottom: 0; 
  color: #000; 
  font: .8em sans-serif; 
  height: 1.4em; 
  line-height: 1.4em; 
  padding-right: .3em; 
  position: absolute; 
  right: 1.3em; 
  text-align: right; 
  text-decoration: none; 
  transition: all 0.5s ease-out 0.1s; 
  width: 1.3em; 
  display: none; 
} 
 
.detail { 
  height: 3.6em; 
  left: 1em; 
  position: absolute; 
  width: 3.6em; 
} 
 
.detail>div { 
  box-shadow: 1px 2px 5px 0px black; 
  color: rgba(255, 255, 255, 0.6); 
  font-family: fantasy; 
  font-size: 0.8em; 
  margin-bottom: 0.3em; 
  margin-top: .3em; 
  padding-right: .3em; 
  text-align: right; 
  text-shadow: 1px 1px 1px #fff, -1px -1px 1px #000; 
} 
 
.css { 
  background: linear-gradient(to right, rgba(0, 0, 255, 0), #0d73b8 100%); 
} 
 
.html { 
  background: linear-gradient(to right, rgba(0, 0, 255, 0), #e45125 100%); 
} 
 
.js { 
  background: linear-gradient(to right, rgba(0, 0, 255, 0), #e4a229 100%); 
}
<hr> 
<div class="detail"> 
  <div class="css">CSS</div> 
</div> 
<div class="cutter singlerow">Одиночная строка, которая обрезается с помощью "text-overflow:ellipsis"</div> 
<hr> 
<div class="detail"> 
  <div class="css">CSS</div> 
</div> 
<div class="cutter multirow pseudo">А этот текст больше и не помещается в контейнер. С помощью псевдоэлемента ::before, в правом нижнем углу, даём понять, что это не весь текст. Тут, нужно добавлять контейнеру обработчик, чтобы пользователь смог перейти к полному тексту, кликнув по контейнеру. 
</div> 
<hr> 
<div class="detail"> 
  <div class="css">CSS</div> 
  <div class="html">HTML</div> 
</div> 
<div class="cutter multirow adjacent">От примера выше, отличие лишь в том, что псевдоэлемент заменён на обычный тег-ссылку "A". Этот элемент уже поддаётся более продвинутой кастомизации (анимация, стилизация, события и т.д.).<a href="#">read more…</a></div> 
<hr> 
<div class="detail"> 
  <div class="css">CSS</div> 
  <div class="js">JS</div> 
</div> 
<div class="cutter multirow dynamic">В этом примере, задействуется реальная подрезка, т.е. вывод только того контента, который помещается в блок. Это самый правильный метод, но к сожалению, не всегда корректно работает (или я чего-то не учёл)). Ещё один минус данного подхода - малое быстродействие. 
  На самом деле, кода не так уж много (большую часть занимают комментарии в коде и вспомогательные функции для наглядности).</div> 
<hr> 
<div class="detail"> 
  <div class="css">CSS</div> 
  <div class="html">HTML</div> 
  <div class="js">JS</div> 
</div> 
<div class="cutter multirow full">Ещё одна неплохая реализация, основанная на сравнении свойств <em>scrollHeight</em> и <em>offsetHeight</em>. Довольно простая и надёжная - оптимальный вариант.<a href="#">…</a></div> 
<hr>

Если интерактивность не требуется, то функции подрезки (в примерах с JS) нужно запускать там же, где что-то меняет текст в блоке. Т.е. вставили текст - запустили подрезку.

READ ALSO
This и его контекст [дубликат]

This и его контекст [дубликат]

На данный вопрос уже ответили:

126
Namespace в JavaScript

Namespace в JavaScript

При разборе мануала по использованию API от Яндекса столкнулся со следующей проблемойНепонятно откуда берется(или как/где подключается) объект...

145
Не вызывать отправку формы по нажатию на кнопку внутри нее

Не вызывать отправку формы по нажатию на кнопку внутри нее

Я создаю реакт компонент, содержащий в себе элементы, одним из которых является форма содержащая кнопкиТак как кнопки внутри формы, по-умолчанию,...

152
Выводить текущую дату по умолчанию на JS?

Выводить текущую дату по умолчанию на JS?

Нашел в сети готовый код на JS расчета предполагаемой даты родовС кодом все ОК, он прост и понятен

345