vue-typer icon indicating copy to clipboard operation
vue-typer copied to clipboard

Line-wrap strategy

Open cngu opened this issue 7 years ago • 10 comments

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 and word-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.

cngu avatar Sep 26 '17 02:09 cngu

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;
}

curilen avatar Mar 24 '18 19:03 curilen

@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 = '';
}

tylergets avatar Apr 01 '18 02:04 tylergets

If you notice that the last char of a word is getting broken up, there are two solutions:

  1. Add a space at the end of your string
  2. 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])
    }

k----n avatar Sep 04 '19 15:09 k----n

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

L-K-Mist avatar Feb 19 '20 21:02 L-K-Mist

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.

htbrown avatar Jun 08 '20 19:06 htbrown

Hey has this issue been resolved? I really need the text not to break-by-letter on smaller devices, like this:

Screenshot 2020-09-23 at 11 03 32

dalylucia avatar Sep 23 '20 09:09 dalylucia

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

Declan-Watts avatar Feb 24 '21 00:02 Declan-Watts

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.'"
/>

JoedsonGabriel avatar Aug 13 '21 19:08 JoedsonGabriel

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.

Declan-Watts avatar Aug 13 '21 21:08 Declan-Watts

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; }

jthk1106 avatar Jan 28 '22 02:01 jthk1106