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 >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 >which</span> <span><div id="typed-effect">might</div></span> <span>have </span> <span>nested</span> <span>elements</span>
The components I have are:
- Extract the innerHTML and wrap in spans:
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 >${item}</span>`
}
}
};
- My best attempt is this
((\<.*?\>)|((\s?).*?(\s)))
but it's returning e.g.<div id="typed-effect">
andmight</div>
as seperate groups when it should be one group<div id="typed-effect">might</div>
.
Thanks so much in advance!
CodePudding user response:
Ok so i came up with these solutions:
- the regex approach(not recommended but what you asked for):
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 >${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
- the childNodes approach(imo better):
function wrapElementTextNodes(elementSelector) {
const element = document.querySelector(elementSelector);
const parsedInnerHTML = [...element.childNodes]
.map(node => node instanceof HTMLElement
? node instanceof HTMLSpanElement
? node.outerHTML
: `<span >${node.outerHTML}</span>`
: node.textContent
.trim()
.split(/\s /)
.map(word => `<span >${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 map
ped 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 ></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