开发者

Show text letter by letter

What is the most elegant way of showing an html text letter by letter (like videogame captions) using CSS and JavaScript?

While I'm sure that his can be solved using a brute-force approach (say, splitting the characters and print them one by one using jQuery.append()), I'm hoping there's some CSS3 (pseud开发者_开发知识库o-elements?) or JQuery magic to do this more elegantly.

Extra points if the solution considers inner HTML content.


HTML

<div id="msg"/>

Javascript

var showText = function (target, message, index, interval) {   
  if (index < message.length) {
    $(target).append(message[index++]);
    setTimeout(function () { showText(target, message, index, interval); }, interval);
  }
}

Call with:

$(function () {
  showText("#msg", "Hello, World!", 0, 500);   
});


If a smooth reveal is reasonable then I think this should be pretty straightforward. Untested, but this is how I imagine it would work

html

<div id="text"><span>The intergalactic space agency</span></div>

css

div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden;  }

jQuery

var spanWidth = $('#test span').width();
$('#text').animate( { width: spanWidth }, 1000 );

Okay, I couldn't resist and made a fiddle. One little code error that I fixed. Looks good to me though!

http://jsfiddle.net/mrtsherman/6qQrN/1/


100% vanilla javascript, strict mode, unobtrusive html,

function printLetterByLetter(destination, message, speed){
    var i = 0;
    var interval = setInterval(function(){
        document.getElementById(destination).innerHTML += message.charAt(i);
        i++;
        if (i > message.length){
            clearInterval(interval);
        }
    }, speed);
}

printLetterByLetter("someElement", "Hello world, bonjour le monde.", 100);


You really should just append, or show/hide.

However, if for some odd reason you don't want to alter your text, you can use this overly-complicated-for-no-good-reason piece of code:

HTML:

<p>I'm moving slowly...<span class="cover"></span></p>

CSS:

p {
    font-family: monospace;
    float: left;
    padding: 0;
    position: relative;
}
.cover {
    height: 100%;
    width: 100%;
    background: #fff;
    position: absolute;
    top: 0;
    right: 0;
}

jQuery:

var $p = $('p'),
    $cover = $('.cover'),
    width = $p.width(),
    decrement = width / $p.text().length;

function addChar()
{        
    $cover.css('width', '-=' + decrement);

    if ( parseInt( $cover.css('width') ) < width )
    {
        setTimeout(addChar, 300);
    }
}

addChar();

And finally, here's the fiddle: http://jsfiddle.net/dDGVH/236/

But, seriously, don't use this...


Make your code more elegant by preparing promises for each iteration, then execute them as second step where you can inject DOM logic.

const message = 'Solution using Promises';

const typingPromises = (message, timeout) =>
  [...message].map(
    (ch, i) =>
      new Promise(resolve => {
        setTimeout(() => {
          resolve(message.substring(0, i + 1));
        }, timeout * i);
      })
  );

typingPromises(message, 140).forEach(promise => {
  promise.then(portion => {
    document.querySelector('p').innerHTML = portion;
  });
});
<div ><p></p></div>


This is the React version using hooks.

import React, { useState, useEffect } from "react";
import "./styles.css";

const App = ({ speed, msg }) => {
  const Typer = ({ speed = 250, children = " Introduce your text" }) => 
{
  const [idx, setidx] = useState(0);
  useEffect(() => {
    const timer = window.setInterval(() => setidx((v) => v + 1), speed);
    return () => window.clearInterval(timer);
  });

  return <div>{children.substr(0, idx)}</div>;
};
return (
  <div>
    <Typer speed={speed} children={msg}></Typer>
  </div>
 );
};

export default App;

and the main index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";

const message2 = "This is some text to get typed on time";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App speed={100} msg={message2} />
  </StrictMode>,
  rootElement
);

Have a look at code sandbox


When I did this I ran into the problem of a word jumping from the end of one line to the begging of the next as it the letters appeared to get around this I used to side by side spans, one of which the text was transparent, the other visible and simply moved letters one by one from the invisible span to the visible. Here's a fiddle.

HTML

<div class='wrapper'>
  <span class='visible'></span><span class='invisible'></span>
</div>

CSS

.visible {
  color: black;
} 

.invisible {
  color: transparent;
}

JS

var text = "Whatever you want your text to be here",
    soFar = "";

