【问题标题】:How to iterate/loop over a text string that contains HTML tags while ignoring those HTML tags in Javascript?如何在忽略 Javascript 中的 HTML 标签的同时迭代/循环包含 HTML 标签的文本字符串?
【发布时间】:2022-01-12 15:35:10
【问题描述】:

我有一些动态复制文本,一个字符串,其中包含一些用于换行和样式的 HTML 标记。例如:“This is a sample <b>piece of text</b> string”。

我希望遍历文本,以便可以将每个字符包装在 <span> </span> 标签中,以便为每个字符设置动画。但是,我需要能够忽略 HTML 标记,例如上面的 <b></b> 标记,以便与这些标记关联的任何/所有中断或样式保持不变......?

我可以在文本字符串上运行一个简单、干净的 for of 循环,以便将所有内容包装在 span 标签中,然后将结果推送到我想要的 div,见下文:

textString = "This is a sample <b>piece of text</b> string."

for (let char of textString) {
    let span = document.createElement('span');
    span.textContent = char;
    let test = document.querySelector("#root");
    test.appendChild(span);
}

这会将字符串中的所有内容都包含在一个 span 标签中,但随后标签显然会显示在屏幕上 - 这不是我需要的!

我知道这个正则表达式:str.replace( /(&lt;([^&gt;]+)&gt;)/ig, ''); 将删除字符串中的所有 HTML 标签,但是,正如我所说,我需要标签实际上保留在原位以进行样式设置?

对此的任何帮助将不胜感激...????????

P.S.:HTML 标记必须保留/替换它们原来的位置并且我不能只是在之后设置样式的原因是因为我收到的文本字符串来自已在其中输入标记的提要,并且它们被用于创建输入到创意 HTML5 横幅广告中的部分文本的换行符和颜色样式。

【问题讨论】:

  • letterize.js?可能还有其他选择,这只是第一个发现。目前,您的问题看起来您还没有搜索现有的解决方案,这对任何人的帮助都没有太大的激励作用。如果您已经(重新)搜索过,请将结果添加到问题中,即使它们没有帮助。重要的是要知道哪些内容对您没有帮助以及原因。详情请见How to Ask
  • OP 请提供简化的示例代码,其中显示了最基本的输入字符串值以及转换后的预期结果。
  • @tao 我没有包括我研究过的所有内容,因为我只想解决问题的核心,我很抱歉。我看过 letterize.js 这很酷,但正如我在最初的问题中所说的那样。这是针对 HTML5 横幅广告的,Google 平台不允许我添加该外部库或将其安装到非常严格的原始文件中。我实际上将 GSAP 3 用于我的动画,并试图让 SplitText 为我完成这项工作,但遇到了同样的问题,它没有忽略标签并将它们留在原处!

标签: javascript arrays loops iterator


【解决方案1】:

这是一个辅助函数:

const letterize = el => el instanceof Text
  ? el.data
      .split('')
      .map(l => l === ' ' ? l : '<letter>' + l + '</letter>')
      .join('')
  : (
      el.innerHTML = ([...el.childNodes] || []).map(letterize).join(''),
      el.outerHTML
    );

在遍历 DOM 树时递归调用。

假设是:递归遍历 DOM 树最终将归结为 Text 节点实例(或空元素,如 &lt;br&gt;)。我还没有彻底测试过它(不确定它如何与&lt;script&gt;&lt;style&gt;&lt;iframe&gt; 标签,甚至整个文档一起工作,但这些都是边缘情况)。对于您的情况,这应该足够了。

如果处理文本节点,则返回所有包含字符的连接结果(空格除外 - 逐字返回),每个字符都包装在我们的标签包装器中。
如果不是 Text 节点,则在所有子节点上运行 letterize,将它们的连接结果分配给当前元素的 innerHTML,并返回其 outerHTML。 如果您还想换行空格字符,请从第一个条件中删除 l === ' ' ? l :
将包装器(上面的&lt;letter&gt;)更改为您想要的任何内容:例如:&lt;span&gt;

测试:

const doneClass = '2.1',
      tag = 'l',
      tagClass = '',
      letterize = (el) => el instanceof Text
  ? el.data
      .split('')
      .map(l => l === ' ' ? l : `<${tag}${tagClass ? ' class="' + tagClass + '"' : ''}>${l}</${tag}>`)
      .join('')
  : (!el.classList.contains(doneClass)
       ? el.innerHTML = (el.classList.add(doneClass), ([...el.childNodes] || []).map(letterize).join(''))
       : void 0,
      el.outerHTML
    );


[...document.querySelectorAll('.test')]
  .map(letterize);

// double parsing test:
[...document.querySelectorAll('.test')]
  .map(letterize);
l:hover {
  background-color: red;
  color: white;
  cursor: none;
}
body {
 font-family: monospace;
 font-size: 20px;
}
div > span { color: red; }
code {
  padding: 1rem;
  background-color: #272727;
  color: white;
  display: block;
  margin-bottom: 1rem;
  font-size: 14px;
}
<p class="test">This is a "<i>.test</i>"</p>
<code class="another test">And this is another one...</code>
<h1 class="test">I'll be parsed</h1>
<h4>I won't be parsed</h4>
<div style="border: 1px solid red;" class="test">
  I am a div. <span>And I am a <b>span</b>.</span>
</div>

上面测试中的功能略有不同。如果您需要这样的版本,它允许多次运行,而不必担心什么是字母和什么不是。

严重警告:上述内容(就像任何替换您的 DOM 元素一样 - 例如设置 innerHTMLouterHTML 值)将从解析的 DOM 中删除所有事件。如果保留事件侦听器是一项要求...这是不同级别的复杂性。
我不会在这里介绍它。


