10

Apple's MobileMe uses Javascript to change the font size in the email login on its homepage as the user types so that the text always fits in the available space without overflow scrolling.

While I can see how how to execute a function on each keypress, I'm curious how one would go about calculating the font-size each time so that it always fits into the input field. Is there a way to measure the length of a piece of text with a variable width font? How do they accomplish this effect?

Try it out to see what I mean: http://www.me.com/

1

2 Answers 2

28

I've done this in the past using jQuery. You can measure the size of a piece of text like this:

// txt is the text to measure, font is the full CSS font declaration,
// e.g. "bold 12px Verdana"
function measureText(txt, font) {
    var id = 'text-width-tester',
        $tag = $('#' + id);
    if (!$tag.length) {
        $tag = $('<span id="' + id + '" style="display:none;font:' + font + ';">' + txt + '</span>');
        $('body').append($tag);
    } else {
        $tag.css({font:font}).html(txt);
    }
    return {
        width: $tag.width(),
        height: $tag.height()
    }
}

var size = measureText("spam", "bold 12px Verdana");
console.log(size.width + ' x ' + size.height); // 35 x 12.6

In order to fit this to a given space, it's a little trickier - you need to separate out the font-size declaration and scale it appropriately. Depending on how you're doing things, this might be easiest if you break out the different parts of the font declaration. A resize function might look like this (again, obviously, this is jQuery-dependent):

function shrinkToFill(input, fontSize, fontWeight, fontFamily) {
    var $input = $(input),
        txt = $input.val(),
        maxWidth = $input.width() + 5, // add some padding
        font = fontWeight + " " + fontSize + "px " + fontFamily;
    // see how big the text is at the default size
    var textWidth = measureText(txt, font).width;
    if (textWidth > maxWidth) {
        // if it's too big, calculate a new font size
        // the extra .9 here makes up for some over-measures
        fontSize = fontSize * maxWidth / textWidth * .9;
        font = fontWeight + " " + fontSize + "px " + fontFamily;
        // and set the style on the input
        $input.css({font:font});
    } else {
        // in case the font size has been set small and 
        // the text was then deleted
        $input.css({font:font});
}

You can see this in action here: http://jsfiddle.net/nrabinowitz/9BFQ8/5/

Testing seems to show that this is a little jumpy, at least in Google Chrome, because only full-integer font sizes are used. You might be able to do better with a em-based font declaration, though this might be a little tricky - you'd need to ensure that the 1em size for the text width tester is the same as that for the input.

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

7 Comments

Really, no love at all for a fully functional and relatively clear example of the exact thing you were interested in?
no, it's not that at all. it's perfect thanks. I got caught up in legal issues (incorporation, trademark, ToS and PP) and I haven't popped into SO in two days. Sorry about the delay in upvote and marking this as answered.
Thanks! Sorry if I was being petulant - I appreciate your consideration.
hehe. np. thanks again for helping me with this. It's a very nice effect.
Great function! I made something similar to this but no way near as neat. +1.
|
2

I made another one from a hodgepodge of other answers. I think this provides the simplest one-property-change solution.

It's likely overly verbose or could be refactored for clarity in some ways, any suggestions welcomed!

$(document).ready(function(){

    // get the current styles size, in px integer.
    var maxSize = parseInt($('.fields').css("font-size"));

    function isOverflowed (element){

        if ( $(element)[0].scrollWidth > $(element).innerWidth() ) {
            return true;
        } else {
            return false;
        }
    };

    function decreaseSize (element){

        var fontSize = parseInt($(element).css("font-size"));
        fontSize = fontSize - 1 + "px";
        $(element).css({'font-size':fontSize});

    }

    function maximizeSize (element){

        var fontSize = parseInt($(element).css("font-size"));
        while (!isOverflowed(element) && fontSize < maxSize){
            fontSize = fontSize + 1 + "px";
            $(element).css({'font-size':fontSize});

            // if this loop increases beyond the width, decrease again. 
            // hacky.
            if (isOverflowed(element)){
                while (isOverflowed(element)) {
                    decreaseSize(element);
                }            
            }     

        }        

    }

    function fixSize (element){
        if (isOverflowed(element)){
            while (isOverflowed(element)) {
                decreaseSize(element);
            }            
        } else {
            maximizeSize(element);
        }
    }

    // execute it onready.
    $('.fields').each(function(){
        fixSize(this);
    });

    // bind to it.
    $(function() {
        $('.fields').keyup(function() {
            fixSize(this);
        })
    });    

});

2 Comments

This looks/feels like a much more elegant solution. I've created a JSFiddle here: jsfiddle.net/dsuv8onp
Also, instead of .fields you can use $('input[type=text]') if you want to apply this by default to all text fields.

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.