vue-typer
vue-typer copied to clipboard
Line-wrap strategy
Original request: #15.
The current line-wrapping behavior will break-by-letter (similar to word-break: break-all
). Change the default line-wrapping behavior to break-by-word (similar to overflow-wrap: break-word
and/or word-break: normal
), and add an option to get the break-by-letter behavior back.
Ideally, this would be done without JS, but since each letter is wrapped in a span for styling, that will make things a bit tricky. If this is really not doable through HTML/CSS alone, implement a line-wrap
prop. Possible values for now can be:
-
break-word
- new default behavior
- Edge case: when a word cannot fit on a single line, fallback to
break-all
- Behaves like a combination of
overflow-wrap: break-word
andword-break: break-all
-
break-all
- existing default behavior at the time of writing
- Behaves like
word-break: break-all
Finally, consider the fact that when a partially-typed word becomes long enough to break to the next line, the entire word will jump down to the next line. This happens because we style the letters-not-yet-typed with display: none
so that VueTyper can flow within text. Perhaps add another optional prop to style these letters as visibility: hidden
instead so they can take up space before they are typed, and therefore the words meant to be on the next line will already be there.
I have a temporary emergency solution in case someone needs it. This adds a span for each word and then adds a style for the line break:
<vue-typer id="typewriter" :@typed-char="onTypedChar"></vue-typer>
data() {
return {
countWord: 0,
}
},
methods: {
onTypedChar: function (typedChar, typedCharIndex) {
if (typedCharIndex == 0) {
document.getElementById('typewriter').firstChild.innerHTML = '';
}
var lessNodes = document.getElementById('typewriter').lastChild.childNodes;
if (typedChar == " " || lessNodes.length == 1) {
var finalNodes = document.getElementById('typewriter').firstChild;
var listNodes = finalNodes.childNodes;
var newNode = document.createElement('span');
var x = this.countWord;
var countNodes = listNodes.length;
while (x < countNodes) {
if (listNodes[this.countWord].innerHTML != " ")
newNode.insertAdjacentElement('beforeend', listNodes[this.countWord]);
else
this.countWord++;
// TODO: ADD LAST CHAR
x++;
}
newNode.className = 'nowrap';
finalNodes.insertAdjacentElement('beforeend', newNode);
this.countWord++;
}
},
}
span.nowrap{
white-space: nowrap;
}
@mcurilen I am not getting proper behavior when using your fix, its placing spans in the middle of words. An example string is "In which movie did Harry Potter compete in the Triwizard Tournament?"
the html looks like the following
<p data-v-819b4fe4="" class="text-base font-bold text-2xl">
<span data-v-c41bac74="" class="vue-typer" id="typewriter" data-v-819b4fe4="">
<span data-v-c41bac74="" class="left">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">I</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">n</span>
<span class="nowrap"></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">w</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">h</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">i</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">c</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">h</span>
<span class="nowrap"></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">m</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">o</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">v</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">i</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">e</span>
<span class="nowrap"></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">d</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">i</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">d</span>
<span class="nowrap"></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">H</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">a</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">r</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">r</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">y</span>
<span class="nowrap"></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">P</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">o</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">t</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">t</span>
<span class="nowrap">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">e</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">r</span></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span class="nowrap">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">c</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">o</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">m</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">p</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">e</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">t</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">e</span></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span class="nowrap">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">i</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">n</span></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span class="nowrap">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">t</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">h</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">e</span></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span class="nowrap">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">T</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">r</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">i</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">w</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">i</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">z</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">a</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">r</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">d</span></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed"> </span>
<span class="nowrap">
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">T</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">o</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">u</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">r</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">n</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">a</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">m</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">e</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">n</span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">t</span></span>
<span data-v-302772ec="" data-v-c41bac74="" class="char custom typed">?</span></span>
<span data-v-a16e0f02="" data-v-c41bac74="" class="caret custom vue-typer-caret-blink complete"></span>
<span data-v-c41bac74="" class="right"></span></span></p>
Edit: Fixed my own problem, in the first if statement add the line
if (typedCharIndex == 0) {
this.countWord = 0;
document.getElementById('typewriter').firstChild.innerHTML = '';
}
If you notice that the last char of a word is getting broken up, there are two solutions:
- Add a space at the end of your string
- Move the last char on a typed event (like below)
<vue-typer id="typewriter" :@typed-char="onTypedChar" @typed="onTyped"></vue-typer>
onTyped (typedString) {
const nodes = document.getElementById('typewriter').firstChild.childNodes
const nodeLength = nodes.length
nodes[nodeLength - 2].insertAdjacentElement('beforeend', nodes[nodeLength - 1])
}
I nearly gave up when none of the above workarounds seemed to work for me (though I most likely was doing something wrong).
What saved the day for me was \n in the string as data attribute, like here: https://github.com/cngu/vue-typer/issues/30
Is this being followed up? There hasn't been a whole lot of activity lately.
Also, if anyone knows a working workaround that I could use to make the text do this on smaller screens (as it looks fine on my bigger desktop, just not on smaller devices), could you let me know?
Thanks.
Hey has this issue been resolved? I really need the text not to break-by-letter on smaller devices, like this:
I dont know if anyone is still dealing with this issue, I have written a quick fix to the issue. Its not perfect but does the job.
You will run the text through a Method that will auto add in the line breaks automatically.
<div
style="
font-size: 30pt;
margin: auto;
color: white;
max-width: 600px;
"
ref="theRef"
>
<vue-typer
v-if="startTypers"
:text="[
formatText(
'TextHere',
'theRef',
22
),
]"
:repeat="0"
:shuffle="false"
initial-action="typing"
:pre-type-delay="70"
:type-delay="70"
:pre-erase-delay="2000"
:erase-delay="100"
erase-style="backspace"
:erase-on-complete="false"
caret-animation="blink"
></vue-typer>
</div>
mounted() {
setTimeout(() => {
this.startTypers = true;
}, 150);
}
The reason for the startTypers is because they will run the formatText method before the div has been rendered. Meaning you won't be able to get the clientWidth of the parent div.
formatText(text, ref, textSize = 22) {
let maxChars = Math.floor(this.$refs[ref].clientWidth / textSize);
let words = text.split(" ");
let breaked = "";
let currentCount = 0;
words.forEach((word) => {
currentCount += word.length;
currentCount += 1;
if (currentCount >= maxChars) {
currentCount = word.length;
breaked = `${breaked}\n${word} `;
} else {
breaked = `${breaked}${word} `;
}
});
return breaked;
},
The Parameters for formatText are the Text that you want to have the line breaks added in, The name of the ref, and the size of the span(Chars) that is rendered (22 was the default for the font and font-size I used in my use case, yours will vary)
Hopefully this helps
I added "\n" where line break is required. This can be a pallative solution.
Example:
<VueTyper
:text="'Example of very large text \n this is a newline.'"
/>
That is pretty much what I am doing on the answer I have except for your answer will only solve the issue for certain devices. If you have long text that will need the /n at different positions based on screen size, you will need to use my solution.
The vue-typer element injects as a span element with class "vue-typer" with a child span element with class "left". You can target that element and change the display to inline-block. Check the injected elements and classes with devtools
example:
.vue-typer .left { display: inline-block; }