JSFiddle - React, Tailwind, and code Playground

HTML

<p class="truncated">
  <streak>start</streak>
  <streak>1</streak>
  <streak>2</streak>
  <double>3</double>
  <double>4</double>
  <span>5</span>
  <span>6</span>
  <streak>7</streak>
  <streak>8</streak>
  <double>9</double>
  <double>10</double>
  <streak>end</streak>
</p>
<p id="before"></p>
How to cut
<input id="count" type="number" min="1" autofocus required />
<button id="go">Go</button>
<p id="after"></p>

CSS

p {
  margin: 0 0 5px 0;
  display: block;
  padding: 5px;
}
streak {
  color: green;
}
double {
  color: orange;
}
p span {
  color: grey;
}

JavaScript

function cutKeepingTags(elem, reqCount) {
  var grabText = '',
      missCount = reqCount;
  $(elem).contents().each(function() {
    switch (this.nodeType) {
      case Node.TEXT_NODE:
        // Get node text, limited to missCount.
        grabText += this.data.substr(0,missCount);
        missCount -= Math.min(this.data.length, missCount);
        break;
      case Node.ELEMENT_NODE:
        // Explore current child:
        var childPart = cutKeepingTags(this, missCount);
        grabText += childPart.text;
        missCount -= childPart.count;
        break;
    }
    if (missCount == 0) {
      // We got text enough, stop looping.
      return false;
    }
  });
  return {
    text:
      // Wrap text using current elem tag.
      elem.outerHTML.match(/^<[^>]+>/m)[0]
      + grabText
      + '</' + elem.localName + '>',
    count: reqCount - missCount
  };
}

var target = $('.truncated')[0];
$('#before')
.append('<pre>' + show(target.outerHTML) + '</pre>');
$('#go').click(function() {
  $('#after').empty();
  var cut = cutKeepingTags(target,$('#count').val());
  $('#after')
  .append(cut.text)
  .append('<pre>' + show(cut.text) + '</pre>');
});

function show(html) {
  return html.replace(/</g, '&lt;').replace(/\n/g,'¶\n').replace(/ /g,'·');
}