Perform text animation by splitting text, but retain line breaks - javascript

I have created a function that maps over a sentence (provided by a CMS) and adds containers to each word. Splitting each word at spaces.
Now, some words will require line breaks, but my function is wiping them out, and I am not quite sure how I am going to retain line breaks in this case.
The demo below displays the issue I am having. The second one, although originally having a line break, has it removed when the function runs.
The breaks are coming from WP WYSIWYG, using a return. The field is using ACF and using the 'Automatically add ' option for new lines. Dev tools interprets/displays the original output as
<h1>This Is<br>Line Broken</h1>
Any ideas or advice of how to retain the breaks will be appreciated. For the purpose of my animations, each word will require the markup in the return value of $reformatedText
let $textAreaContent;
let $splitText;
let $reformatedText;
let $delayTime;
let $delayValue;
let $textAreas = document.querySelectorAll('.stagger-text');
function setDelayValue(curCount) {
$delayTime = 0.075;
return parseFloat(curCount * $delayTime).toFixed(3);
}
function createStaggerContainers($textArea) {
$textAreaContent = $textArea;
$splitText = $textAreaContent.innerText.split(/\s|<br>/g);
$reformatedText = $splitText.map((item, i, arr) => {
$delayValue = setDelayValue(i);
return `<span class="stagger__container">
<span class="stagger__text" style="animation-delay: ${$delayValue}s">${arr.length - 1 === i ? item : `${item} `}</span>
</span>`;
}).join('');
$textAreaContent.innerText = '';
$textAreaContent.innerHTML = $reformatedText;
}
if ($textAreas && $textAreas.length) {
$textAreas.forEach(($textArea) => {
createStaggerContainers($textArea);
});
}
<div class="stagger-text">Some text here</div>
<div class="stagger-text">This has line<br> breaks</div>

Related

How to get the current row of list of strings with map()

I have the following code:
{words.map((word: string, index) => (
<Span key={index}>
<Span>
{word}
</Span>
</Span>
))}
This is giving me number of lines of different words in my browser. What I would like to do is shown in the picture:
If there are more then three rows of words I want to show only first three rows of words and add "..." as a sign that there are more words in the list that are not shown.
Does anyone have idea how I can do that? How to find on which row of words I am?
If you know how many characters fit per line, you could do a calculation based off of that. For clarification, though: do you just want to show an ellipsis at the end, or is that supposed to be a clickable element to expand the container to show the entire list? If it's the more basic part, you could do it all via JS or use CSS pseudo-content.
Example JS:
const container = document.querySelector('.words-list'); // whatever contains the list of words
const wordsStr = words.join(', ');
const lineChars = 20;
const linesToShow = 3;
if (wordsStr.length > lineChars * linesToShow) {
let wordsSub = wordsStr.substr(0, lineChars * linesToShow - 3) + '…';
container.innerText = wordsSub;
}
For the CSS portion, once you've added a class via JS, you can use text-overflow: ellipsis or ::after { content: '\2026' }.

What is the aspect of Javascript and DOM scoping and assignment rules which prevent the code from working on the second block of html?

