Поиск отображаемых разрывов строк с помощью javascript

#javascript

#javascript

Вопрос:

У меня такая ситуация :

 div { width: 200px }  
 <div> example example example example example</div>  

Текст автоматически переходит на следующую строку при заполнении всей ширины <div> .

Используя javascript, как я могу получить отображаемое содержимое в приведенной выше строке?

примечание: В символьной строке нет символа новой строки

ожидаемый результат из приведенного выше фрагмента:

"example example example" соответствует строке 1 и "example example" соответствует строке 2

Комментарии:

1. Что вы подразумеваете под «вырезать» текст? Как выглядит ваш ожидаемый результат?

2. Попробуйте свойство переноса текста css. Нет необходимости в javascript

3. @RickyMo Правильно, например, как указано выше, div простирается на 2 строки. Я только хочу просто получить текст в строке выше

4. @MayankDudakiya мне нужно получить текст в строке выше и не хочу скрывать нижнюю строку

5. Вы имеете в виду, что у вас есть что-то вроде jsfiddle.net/c0tz35xv и попытаться получить каждую отображаемую строку ( ["This is", "some", "quite long" ... ) ?

Ответ №1:

Вы можете использовать Range API и его удобный getBoundingClientRect() метод, чтобы определить, какой символ отмечает захват в TextNode.

Обратите внимание, что это, очевидно, необходимо пересчитывать каждый раз, когда изменяется размер окна / что-то меняет макет.

 function getLineBreaks(node) {
  // we only deal with TextNodes
  if(!node || !node.parentNode || node.nodeType !== 3)
    return [];
  // our Range object form which we'll get the characters positions
  const range = document.createRange();
  // here we'll store all our lines
  const lines = [];
  // begin at the first char
  range.setStart(node, 0);
  // initial position
  let prevBottom = range.getBoundingClientRect().bottom;
  let str = node.textContent;
  let current = 1; // we already got index 0
  let lastFound = 0;
  let bottom = 0;
  // iterate over all characters
  while(current <= str.length) {
    // move our cursor
    range.setStart(node, current);
    if(current < str.length -1)
     range.setEnd(node, current 1);
    bottom = range.getBoundingClientRect().bottom;
    if(bottom > prevBottom) { // line break
      lines.push(
        str.substr(lastFound , current - lastFound) // text content
      );
      prevBottom = bottom;
      lastFound = current;
    }
    current  ;
  }
  // push the last line
  lines.push(str.substr(lastFound));

  return lines;
}

console.log(getLineBreaks(document.querySelector('.test').childNodes[0]));  
 div.test {
  width: 50px;
  margin-bottom: 100px;
  word-break: break-all;
}

body>.as-console-wrapper{max-height:100px}  
 <div class="test">This is some quite long content that will wrap in multiple lines</div>  

И если вам нужна относительная позиция y каждой строки:

 function getLineBreaks(node) {
  // we only deal with TextNodes
  if(!node || !node.parentNode || node.nodeType !== 3)
    return [];
  // our Range object form which we'll get the characters positions
  const range = document.createRange();
  // here we'll store all our lines
  const lines = [];
  // begin at the first character
  range.setStart(node, 0);
  // get the position of the parent node so we can have relative positions later
  let contTop = node.parentNode.getBoundingClientRect().top;
  // initial position
  let prevBottom = range.getBoundingClientRect().bottom;
  let str = node.textContent;
  let current = 1; // we already got index 0
  let lastFound = 0;
  let bottom = 0;
  // iterate over all characters
  while(current <= str.length) {
    // move our cursor
    range.setStart(node, current);
    if(current < str.length - 1)
      range.setEnd(node, current 1); // wrap it (for Chrome...)
    bottom = range.getBoundingClientRect().bottom;
    if(bottom > prevBottom) { // line break
      lines.push({
        y: prevBottom - (contTop || 0), // relative bottom
        text: str.substr(lastFound , current - lastFound) // text content
      });
      prevBottom = bottom;
      lastFound = current;
    }
    current  ;
  }
  // push the last line
  lines.push({
    y: bottom - (contTop || 0),
    text: str.substr(lastFound)
  });

  return lines;
}

console.log(getLineBreaks(document.querySelector('.test').childNodes[0]));  
 div.test {
  width: 50px;
  margin-bottom: 100px;
}

body>.as-console-wrapper{max-height:100px}  
 <div class="test">This is some quite long content that will wrap in multiple lines</div>  

Для тех, кому это нужно для работы с элементами вместо одного текстового узла, вот перезапись, которая вполне может завершиться неудачей (например, с направлением RTL), но которая должна быть подходящей для большинства случаев.

 function getLineBreaks(elem) {
  // our Range object form which we'll get the characters positions
  const range = document.createRange();
  // here we'll store all our lines
  const lines = [];
  const nodes = grabTextNodes(elem);
  let left = 0;
  // get the position of the parent node so we can have relative positions later
  let contTop = nodes[0].parentNode.getBoundingClientRect().top;
  // initial position
  let prevLeft = null;
  let lineText = "";
  let startRange = null;
  for (const node of nodes) {
    let nodeText = node.textContent;
    const textLength = nodeText.length;
    let rangeIndex = 0;
    let textIndex = 0;
    while (rangeIndex <= textLength) {
      range.setStart(node, rangeIndex);
      if (rangeIndex < textLength - 1) {
        range.setEnd(node, rangeIndex   1); // wrap the range (for Chrome...)
      }
      left = range.getBoundingClientRect().right;
      if (prevLeft === null) { // first pass
        prevLeft = left;
        startRange = range.cloneRange();
      } else if (left < prevLeft) { // line break
        // store the current line content
        lineText  = nodeText.slice(0, textIndex);
        startRange.setEnd(range.endContainer, range.endOffset);
        const {
          bottom
        } = startRange.getBoundingClientRect();
        lines.push({
          y: bottom - contTop,
          text: lineText
        });
        // start a new line
        prevLeft = left;
        lineText = "";
        nodeText = nodeText.slice(textIndex);
        textIndex = 0;
        startRange = range.cloneRange();
      }
      rangeIndex  ;
      textIndex  ;
      prevLeft = left;
    }
    // add the remaining text from this node into the current line content
    lineText  = nodeText;
  }
  // push the last line
  startRange.setEnd(range.endContainer, range.endOffset);
  const { bottom } = startRange.getBoundingClientRect();
  lines.push({
    y: bottom - contTop,
    text: lineText
  });
  return lines;
}

console.log(getLineBreaks(document.querySelector('.test')));

function grabTextNodes(elem) {
  const walker = document.createTreeWalker(elem, NodeFilter.SHOW_TEXT, null);
  const nodes = [];
  while (walker.nextNode()) {
    nodes.push(walker.currentNode);
  }
  return nodes;
}  
 div.test {
  width: 150px;
  margin-bottom: 100px;
}

.red {
  color: red;
}  
 <div class="test"><span class="red">This</span> is some quite long content that will wrap in <span class="red">mutiple</span> lines..</div>  

Комментарии:

1. спасибо за код. но в скрипке это не работает, когда я меняю css-класс .red, чтобы он также имел размер шрифта: 2em

2. @icube, да, использование .bottom в этом случае было не очень разумным, .red элементы не имеют одинакового .bottom значения, поскольку они больше. Я обновил свой ответ, включив новую версию, которая .left вместо этого просматривается, но обратите внимание, что это все еще не пуленепробиваемо. О, и если вы хотите, чтобы это было скрипкой: jsfiddle.net/puk067ym

3. браво, такое умное использование этих API, так красиво написано / прокомментировано. эта небольшая функция станет ценным (amp;amp; полностью атрибутируемым) модулем в моей личной коллекции. многие thnx

Ответ №2:

Это можно сделать с помощью CSS. Javascript не требуется.

 <div> example example example example example</div>
<style>
div{
    width: 200px;
    word-wrap: break-word;
}
</style>
  

Комментарии:

1. Они хотят получить содержимое отображаемого текста после применения CSS разрывов строк, они не хотят знать, как делать разрывы строк.

Ответ №3:

Попробуйте CSS

 div {
  width:200px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
  

Комментарии:

1. Они хотят получить содержимое отображаемого текста после применения CSS разрывов строк, они не хотят знать, как делать разрывы строк.