Given the innerHTML of an element, I'm trying to wrap each word in a span, and if the word is already wrapped, wrap the element.
e.g. Such that Here is a paragraph <span class="red">which</span> <div id="typed-effect">might</div> have nested elements
Becomes
<span>Here</span> <span>is</span> <span>a</span> <span>paragraph</span> <span class="red">which</span> <span><div id="typed-effect">might</div></span> <span>have </span> <span>nested</span> <span>elements</span>
The components I have are:
function wrapElementText(elementSelector) {
const element = document.querySelector(elementSelector);
const rawHTML = element.innerHTML;
// Do magic to create list
element.innerHTML = "";
list.forEach((i, item) => {
if (!item.contains(`<span`)) {
element.innerHTML += `<span class="wrapped-text">${item}</span>`
}
}
};
((\<.*?\>)|((\s?).*?(\s))) but it's returning e.g. <div id="typed-effect"> and might</div> as seperate groups when it should be one group <div id="typed-effect">might</div>.Thanks so much in advance!
Ok so i came up with these solutions:
function wrapElementTextRegexp(elementSelector) {
const element = document.querySelector(elementSelector);
const chunks = element.innerHTML.match(/[a-zA-Z]+|(<[a-z][^>]+>.+<\/[^>]+>)/g);
element.innerHTML = "";
for(const chunk of chunks) {
element.innerHTML += !chunk.startsWith('<span')
? `<span class="wrapped-text">${chunk}</span>`
: chunk
}
}
Here, the regexp is searching for a whole text or for a html tag pattern(simplified), in case it finds something that is not a span it wraps it in a span, otherwise it just return the original span
function wrapElementTextNodes(elementSelector) {
const element = document.querySelector(elementSelector);
const parsedInnerHTML = [...element.childNodes]
.map(node => node instanceof HTMLElement
? node instanceof HTMLSpanElement
? node.outerHTML
: `<span class="wrapped-text">${node.outerHTML}</span>`
: node.textContent
.trim()
.split(/\s+/)
.map(word => `<span class="wrapped-text">${word}</span>`))
.flat()
.filter(chunk => !chunk.match(/><\//g))
.join('')
element.innerHTML = parsedInnerHTML;
}
Here we have access to the instances and we don't have to split the innerHTML since we're using childNodes, in the example i mapped the nodes by checking if the node was an actual HTMLElement and if yes if it was a HTMLSpanElement. After the first map i used a filter to remove from the array every empty node generated after the map(filtering out nodes like <span class="wrapped-text"></span>) and finally i join() the elements.
IMPORTANT NOTE
wrapping a <div> element inside a <p> it's a bad practice, javascript won't recognize any childNode after the div(included) so for example if you have
<p>
some
<div>bad</div>
example
</p>
the div and the node example will be omitted in element.innerHTML that, in this case, will return some. So make sure you correct your markup