The html code is below is exported form orgmode and the Javascript function is meant to transform the <pre class="src src-emacs-lisp"> block to <pre><code class="lisp"> for it to be syntax-highlighted by highlightjs.
For some Javascript quirk I don't understand the function doesn't work on the second block. I thought it was the syntax of the html code in it, but when I rearrange the blocks, the fault always happens on the second. There must be something about the scoping rules of Javascript that I don't understand. The code to locate the blocks works fine and if I print them out without processing it works they all show up. It is when I apply the transformations that things go wrong. What feature of Javascript am I not comprehending?
The code which just prints them out works fine. They are all detected.
document.addEventListener('DOMContentLoaded', (event) => {
var lispBlocks = document.getElementsByClassName('src src-emacs-lisp');
alert(lispBlocks.length);
for (i=0;i<lispBlocks.length;i++){
var iHtml = lispBlocks[i].outerHTML;
//alert(lispBlocks[i].outerHTML);
alert(iHtml);
};
});
With the code that transforms them, things go south
document.addEventListener('DOMContentLoaded', (event) => {
var lispBlocks = document.getElementsByClassName('src src-emacs-lisp');
alert(lispBlocks.length);
var iHtml;
var lBlockText;
for (i=0;i<lispBlocks.length;i++){
//lispBlocks[i].className = '';
iHtml = lispBlocks[i].innerHTML;
alert(lispBlocks[i].innerHTML);
alert(lispBlocks[i].outerHTML);
lBlockText = '<pre><code class="lisp">' + iHtml + '</code></pre>';
alert(lBlockText);
lispBlocks[i].outerHTML = lBlockText;
//alert(lispBlocks[i].outerHTML);
//hljs.highlightBlock(lispBlocks[i]);
};
});
<!-- language: lang-html -->
<div class="org-src-container">
<pre class="src src-emacs-lisp">;; after splitting a frame automatically, switch to the new window (unless we
;; were in the minibuffer)
(setq split-window-preferred-function 'my/split-window-func)
(defun my/split-window-func (&optional window)
(let ((new-window (split-window-sensibly window)))
(if (not (active-minibuffer-window))
(select-window new-window))))
</pre>
</div>
<div class="org-src-container">
<pre class="src src-emacs-lisp"> (defun split-window--select-window (orig-func &rest args)
"Switch to the other window after a `split-window'"
(let ((cur-window (selected-window))
(new-window (apply orig-func args)))
(when (equal (window-buffer cur-window) (window-buffer new-window))
(select-window new-window))
new-window))
(advice-add 'split-window :around #'split-window--select-window)
</pre>
</div>
<div class="org-src-container">
<pre class="src src-emacs-lisp">;; settings for default frames
(add-to-list 'default-frame-alist '(font . FONT ))
;;or
;; set-face-atribute, ='default nil= for all existing frames and new frames
;; ='default t= for new frames only
;; function sets a number of attributes besides :font see docs
(set-face-attribute 'default nil :font FONT )
;;set frame font
(set-frame-font FONT nil t)
</pre>
</div>
The problem is that getElementsByClassName returns a live list
Therefore, when changing the outerHTML you change the list, because you've changed the class of the element that is in the live list
In the example, you'd have a list of elements, [a, b, c], and i == 0
You change a such that it disappears from the list, now the list is [b, c] but i increments to 1, so the next iteration changes c, skipping b
use
var lispBlocks = document.querySelectorAll('.src.src-emacs-lisp');
instead
Alternatively you could use a while loop
while(lispBlocks.length) {
iHtml = lispBlocks[0].innerHTML; // note, the hard coded `0` here
// etc
}

How do you put a line break in a React string?

I have a text file that I'm reading as a string, and then inserting into the DOM like so:
//the state gets set with the contents of a text file that has proper indentation
state = {
parsedText: null
}
//in the render function, this is how the text comes in
<p>{this.state.parsedText}</p>
When I iterate through the character codes, I can see that the 'enter' codes (i.e., 13) seem to work when I console.log the file (the indentation looks correct). Unfortunately, those character codes don't translate to HTML - the entire string just comes out as one solid chunk of text. So, what I'm trying to do is insert either <br> tags or \n characters or in order to make the breaks work in the HTML.
Here's what that looks like:
rawFile.onreadystatechange = () => {
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
let allText = rawFile.responseText;
for (let i = 0; i < allText.length; i++) {
let uni = allText.charCodeAt(i)
if (uni === 13) {
console.log('enter!!')
allText = allText.substring(0, i) + "\n" + allText.substring(i + 1, allText.length - 1)
}
}
console.log("allText: ", allText);
console.log(typeof allText)
this.setState({
parsedText: allText
});
}
}
};
rawFile.send(null);
}
The \n insertions show up in the text string but are seemingly just ignored in the DOM. Is there a way to make this work? I've tried dangerouslySetInnerHTML and that does nothing.
Any ideas on how to make this work?
Had the same issue. just solved it.
All you need to do is to save the string as array. like so:
let text = "this is text without break down \n this won't work but will be one line"
let text = ["this is text with breakdown",
<br/>,
"this is the second line"];
Hope that helps
If possible, avoid using dangerouslySetInnerHTML.
An alternate solution could be splitting your text into an array then rendering a new div for each "newline".
Of course, you would need to come up with a better key for the text, as there could be duplicate text - though that is a separate question in and of itself.
class Hello extends React.Component {
constructor(props) {
super(props)
this.state = {
parsedText: [
"Some text",
"More text",
"Keep on texting"
]
}
}
render() {
return <div>{this.state.parsedText.map(text => <div key={text}>{text}</div>)}</div>;
}
}
ReactDOM.render(
<Hello/>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
So you need to dangerouslySetInnerHTML, and as was pointed by FrV, the answer is to insert a <br /> ... turns out I was missing the closing "/". Whoops. Hope this helps somebody running into a similar problem.
If you want to print a <br/> tag in react I wanted to point out that a neat workaround is to use the <Trans> component from i18next.
If you have a variable like this:
const customInviteMessageByCreator = "DEMO APPOINTMENT<br/>This room is to test your video room.";
You can write your variable wrapped in a Trans component like so:
<Trans>{customInviteMessageByCreator}</Trans>
And then it replaces the whitelisted html elements that you can configure in your i18n.js file like this:
transKeepBasicHtmlNodesFor: ["br", "strong", "b", "i"],
I didn't read that this is possible but I was curious if it works because i18next also translates our linebreaks in the translation.json and indeed it does.
I think it is a less hacky and more reliable method than what I've found so far like doing a map on <br> where there could be a / before the >. Plus you can whitelist more than just linebreaks. And of course a smarter choice over dangerouslySetInnerHtml.

Why this space crashes the javascript function?

I have this code, which works perfectly to replace stuff in some divs (the example is actually at http://wiki-es.guildwars2.com/wiki/Reemplazador_Wiki, click on the red "AQUI" to see a live demonstration).
$("#SaeReplacerButton").dblclick(function () {
document.getElementById("SaeReplacerResult").innerHTML = "Error";
var TextoInicial = document.getElementById("SaeReplacerInput").innerHTML;
var Reemplazos = {
quemadura: "{{quemadura}}",
incapacitar: "{{incapacitar}}",
inmovilizar: "{{inmovilizar}}",
égida: "{{égida}}",
furia: "{{furia}}",
poder: "{{poder}}",
protección: "{{protección}}",
regeneración: "{{regeneración}}",
represalia: "{{represalia}}",
celeridad: "{{celeridad}}",
vigor: "{{vigor}}",
Primordus: "[[Primordus]]",
Kralkatorrik: "[[Kralkatorrik]]",
Guerrero: "{{guerrero}} [[Guerrero]]",
Guardián: "{{guardián}} [[Guardián]]",
Retornado: "{{retornado}}[[Retornado]]",
Elementalista: "{{elementalista}} [[Elementalista]]",
Guardabosques: "{{guardabosques}} [[Guardabosques]]",
Ingeniero: "{{ingeniero}} [[Ingeniero]]",
Nigromante: "{{nigromante}} [[Nigromante]]",
Ladrón: "{{ladrón}} [[Ladrón]]",
Hipnotizador: "{{hipnotizador}} [[Hipnotizador]]"
}
for (key in Reemplazos) {
var TextoInicial = TextoInicial.replace(key, Reemplazos[key]);
};
document.getElementById("SaeReplacerResult").innerHTML = TextoInicial;
});
However the problem starts if I change:
Retornado:"{{retornado}}[[Retornado]]",
for this:
Retornado:"{{retornado}} [[Retornado]]",
As you can see, I just added an space, which shouldn't matter at all because all the other arrays replacement works. However when I add that space to the Retornado one, the entire function stops working.
The question is: Why?

empty span elements within div block

I'd like to append updated javascript data to 2 span tags, rescued and rescued2, when it's available. Since I'm appending new data, I first need to empty out the tags first so they don't keep reappending data...
function updateHUD() {
$('#bottomDisplay').empty();
$('#rescued').append("Total Animals Rescued: " + rescuedTotal_Array.length);
$('#rescued2').append("Total Animals Rescued lol: " + rescuedTotal_Array.length);
}
<div id="bottomDisplay">
<ul>
<li>Total Animals Rescued: <span id="rescued"></span></li>
<li>Total Animals Rescued2: <span id="rescued2"></span></li>
</ul>
</div>
I'd like to do the clear that way so I don't need to individually clear each span... but instead clear the entire div block then start over. Instead, when I clear the div block, it just erases all spans inside.
Why is this not acceptable?
On another note, I suppose I can just skip the clearing and appending, and just set the text...
$('#rescued').text(rescuedTotal_Array.length);
$('#rescued2').text(rescuedTotal_Array.length);
.empty gets rid of everything inside the selected element. If you want to empty both spans in one go, you'd need to do:
$('#bottomDisplay').find('span').empty();
For reference, here's what .empty looks like:
empty: function() {
var elem,
i = 0;
for ( ; (elem = this[i]) != null; i++ ) {
if ( elem.nodeType === 1 ) {
// Prevent memory leaks
jQuery.cleanData( getAll( elem, false ) );
// Remove any remaining nodes
elem.textContent = "";
}
}
return this;
}
elem.textContent = ""; is what is getting rid of everything inside.
I'm a little confused by the question but I think I understand. Correct me if I'm not understanding you fully.
You are looking for a single line to empty both spans. However, you want to leave the rest of the HTML inside the div intact and you don't want to have to target each individual span. The following line would work:
$('#bottomDisplay span').empty();

Categories

Resources