var visible = document.querySelector(".visible"),
    invisible = document.querySelector(".invisible");

invisible.innerHTML = text;
var t = setInterval(function(){
  soFar += text.substr(0, 1),
  text = text.substr(1);

  visible.innerHTML = soFar;
  invisible.innerHTML = text;

  if (text.length === 0) clearInterval(t);
}, 100)


I made a tiny jquery plugin for that. First you need to make sure that the text will be visible if javascript is disabled, and if not, redisplay the text letter by letter.

$.fn.retype = function(delay) {
    var el = this,
        t = el.text(),
        c = t.split(''),
        l = c.length,
        i = 0;
    delay = delay || 100;
    el.empty();
    var interval = setInterval(function(){
        if(i < l) el.text(el.text() + c[i++]); 
        else clearInterval(interval);
    }, delay);
};

Usage will be just as easy as this:

$('h1').retype();


This is based on armen.shimoon's:

var showText = function (target, message, index, interval) {    
    if (index <= message.length && $(target).is(':visible')) { 
        $(target).html(message.substr(0, index++)); 
        setTimeout(function () { showText(target, message, index, interval); }, interval); 
    } 
}

message[index++] wasn't working in my jquery webpage - I had to change it to substr. Also my original text uses HTML and the text that is typed uses HTML formatting (br, b, etc). I've also got the function stopping if the target has been hidden.


Vanilla JavaScript version of @armen.shimoon's answer:

document.addEventListener('DOMContentLoaded', function() {

    showText("#msg", "Hello, World!", 0, 100);

});

let showText = function (target, message, index, interval) {
    if (index < message.length) {
        document.querySelector(target).innerHTML =
            document.querySelector(target).innerHTML + message[index++];
        setTimeout(function () { showText(target, message, index, interval); }, interval);
    }
}
<div id="msg"></div> 
<span id="text_target"></span>


You need to wrap each letter in span tag, because anonymous html elements cannot be styled. Then reveal one span at a time. This avoids some innerText / innerHTML issues (no DOM reflow?) but can be overkill in your case.


Vanillla

(function () {

var showText = function(target, msg, index, interval){

  var el = document.getElementById(target);

  if(index < msg.length){
    el.innerHTML = el.innerHTML + msg.charAt(index);
    index = index + 1;
    setTimeout(function(){
      showText(target,msg,index,interval);
    },interval);
  }

};


showText("id", "Hello, World!", 0, 50);   

})();

you could improve on this code by changing it so you only get the el one time due to the fact that it takes a bit of resources to modify the DOM.


there is a good answer how to do it here: this is a way that you can manipulate each letter with any .animate() property available, not with hacks like covering the text with s etc.


I was trying to solve the same problem and I came up with this solution that seems to work.

HTML

<div id='target'></div>

jQuery

$(function() {
  var message = 'Hello world';
  var index = 0;

  function displayLetter() {
    if (index < message.length) {
      $('#target').append(message[index++]);
    }
    else{
      clearInterval(repeat);
    }
  }
  var repeat = setInterval(displayLetter, 100);
});


There are a couple libraries for this now. There are TypeItJs and TypedJS both of them allow for html to be in the thing that you are typing out as well as numerous other methods that assist in animating Typewriter effects.


The answer I will type is the answer Show text letter by letter , but I made some changes to it

HTML

<h1>
    <span id="msg"></span>
</h1>

Javacript

var showText = function (target, message, index, interval) {    
  if (index < message.length) { 
    //$(target).append(message[index++]);
    $(target).text($(target).text() + message[index++]);
    setTimeout(function () { showText(target, message, index, interval); }, interval); 
  } 
}

$(function () { 
  showText("#msg", "Hello, World!", 0, 80);    
  //showText("#msg", "Hello, World!", 0, 500);
}); 

CSS

#msg {
    color: #6d67c6;
    font-family: sans-serif;
    font-size: 30px;
    font-weight: bold;
}


function textRoll(message, interval){
   setTimeout(() => {
         para.innerHTML += message.charAt(0);
         return textRoll(message.slice(1))
   }, interval)
}


try this with jquery

var spanWidth = $('#text span').width();
$('#text').animate( { width: spanWidth }, 3000 );
div#text { width: 0px; height: 2em; white-space: nowrap; overflow: hidden;  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="text"><span>Hello World !</span></div>

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