注意:它不必是页面 DOM 的一部分,您可以在内存中完成所有操作(但它确实使用 DOM API;假设您将在浏览器):

const letterize = el => el instanceof Text
  ? el.data
      .split('')
      .map(l => l === ' ' ? l : '<span class="letter">' + l + '</span>')
      .join('')
  : (
      el.innerHTML = ([...el.childNodes] || []).map(letterize).join(''),
      el.outerHTML
    );

const letterizeHTML = input => {
  const d = document.createElement('div');
  d.innerHTML = input;
  letterize(d);
  return d.innerHTML;
}

console.log(
  letterizeHTML(`<p class="test">This is a <b>test</b>.</p>
<code class="another test">And this is another one...</code>`)
);

这是一个动画测试(忍不住 :):

const initState = document.querySelector('.animation-container').innerHTML;

const letterize = el => el instanceof Text
  ? el.data
    .split('')
    .map(l => l === ' ' ? l : '<letter>' + l + '</letter>')
    .join('')
  : (
    el.innerHTML = ([...el.childNodes] || []).map(letterize).join(''),
    el.outerHTML
  );

const animate = () => {
  [...document.querySelectorAll('.test')].map(letterize);
  [...document.querySelectorAll('letter')].forEach((l, k) => {
    setTimeout(() => l.classList.add('hiccup'), 12 * k + 4.2e3);
    setTimeout(() => l.classList.add('on'), Math.random() * 2e3 + 10 * k)
  });
}

animate();

function replay() {
  document.querySelector('.animation-container').innerHTML = initState;
  requestAnimationFrame(animate);
}
* {
  box-sizing: border-box;
}

p {
  margin-bottom: 0;
}

.animation-container {
  perspective-origin: center;
  position: relative;
  min-height: 100vh;
  overflow: hidden;
  perspective: 0;
  font-family: sans-serif;
  padding: 2rem 2rem 10rem;
  font-family: monospace;
  font-size: 20px;
  overflow: hidden;
  max-width: 800px;
  margin: 0 auto;
  text-rendering: geometricPrecision;
  -webkit-font-smoothing: subpixel-antialiased;
}

letter {
  transform-style: preserve-3d;
  perspective-origin: center;
  display: inline-block;
  position: relative;
  opacity: .21;
  transform: perspective(240px) translate3d(20px, -20px, 240px);
  transition: transform 1.8s cubic-bezier(.5, 0, .3, 1), opacity .7s ease-out;
}

letter.on {
  opacity: .5;
  transform: perspective(240px) translate3d(0, 0, 0);
}

letter.hiccup {
  opacity: 1;
  animation: hiccup 8s cubic-bezier(.4, 0, .2, 1) -6s infinite;
}

div>span {
  color: red;
}

code {
  padding: 1rem;
  background-color: #272727;
  color: white;
  display: block;
  margin-bottom: 1rem;
  font-size: 14px;
}

@keyframes hiccup {
  0% {
    transform: perspective(240px) translate3d(0, 0, 0) scale(1);
  }
  96% {
    transform: perspective(240px) translate3d(0, 0, 0) scale(1);
  }
  98% {
    transform: perspective(240px) translate3d(0, 0, 0) scale(1.5);
  }
  100& {
    transform: perspective(240px) translate3d(0, 0, 0) scale(1);
  }
}
<div class="animation-container">
  <p class="test">This is a "<i>.test</i>"</p>
  <code class="another test">And this is another one...</code>
  <h1 class="test">I'll be parsed</h1>
  <h4>I won't be parsed</h4>
  <div style="border: 1px solid red; padding: 0 1rem;" class="test">
    I am a div. <span>And I am a <b>span</b>.</span>
    <p>Lorem ipsum dolor sit amet. The cat is sleeping. <br>Over.</p>
    <p>Sometimes a financial Bacardi Silver flies into a rage, but a line dancer always makes a pact with the Keystone light! When a jersey cow about some Heineken is familiar, a tipsy jersey cow makes a pact with the cantankerous Coors. <br><br> Hold my
      beer!</p>
  </div>
  <button onclick="replay()">Replay animation</button>
</div>

最后说明:虽然了解这些东西是如何工作的以及如何从头开始编写它们很有趣且很有用,但如果您对动画很认真,我建议您看看更认真的工具,提供了补间、链接、暂停和/或恢复动画的便捷方法,在不久的将来,这些动画需要很多更长的时间来编写代码。
查看有关 API changes in GSAP 3 的视频(以及它的功能,几乎无需代码!),或者在他们的展示页面上大开眼界。

附:我与 greensock 没有任何关系。我只是对动画充满热情,出于显而易见的原因,我已经使用了他们的库。


哦,为什么我喜欢这个挑战,我仍然认为你应该看看letterize.js。它已经存在了一段时间,所以它在很多场景中进行了测试,与我在这里写的不同。

【讨论】:

    【解决方案2】:

    这就是我修复它的方法:

    const isElement = node => node.nodeType === 1; // element
    const isTextNode = node => node.nodeType === 3; // text 
    const wrap = node => (node || '').split('').map(c => `<span>${c}</span>`).join('');
    
    function wrapNodes(nodeList) {
      nodeList = Array.from(nodeList).map(node => {
        if (isTextNode(node))  {
          return wrap(node.textContent);
        }
        if (isElement(node)) {
          node.innerHTML = wrapNodes(node.childNodes)
          return node.outerHTML;
        }
      });
      
      return nodeList.join('');
    }
    
    let textSample = "This is some copy text with a <b>Bold</b> sample to <em>test</em>."
    const div = document.createElement('div');
    div.innerHTML = textSample; 
    div.innerHTML = wrapNodes(div.childNodes);
    

    【讨论】:

      猜你喜欢
      • 2018-08-24
      • 2015-12-27
      • 1970-01-01
      • 2017-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多