简体   繁体   中英

HTML5 Canvas typewriter effect with word wrapping?

I'm trying to get an animated typewriter effect on a HTML5 Canvas but I'm really struggling with Word Wrapping.

Here's my Shapes.js in a Gist: https://gist.github.com/Jamesking56/0d7df54473085b3c5394

In there, I've created a Text object which has lots of methods. One of which is called typeText() .

typeText() basically starts off the typewriting effect but it keeps on falling off the edge and I'm really struggling to find a way of fixing word wrapping.

Can anybody guide me on whats the best way to do this?

A solution I have used is roughly:

var maxWidth = 250;
var text = 'lorem ipsum dolor et sit amet...';

var words = text.split(' ');
var line = [words[0]]; //begin with a single word

for(var i=1; i<words.length; i++){
    while(ctx.measureText(line.join(' ')) < maxWidth && i<words.length-1){
        line.push(words[i++]);
    }
    if(i < words.length-1) { //Loop ended because line became too wide
        line.pop(); //Remove last word
        i--; //Take one step back
    }
    //Ouput the line
}

Since there seems to be no way to measure the height of the output text, you need to manually offset each line you output by some hardcoded line height.

I couldn't figure out how your Text constructor works. So I wrote an independent function (not belonging to any object). All you need to pass to this function is a string, the X and Y position, the line height and padding. It assumes the canvas to be assigned to a variable canvas and the 2d context to be assigned to variable ctx .

var canvas, ctx;
function typeOut(str, startX, startY, lineHeight, padding) {
    var cursorX = startX || 0;
    var cursorY = startY || 0;
    var lineHeight = lineHeight || 32;
    padding = padding || 10;
    var i = 0;
    $_inter = setInterval(function() {
        var w = ctx.measureText(str.charAt(i)).width;
        if(cursorX + w >= canvas.width - padding) {
            cursorX = startX;
            cursorY += lineHeight;
        }
        ctx.fillText(str.charAt(i), cursorX, cursorY);
        i++;
        cursorX += w;
        if(i === str.length) {
            clearInterval($_inter);
        }
    }, 75);
}

Check out the demo here .

Tips -

I was going through your code and found something link this-

function Rectangle(x,y,width,height,colour) {
    //properties
    this.draw = function() { //Don't assigns object methods through constructors
        ctx.fillStyle = this.colour;
        ctx.fillRect(this.x, this.y, this.width, this.height);
    };
}

You shouldn't add methods to an object through the constructor as it creates the method every time the object is instantiated. Rather add methods to object's prototype, this way, they will be created only once and will be shared by all the instances. Like-

function Rectangle(x,y,width,height,colour) {
    //properties
}
Rectangle.prototype.draw = function() {
    ctx.fillStyle = this.colour;
    ctx.fillRect(this.x, this.y, this.width, this.height);
}

Also, I found that you are creating extra variables to point to some objects. Like -

var that = this;
var fadeIn = setInterval(function() {
    //code
    that.draw();
    //code
}, time);

You shouldn't create an extra reference to the Text object. Use bind method so that this will point to the Text object. Like-

var fadeIn = setInterval(function() {
    //code
    this.draw(); // <-- Check this it is `this.draw` no need for `that.draw`
    //code
}.bind(this), time);   //bind the object

Read more about bind here .

Hope that helps.

PS: 75 millisecond for each character, that will be holy 800 characters per minute !!!


Update - If you want scalable graphics you should consider SVG . Canvas is raster-based, whereas SVG is vector-based. Which means SVG can easily be resized whereas when you resize a canvas the content of the canvas will start to pixelate and look fuzzy. Read more .

To resize the content of a canvas you need to repaint the whole canvas. Whenever something is drawn on a canvas, the browser draws and forgets about it. So if you want to change the size/position of the object you'll need to clear the canvas completely and redraw the object. In your case, you'll need to change the ctx.font according to the canvas's size and then update the canvas, which is going to be a really tedious task.

<canvas> is not designed for text handling. It is much, much, more easier doing this by overlaying transparent DOM elements on the top of your <canvas> . This, naturally, assumes what you don't need to post-process text pixels in <canvas> . Otherwise you need to re-implement the whole text handling stuff in Javascript and that's kind of reinventing the wheel, because browsers can simply do it for us.

Here are more information how to perform it using CSS position: absolute and position: relative .

Allowing the user to type text in a HTML5 Canvas game

I think you should have a DOM element which has all text with transparent colour, each letter wrapped with a <span> . Then you just start making those <span> s visible by adjusting opacity. This way the letter positions would not change.

Personally, I always use bitmap fonts when I need text in my canvas games. It has a couple of advantages:

  1. FillText on canvas is slow. Bitmap fonts are much faster.
  2. You know the width of each letter making wrapping and text alignment much easier.

You still get the benefit of selecting font color by using an cases compositing operations, and font size by dynamically adjusting the draw width height.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM