Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
246 views
in Technique[技术] by (71.8m points)

javascript - Count number of characters in each line of the span element

Suppose I have a <span> tag with some text inside. Span node can also have child nodes(strong, em) tags. I can easily break the span into the wrapped lines by using getClientRects(). The problem with that however is that I won't be able to track the number of characters in each line (I need to) I tried putting each word inside of the <n> tag and comparing .top values of each line and word to calculate the total character count in one line. The solution stopped working once there were other nodes inside the main span. Since putting words into <n> removed the context of other child. The solution of getting all the child nodes and modifying them seems very ineffective.

Looking forward for any ideas.

Example span:

<span style="font-size:22pt;line-height:1;">
   <strong>
     Qui
     <em>
         <u>
            <s>
               squ
            </s>
         </u>
     </em>
     e ac justo pulvinar, efficitur odio nec, convallis ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Vestibulum ex tortor, pretium ac sapien sed, condimentum fringilla elit. Fusce aliquam ullamcorper magna eget dignissim. In porttitor nec magna non auctor. Nullam feugiat ultrices molestie. 
     </strong>
</span>

I want to get a number of chars in x, y, and z (See the image below)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The comment by espascarello really hits the nail on the head:

Basically people would either wrap every character in.a span and check the position of each character. Or they would make a function that basically adds characters one at a time and figure out when it wraps

So I think your initial approach is correct, you just need to modify it to handle nested content.

Some notes:

  • You will have to run this code whenever the page rerenders. Any change to window size, user zoom level, content, etc. could mess with the results.
  • You'll have to look at the white space logic. I ignored whitespace other than just single spaces (" "), which technically isn't correct.
  • I chose to put back the old content by storing it as a string. If you have any references to those nodes or event listeners attached, those will break.
  • The getBoundingClientRect can be off once your styling per inline tag differs.This can be corrected for by your grouping logic. (e.g. use Math.floor(s.getBoundingClientRec().top / LINE_HEIGHT))

In the end, I think this will only get you a rough approximation and never a 100% correct value ???♂?

const run = () => {
  const span = document.querySelector("#my-span");
  const old = span.innerHTML;
  
  // Cut up the content of the span in to 1-span-per-word
  // Watch out if you expect nested spans!
  cutUpTagsPerWord(span);

  // Get all the spans we created, group by Y-distance, and count
  // chars per group
  const lines =
    Array
      // Get all nested spans
      .from(span.querySelectorAll("span"))
      // Create key-value-pairs of y-position + content length
      .map(s => [ s.getBoundingClientRect().top, s.innerText.length ])
      // Group in to an object by y-pos, while summing the content length
      .reduce(
        (lines, [ line, l ]) => ({ ...lines, [line]: l + (lines[line] || 0) }),
        {}
      );

  // Put back our old content
  span.innerHTML = old;
  
  // Log the character counts
  console.log(Object.values(lines).join(", "));
}

document.querySelector("button").addEventListener("click", run);
run();

function cutUpTagsPerWord(node) {
  switch(node.nodeType) {
    // For text nodes, split by whitespace and 
    // put back 1-span-per-word
    case Node.TEXT_NODE:
      node.parentElement.replaceChild(
        Fragment(node
          .textContent
          .split(/s/)
          .filter(str => str.trim().length)
          .map(Span)
        ),
        node
      );
      break;
    case Node.ELEMENT_NODE:
      // For element nodes, process its contents
      Array
        .from(node.childNodes) // Important to create an array, because we
                               // modify the NodeList on the go
        .forEach(cutUpTagsPerWord);
      break;
  }
}


function Fragment(nodes) {
  const frag = document.createDocumentFragment();
  nodes.forEach(
    n => frag.appendChild(n)
  )
  
  return frag;
}

function Span(txt) {
  const span = document.createElement("span");
  span.innerText = txt +" ";
  return span;
}
<div>
  <button>analyze</button>
</div>

<span id="my-span" style="font-size:22pt;line-height:1;">
  <strong>
     Qui
     <em>
         <u>
            <s>
               squ
            </s>
         </u>
     </em>
     e ac justo pulvinar, efficitur odio nec, convallis ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Vestibulum ex tortor, pretium ac sapien sed, condimentum fringilla elit. Fusce aliquam ullamcorper magna eget dignissim. In porttitor nec magna non auctor. Nullam feugiat ultrices molestie. 
     </strong>
</span>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...