2

How would I animate individual characters of text on a page in HTML5. Like this example in flash. http://www.greensock.com/splittextfield/

7
  • Retagged. Question doesn't seem to be about flash. Why are you limiting yourself to HTML5? Commented May 14, 2010 at 22:06
  • 1
    @Eric: Because that's what all the cool kids are talking about lately, of course. Commented May 14, 2010 at 22:07
  • You don't. CSS and javascript does this, not HTML. Commented May 15, 2010 at 0:10
  • I am making an html and html5 version of an application. I have the code in AS3, this versions would be specifically for iPad/iPhone and the html version to degrade gracefully. Commented May 15, 2010 at 12:40
  • You'll be maintaining to different versions of the same thing? Why, if I may ask? I think the HTML 5 version would be sufficient for most systems. Commented May 15, 2010 at 16:10

1 Answer 1

3

You'd have to wrap each character in a <span> then move that span using CSS position/top/left.

You couldn't completely reproduce what the Flash example did, because that example uses a blur effect. CSS can't do that; SVG could, and IE's non-standard filter could, but that would mean a total code branch.

You could change the size of each letter by setting the font-size, but to so the kind of shear/rotate effects the example does you'd have to use a CSS transform. This isn't standardised yet and there are holes in browser support.

Here's a proof-of-concept I just hacked up mainly for Firefox (sorry for the code length):

<p id="effect">I've got a lovely bunch of coconuts.</p>
<button id="animate">Animate</button>


// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}

// Lightweight class/instance system
//
Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw 'Constructor function requires new operator';
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    if (this!==Object) {
        Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
        Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    }
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

// Abstract base for animated linear sliding switch between 0 and 1
//
var Animation= Object.makeSubclass();
Animation.prototype._init= function(period, initial) {
    this.period= period;
    this.interval= null;
    this.aim= initial || 0;
    this.t= 0;
};
Animation.prototype.set= function(aim) {
    if (aim===this.aim)
        return;
    this.aim= aim;
    var now= new Date().getTime();
    if (this.interval===null) {
        this.t= now;
        this.interval= window.setInterval(this.update.bind(this), 32);
    } else {
        this.t= now-this.t-this.period+now
        this.update();
    }
};
Animation.prototype.toggle= function() {
    this.set(this.aim===0? 1 : 0);
};
Animation.prototype.update= function() {
    var now= new Date().getTime();
    var x= Math.min((now-this.t)/this.period, 1);
    this.show(this.aim===0? 1-x : x);
    if (x===1) {
        window.clearInterval(this.interval);
        this.interval= null;
    }
};
Animation.prototype.show= function(d) {};

// Animation that spins each character in a text node apart
//
var ExplodeAnimation= Animation.makeSubclass();
ExplodeAnimation.prototype._init= function(node, period) {
    Animation.prototype._init.call(this, period, 0);
    this.spans= [];

    // Wrap each character in a <span>
    //
    for (var i= 0; i<node.data.length; i++) {
        var span= document.createElement('span');
        span.style.position= 'relative';
        span.appendChild(document.createTextNode(node.data.charAt(i)));
        node.parentNode.insertBefore(span, node);
        this.spans.push(span);
    }

    // Make up random positions and speeds for each character.
    // Possibly this should be re-randomised on each toggle?
    //
    this.randomness= [];
    for (var i= this.spans.length; i-->0;)
        this.randomness.push({
            dx: Math.random()*200-100, dy: Math.random()*200-150,
            sx: Math.random()*1.5, sy: Math.random()*1.5,
            dr: Math.random()*240-120, og: Math.random()+0.5
        });

    node.parentNode.removeChild(node);
};
ExplodeAnimation.prototype.show= function(d) {
    for (var i= this.spans.length; i-->0;) {
        var style= this.spans[i].style;
        var r= this.randomness[i];

        style.left= d*r.dx+'px';
        style.top= d*r.dy+'px';
        var transform= 'rotate('+Math.floor(d*r.dr)+'deg) scale('+(d*r.sx+1-d)+', '+(d*r.sy+1-d)+')';
        if ('transform' in style)
            style.transform= transform;
        else if ('MozTransform' in style)
            style.MozTransform= transform;

        var o= 1-Math.pow(d, r.og);
        if ('opacity' in style)
            style.opacity= o+'';
        else if ('filter' in style)
            style.filter= 'opacity(alpha='+Math.ceil(o*100)+')';
    }
};


var animation= new ExplodeAnimation(document.getElementById('effect').firstChild, 1000);
document.getElementById('animate').onclick= animation.toggle.bind(animation);

This could be improved by adding gravity and better 3D-space modelling of the currently-completely-random transforms, plus better browser support for the scale/rotation in browsers other than Firefox.

(IE has its own non-standard CSS transform filters it might be possible to support; Webkit and Opera have webkitTransform and oTransform styles, but they refuse to transform relative-positioning inline spans, so you'd have to absolute-position each character, which would mean reading all their normal positions to use as baseline. I got bored at this point.)

Sign up to request clarification or add additional context in comments.

5 Comments

would love to see this in action! PS: you could fake the blur with a text-shadow: 0 0 5px samecolorasfont
...combined with color: rgba(0,0,0,0) to stop the text itself appearing. Clever! But I'm off to bed.
yeah nice idea i have tried it with opacity but it would look much better with rgba!! jsfiddle.net/GqTam/1
cool i think if you tune the the values a little it could look like a real blur: jsfiddle.net/GqTam/4
Thanks, this is extremely helpful, the code I was struggling with was the wrapping in span. AS3 has a function for getcharboundaries(); and this seems pretty painless. Very much appreciated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.