Dimensionar texto dinâmico automaticamente para preencher contêiner de tamanho fixo


312

Eu preciso exibir o texto digitado pelo usuário em uma div de tamanho fixo. O que eu quero é que o tamanho da fonte seja ajustado automaticamente para que o texto preencha a caixa o máximo possível.

Então - se a div for 400px x 300px. Se alguém entra no ABC, é uma fonte realmente grande. Se eles digitarem um parágrafo, seria uma fonte minúscula.

Eu provavelmente gostaria de começar com um tamanho máximo de fonte - talvez 32px e, enquanto o texto é muito grande para caber no contêiner, reduza o tamanho da fonte até que caiba.


119
Provavelmente os recursos mais incríveis que deveriam ser adicionados ao HTML5 / CSS3 sem a necessidade de JS.
John Magnolia

Fiz algumas medições em que alterei o comprimento de um texto dinâmico e o tamanho do contêiner para descobrir qual tamanho de fonte fará com que o texto caiba perfeitamente. E depois de fazer algumas análises de regressão, criei uma função matemática simples que gerará automaticamente o melhor tamanho de fonte.
Friend of Kim

2
Acontece que o gráfico que oferece o melhor tamanho de fonte é dado por f (x) = g (letras) * (x / 1000) ^ n, onde g (x) é uma função simples, n varia de acordo com a fonte que você está usando. (Embora possa ter um valor padrão para todas as fontes, se você não quiser ajustá-lo para torná-lo absolutamente perfeito ...). x é o tamanho em pixels quadrados do contêiner.
Friend of Kim

1
Se você ainda estiver interessado, posso adicionar uma resposta. Pessoalmente, acho que é um método muito melhor para gerar o tamanho correto da fonte, em vez de tentar e falhar até o script "acertar".
Friend of Kim

1
Verifique a minha resposta para uma maneira melhor de fazer isso
Hoffmann

Respostas:


167

Obrigado Attack . Eu queria usar o jQuery.

Você me apontou na direção certa, e foi assim que acabei:

Aqui está um link para o plugin: https://plugins.jquery.com/textfill/
E um link para a fonte: http://jquery-textfill.github.io/

;(function($) {
    $.fn.textfill = function(options) {
        var fontSize = options.maxFontPixels;
        var ourText = $('span:visible:first', this);
        var maxHeight = $(this).height();
        var maxWidth = $(this).width();
        var textHeight;
        var textWidth;
        do {
            ourText.css('font-size', fontSize);
            textHeight = ourText.height();
            textWidth = ourText.width();
            fontSize = fontSize - 1;
        } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);
        return this;
    }
})(jQuery);

$(document).ready(function() {
    $('.jtextfill').textfill({ maxFontPixels: 36 });
});

e meu html é assim

<div class='jtextfill' style='width:100px;height:50px;'>
    <span>My Text Here</span>
</div>

Este é o meu primeiro plugin jquery, portanto, provavelmente não é tão bom quanto deveria ser. Ponteiros são certamente bem-vindos.


8
Eu realmente só limpa-lo e embalados-lo como um disponíveis plugin jquery.com em plugins.jquery.com/project/TextFill
GeekyMonkey

3
@GeekyMonkey, você puxou o plugin? Acabei de seguir um link enganador para esta página e pensei em dar uma olhada, mas os links do jQuery.com para o seu site retornam 404.
David diz que restabelece Monica

Nota: Descobri que, por algum motivo, este plugin funciona apenas quando o div ($ ('. Jtextfill') no exemplo acima) faz parte do documento raiz. Parece que .width () retorna zero quando a div é incorporada dentro de outras divs.
Jayesh

1
A linha "while" nesse loop parece errada para mim - deve haver parênteses em volta do "||" subexpressão. Do jeito que está escrito agora, o tamanho mínimo da fonte é verificado apenas quando a largura é muito grande, não a altura.
Pointy

4
Essa abordagem é MUITO lenta, sempre que a fonte muda de tamanho, é necessário renderizar novamente o elemento. Verifique minha resposta para uma maneira melhor de fazer isso.
Hoffmann

52

Eu não achei nenhuma das soluções anteriores adequadas o suficiente devido ao desempenho ruim, então criei a minha própria que usa matemática simples em vez de loop. Também deve funcionar bem em todos os navegadores.

De acordo com este caso de teste de desempenho , é muito mais rápido que as outras soluções encontradas aqui.

(function($) {
    $.fn.textfill = function(maxFontSize) {
        maxFontSize = parseInt(maxFontSize, 10);
        return this.each(function(){
            var ourText = $("span", this),
                parent = ourText.parent(),
                maxHeight = parent.height(),
                maxWidth = parent.width(),
                fontSize = parseInt(ourText.css("fontSize"), 10),
                multiplier = maxWidth/ourText.width(),
                newSize = (fontSize*(multiplier-0.1));
            ourText.css(
                "fontSize", 
                (maxFontSize > 0 && newSize > maxFontSize) ? 
                    maxFontSize : 
                    newSize
            );
        });
    };
})(jQuery);

Se você quiser contribuir, adicionei isso ao Gist .


1
@ Jon, obrigado! Você está certo de que meu script não faz várias linhas, mas, novamente, o OP não pediu especificamente isso, então sua suposição pode estar errada. Além disso, esse tipo de comportamento não faz muito sentido. Eu acho que a melhor maneira de adicionar suporte a várias linhas seria dividir a string com base na quantidade de palavras e calcular cada parte com o script acima, e provavelmente seria mais rápido de qualquer maneira.
mekwall

4
@ Jon, eu brinquei um pouco com o preenchimento de texto com várias linhas e acabei com essa solução . O método de tempestade de areia é mais provável mais preciso, mas este é mais rápido;)
mekwall

2
Aqui está uma versão com, bem como mínimo da fonte tamanho máximo: gist.github.com/1714284
Jess Telford

1
@Hoffmann Hmm. Minha solução não chama .css("font-size")em um loop. Onde você conseguiu aquilo? Minha solução é provavelmente mais rápida, pois não possui nenhuma das coisas sofisticadas que você adicionou ao seu plugin. Você é bem-vindo para adicionar seu plugin para o JSPerf e vamos ver qual deles é o mais rápido;)
mekwall

1
@ MarcusEkwall Oh, desculpe, por algum motivo, eu vi um tempo lá. Sua abordagem é semelhante à minha e, de fato, seria um pouco mais lenta, pois meu plugin também faz outras coisas (como ajustar para caber altura e largura, centralizar o texto e outras opções), não importa, a parte realmente lenta está chamando a função .css dentro de um loop.
Hoffmann

35

Por mais que eu goste das votações ocasionais que recebo por esta resposta (obrigado!), Essa não é realmente a melhor abordagem para esse problema. Confira algumas das outras respostas maravilhosas aqui, especialmente as que encontraram soluções sem loop.


Ainda assim, por uma questão de referência, aqui está a minha resposta original :

<html>
<head>
<style type="text/css">
    #dynamicDiv
    {
    background: #CCCCCC;
    width: 300px;
    height: 100px;
    font-size: 64px;
    overflow: hidden;
    }
</style>

<script type="text/javascript">
    function shrink()
    {
        var textSpan = document.getElementById("dynamicSpan");
        var textDiv = document.getElementById("dynamicDiv");

        textSpan.style.fontSize = 64;

        while(textSpan.offsetHeight > textDiv.offsetHeight)
        {
            textSpan.style.fontSize = parseInt(textSpan.style.fontSize) - 1;
        }
    }
</script>

</head>
<body onload="shrink()">
    <div id="dynamicDiv"><span id="dynamicSpan">DYNAMIC FONT</span></div>
</body>
</html>

E aqui está uma versão com classes :

<html>
<head>
<style type="text/css">
.dynamicDiv
{
    background: #CCCCCC;
    width: 300px;
    height: 100px;
    font-size: 64px;
    overflow: hidden;
}
</style>

<script type="text/javascript">
    function shrink()
    {
        var textDivs = document.getElementsByClassName("dynamicDiv");
        var textDivsLength = textDivs.length;

        // Loop through all of the dynamic divs on the page
        for(var i=0; i<textDivsLength; i++) {

            var textDiv = textDivs[i];

            // Loop through all of the dynamic spans within the div
            var textSpan = textDiv.getElementsByClassName("dynamicSpan")[0];

            // Use the same looping logic as before
            textSpan.style.fontSize = 64;

            while(textSpan.offsetHeight > textDiv.offsetHeight)
            {
                textSpan.style.fontSize = parseInt(textSpan.style.fontSize) - 1;
            }

        }

    }
</script>

</head>
<body onload="shrink()">
    <div class="dynamicDiv"><span class="dynamicSpan">DYNAMIC FONT</span></div>
    <div class="dynamicDiv"><span class="dynamicSpan">ANOTHER DYNAMIC FONT</span></div>
    <div class="dynamicDiv"><span class="dynamicSpan">AND YET ANOTHER DYNAMIC FONT</span></div>
</body>
</html>

3
Achei isso funcionou melhor com offsetWidth, eu também tive que criar uma variável para o tamanho e, em seguida, acrescentar o pxtextSpan.style.fontSize = size+"px";
Wez

2
é necessário '+ "px"'.
Sandun Dhammika

@IdanShechter Desculpe pela longa, longa espera! Adicionado um exemplo!
ataque

Obrigado, salva-vidas! Eu não estou ciente de jQuery, então eu ficar com a sua solução :)
vintproykt

32

A maioria das outras respostas usa um loop para reduzir o tamanho da fonte até que ela caiba na div, isso é MUITO lento, pois a página precisa renderizar novamente o elemento cada vez que a fonte muda de tamanho. Acabei precisando escrever meu próprio algoritmo para que ele funcionasse de uma maneira que me permitisse atualizar seu conteúdo periodicamente sem congelar o navegador do usuário. Adicionei algumas outras funcionalidades (rotação de texto, adição de preenchimento) e o empacotei como um plugin jQuery, você pode obtê-lo em:

https://github.com/DanielHoffmann/jquery-bigtext

simplesmente chame

$("#text").bigText();

e caberá bem no seu contêiner.

Veja em ação aqui:

http://danielhoffmann.github.io/jquery-bigtext/

Por enquanto, ele tem algumas limitações, a div deve ter altura e largura fixas e não suporta quebra de texto em várias linhas.

Vou trabalhar para obter uma opção para definir o tamanho máximo da fonte.

Edit: Eu encontrei mais alguns problemas com o plugin, ele não lida com outro modelo de caixa além do padrão e o div não pode ter margens ou bordas. Vou trabalhar nisso.

Edit2: Eu já corrigi esses problemas e limitações e adicionei mais opções. Você pode definir o tamanho máximo da fonte e também pode limitar o tamanho da fonte usando largura, altura ou ambas. Vou trabalhar para aceitar valores de largura e altura máx. No elemento wrapper.

Edit3: Eu atualizei o plugin para a versão 1.2.0. Limpeza principal no código e novas opções (verticalAlign, horizontalAlign, textAlign) e suporte para elementos internos dentro da tag span (como quebras de linha ou ícones impressionantes de fontes).


1
Eu estou querendo saber por que o texto em várias linhas não é suportado?
Manish Sapariya

1
@ManishSapariya É suportado, mas você precisa adicionar as quebras de linha (tags br) manualmente. O motivo de eu não oferecer suporte à quebra automática de texto é porque, para torná-la rápida (altere o tamanho da fonte duas vezes em vez de várias vezes), preciso assumir que o texto não será quebrado entre as palavras. A maneira como meu plug-in funciona é definir o tamanho da fonte como 1000px e, em seguida, ver o fator do tamanho em que o texto é comparado ao contêiner e, em seguida, reduzo o tamanho da fonte pelo mesmo fator. Para dar suporte à quebra de texto normal, eu precisaria usar a abordagem lenta (reduza o tamanho da fonte várias vezes), que é muito lenta.
Hoffmann

Ei! Como não há mensagens privadas aqui, no StackOverflow, precisarei perguntar a você comentando sua resposta. Eu amo o seu plugin jQuery, mas não consigo fazê-lo funcionar para mim. Incluí a biblioteca jQuery correta, baixei e incluí o plugin. Agora, quando eu tento usá-lo, o console diz 'Uncaught TypeError: undefined não é uma função'. É algo com que você está familiarizado? Você sabe como consertar isso? Obrigado
Gust van de Wal

@GustvandeWal Você precisa incluir o plug-in depois de incluir a biblioteca jquery
Hoffmann

Eu fiz. Eu tenho <script type = "text / javascript" src = " code.jquery.com/jquery-2.1.1.min.js"></… src" js / jquery-bigtext.js "> </ script > O navegador não me avisa que não pode carregar a biblioteca jQuery ou o plug-in.
Gust van de Wal

9

Isso é baseado no que o GeekyMonkey postou acima, com algumas modificações.

; (function($) {
/**
* Resize inner element to fit the outer element
* @author Some modifications by Sandstrom
* @author Code based on earlier works by Russ Painter (WebDesign@GeekyMonkey.com)
* @version 0.2
*/
$.fn.textfill = function(options) {

    options = jQuery.extend({
        maxFontSize: null,
        minFontSize: 8,
        step: 1
    }, options);

    return this.each(function() {

        var innerElements = $(this).children(':visible'),
            fontSize = options.maxFontSize || innerElements.css("font-size"), // use current font-size by default
            maxHeight = $(this).height(),
            maxWidth = $(this).width(),
            innerHeight,
            innerWidth;

        do {

            innerElements.css('font-size', fontSize);

            // use the combined height of all children, eg. multiple <p> elements.
            innerHeight = $.map(innerElements, function(e) {
                return $(e).outerHeight();
            }).reduce(function(p, c) {
                return p + c;
            }, 0);

            innerWidth = innerElements.outerWidth(); // assumes that all inner elements have the same width
            fontSize = fontSize - options.step;

        } while ((innerHeight > maxHeight || innerWidth > maxWidth) && fontSize > options.minFontSize);

    });

};

})(jQuery);

A diferença é que ele pode levar vários elementos filhos e que leva em consideração o preenchimento. Usa o tamanho da fonte como o tamanho máximo padrão, para evitar a mistura de javascript e css.
Sandstrom

5
Isso é ótimo, mas como eu o uso? Eu faço $ ('. Outer'). Textfill (); e não tenho troco.
Tirou Baker

3
Obrigado, esta é uma implementação muito boa. Uma coisa eu me deparei: se você estiver lidando com seqüências de texto muito longas e contêineres muito estreitos, a sequência de texto ficará presa no contêiner, mas a Largura Externa ainda será calculada como se não o fizesse. Atire "quebra de linha: quebra de palavra;" no seu CSS para esse contêiner, ele corrigirá esse problema.
Jon Jon #

8

Aqui está um método de loop aprimorado que usa a pesquisa binária para encontrar o maior tamanho possível que se encaixa no pai nas poucas etapas possíveis (isso é mais rápido e preciso do que o tamanho de uma fonte fixa). O código também é otimizado de várias maneiras para desempenho.

Por padrão, serão executadas 10 etapas de pesquisa binária, que chegarão a 0,1% do tamanho ideal. Em vez disso, você pode definir numIter com algum valor N para obter 1/2 ^ N do tamanho ideal.

Chame-o com um seletor de CSS, por exemplo: fitToParent('.title-span');

/**
 * Fit all elements matching a given CSS selector to their parent elements'
 * width and height, by adjusting the font-size attribute to be as large as
 * possible. Uses binary search.
 */
var fitToParent = function(selector) {
    var numIter = 10;  // Number of binary search iterations
    var regexp = /\d+(\.\d+)?/;
    var fontSize = function(elem) {
        var match = elem.css('font-size').match(regexp);
        var size = match == null ? 16 : parseFloat(match[0]);
        return isNaN(size) ? 16 : size;
    }
    $(selector).each(function() {
        var elem = $(this);
        var parentWidth = elem.parent().width();
        var parentHeight = elem.parent().height();
        if (elem.width() > parentWidth || elem.height() > parentHeight) {
            var maxSize = fontSize(elem), minSize = 0.1;
            for (var i = 0; i < numIter; i++) {
                var currSize = (minSize + maxSize) / 2;
                elem.css('font-size', currSize);
                if (elem.width() > parentWidth || elem.height() > parentHeight) {
                    maxSize = currSize;
                } else {
                    minSize = currSize;
                }
            }
            elem.css('font-size', minSize);
        }
    });
};

Adoro esta opção. Eu o modifiquei para adicionar parâmetros para vAligne padding. vAlign == truedefine a altura da linha do elemento selecionado para a altura dos pais. O preenchimento diminui o tamanho final pelo valor passado. O padrão é 5. Eu acho que parece muito bom.
Advogado do dia

6

Eu criei uma diretiva para o AngularJS - profundamente inspirada na resposta do GeekyMonkey, mas sem a dependência do jQuery.

Demo: http://plnkr.co/edit/8tPCZIjvO3VSApSeTtYr?p=preview

Marcação

<div class="fittext" max-font-size="50" text="Your text goes here..."></div>

Directiva

app.directive('fittext', function() {

  return {
    scope: {
      minFontSize: '@',
      maxFontSize: '@',
      text: '='
    },
    restrict: 'C',
    transclude: true,
    template: '<div ng-transclude class="textContainer" ng-bind="text"></div>',
    controller: function($scope, $element, $attrs) {
      var fontSize = $scope.maxFontSize || 50;
      var minFontSize = $scope.minFontSize || 8;

      // text container
      var textContainer = $element[0].querySelector('.textContainer');

      angular.element(textContainer).css('word-wrap', 'break-word');

      // max dimensions for text container
      var maxHeight = $element[0].offsetHeight;
      var maxWidth = $element[0].offsetWidth;

      var textContainerHeight;
      var textContainerWidth;      

      var resizeText = function(){
        do {
          // set new font size and determine resulting dimensions
          textContainer.style.fontSize = fontSize + 'px';
          textContainerHeight = textContainer.offsetHeight;
          textContainerWidth = textContainer.offsetWidth;

          // shrink font size
          var ratioHeight = Math.floor(textContainerHeight / maxHeight);
          var ratioWidth = Math.floor(textContainerWidth / maxWidth);
          var shrinkFactor = ratioHeight > ratioWidth ? ratioHeight : ratioWidth;
          fontSize -= shrinkFactor;

        } while ((textContainerHeight > maxHeight || textContainerWidth > maxWidth) && fontSize > minFontSize);        
      };

      // watch for changes to text
      $scope.$watch('text', function(newText, oldText){
        if(newText === undefined) return;

        // text was deleted
        if(oldText !== undefined && newText.length < oldText.length){
          fontSize = $scope.maxFontSize;
        }
        resizeText();
      });
    }
  };
});

Um problema que tenho tido com isso é que resizeTextparece ser chamado antes de ng-bindrealmente atribuir o texto ao elemento, resultando em seu dimensionamento com base no texto anterior e não no texto atual. Isso não é tão ruim na demonstração acima, onde é chamado repetidamente à medida que o usuário digita, mas se for chamado uma vez passando de nulo para o valor real (como em uma ligação unidirecional), permanecerá no tamanho máximo.
Miral 14/10

5

Bifurquei o script acima de Marcus Ekwall: https://gist.github.com/3945316 e o ajustei de acordo com minhas preferências; ele agora é acionado quando a janela é redimensionada, para que a criança sempre se encaixe no seu contêiner. Eu colei o script abaixo para referência.

(function($) {
    $.fn.textfill = function(maxFontSize) {
        maxFontSize = parseInt(maxFontSize, 10);
        return this.each(function(){
            var ourText = $("span", this);
            function resizefont(){
                var parent = ourText.parent(),
                maxHeight = parent.height(),
                maxWidth = parent.width(),
                fontSize = parseInt(ourText.css("fontSize"), 10),
                multiplier = maxWidth/ourText.width(),
                newSize = (fontSize*(multiplier));
                ourText.css("fontSize", maxFontSize > 0 && newSize > maxFontSize ? maxFontSize : newSize );
            }
            $(window).resize(function(){
                resizefont();
            });
            resizefont();
        });
    };
})(jQuery);

2
É ótimo que você esteja tentando ajudar o solicitante. No entanto, deixar uma resposta com apenas um link pode ser prejudicial em alguns casos. Embora sua resposta seja boa agora, se o link morrer, sua resposta perderá seu valor. Portanto, será útil se você resumir o conteúdo do artigo em sua resposta. Veja esta pergunta para esclarecimentos.
Cody Guldner

5

Aqui está a minha modificação da resposta do OP.

Em resumo, muitas pessoas que tentaram otimizar isso reclamaram que um loop estava sendo usado. Sim, enquanto os loops podem ser lentos, outras abordagens podem ser imprecisas.

Portanto, minha abordagem usa a Pesquisa binária para encontrar o melhor tamanho da fonte:

$.fn.textfill = function()
{
    var self = $(this);
    var parent = self.parent();

    var attr = self.attr('max-font-size');
    var maxFontSize = parseInt(attr, 10);
    var unit = attr.replace(maxFontSize, "");

    var minFontSize = parseInt(self.attr('min-font-size').replace(unit, ""));
    var fontSize = (maxFontSize + minFontSize) / 2;

    var maxHeight = parent.height();
    var maxWidth = parent.width();

    var textHeight;
    var textWidth;

    do
    {
        self.css('font-size', fontSize + unit);

        textHeight = self.height();
        textWidth = self.width();

        if(textHeight > maxHeight || textWidth > maxWidth)
        {
            maxFontSize = fontSize;
            fontSize = Math.floor((fontSize + minFontSize) / 2);
        }
        else if(textHeight < maxHeight || textWidth < maxWidth)
        {
            minFontSize = fontSize;
            fontSize = Math.floor((fontSize + maxFontSize) / 2);
        }
        else
            break;

    }
    while(maxFontSize - minFontSize > 1 && maxFontSize > minFontSize);

    self.css('font-size', fontSize + unit);

    return this;
}

function resizeText()
{
  $(".textfill").textfill();
}

$(document).ready(resizeText);
$(window).resize(resizeText);

Isso também permite que o elemento especifique a fonte mínima e máxima:

<div class="container">
    <div class="textfill" min-font-size="10px" max-font-size="72px">
        Text that will fill the container, to the best of its abilities, and it will <i>never</i> have overflow.
    </div>
</div>

Além disso, esse algoritmo é sem unidade. Você pode especificar em, rem,% , etc. e ele vai usar isso para o seu resultado final.

Aqui está o violino: https://jsfiddle.net/fkhqhnqe/1/


2

Eu tive exatamente o mesmo problema com o meu site. Eu tenho uma página exibida em um projetor, em paredes, telas grandes.

Como não sei o tamanho máximo da minha fonte, reutilizei o plug-in acima do @GeekMonkey, mas aumentando o tamanho da fonte:

$.fn.textfill = function(options) {
        var defaults = { innerTag: 'span', padding: '10' };
        var Opts = jQuery.extend(defaults, options);

        return this.each(function() {
            var ourText = $(Opts.innerTag + ':visible:first', this);
            var fontSize = parseFloat(ourText.css('font-size'),10);
            var doNotTrepass = $(this).height()-2*Opts.padding ;
            var textHeight;

            do {
                ourText.css('font-size', fontSize);
                textHeight = ourText.height();
                fontSize = fontSize + 2;
            } while (textHeight < doNotTrepass );
        });
    };

+1 por ser o único plug-in nesta página que realmente funcionou para mim!
skybondsor

2
Este plugin trava a página para mim.
Jezen Thomas

1

Aqui está uma versão da resposta aceita, que também pode receber um parâmetro minFontSize.

(function($) {
    /**
    * Resizes an inner element's font so that the inner element completely fills the outer element.
    * @author Russ Painter WebDesign@GeekyMonkey.com
    * @author Blake Robertson 
    * @version 0.2 -- Modified it so a min font parameter can be specified.
    *    
    * @param {Object} Options which are maxFontPixels (default=40), innerTag (default='span')
    * @return All outer elements processed
    * @example <div class='mybigdiv filltext'><span>My Text To Resize</span></div>
    */
    $.fn.textfill = function(options) {
        var defaults = {
            maxFontPixels: 40,
            minFontPixels: 10,
            innerTag: 'span'
        };
        var Opts = jQuery.extend(defaults, options);
        return this.each(function() {
            var fontSize = Opts.maxFontPixels;
            var ourText = $(Opts.innerTag + ':visible:first', this);
            var maxHeight = $(this).height();
            var maxWidth = $(this).width();
            var textHeight;
            var textWidth;
            do {
                ourText.css('font-size', fontSize);
                textHeight = ourText.height();
                textWidth = ourText.width();
                fontSize = fontSize - 1;
            } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > Opts.minFontPixels);
        });
    };
})(jQuery);

graças, embora eu acho que você tem um ponto e vírgula no topo do código que não deveria estar lá
Patrick Moore

1

Você pode usar o FitText.js ( página do github ) para resolver esse problema. É realmente pequeno e eficiente em comparação com o TextFill. O TextFill usa um loop while caro e o FitText não.

O FitText também é mais flexível (eu o uso em um projeto com requisitos muito especiais e funciona como um campeão!).

HTML:

<div class="container">
  <h1 id="responsive_headline">Your fancy title</h1>
</div>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="jquery.fittext.js"></script>
<script>
  jQuery("#responsive_headline").fitText();
</script>

Você também pode definir opções para ele:

<script>
  jQuery("#responsive_headline").fitText(1, { minFontSize: '30px', maxFontSize: '90px'});
</script>

CSS:

#responsive_headline {
   width: 100%;
   display: block;
}

E se você precisar, o FitText também possui uma versão sem jQuery .


O fittext leva em consideração a altura?
Manish Sapariya

1
@ManishSapariya não, não. Ele apenas divide a largura do contêiner por 10 e a usa como tamanho da fonte.
Dan H

1

EDIT: este código foi usado para mostrar notas em cima de um vídeo HTML5. Ele muda o tamanho da fonte rapidamente quando o vídeo é redimensionado (quando a janela do navegador é redimensionada.) As notas foram conectadas ao vídeo (assim como as notas do YouTube), e é por isso que o código usa instâncias em vez de um identificador DOM diretamente.

Conforme a solicitação, lançarei algum código que usei para conseguir isso. (Caixas de texto em um vídeo HTML5.) O código foi escrito há muito tempo e, francamente, acho bastante bagunçado. Como a pergunta já foi respondida e uma resposta já foi aceita há muito tempo, não me importo em reescrever isso. Mas se alguém quiser simplificar um pouco, você é bem-vindo!

// Figure out the text size:
var text = val['text'];
var letters = text.length;
var findMultiplier = function(x) { // g(x)
    /* By analysing some functions with regression, the resulting function that
     gives the best font size with respect to the number of letters and the size
     of the note is:
     g(x) = 8.3 - 2.75x^0.15 [1 < x < 255]
     f(x) = g(letters) * (x / 1000)^0.5
     Font size = f(size)
     */
    return 8.3 - 2.75 * Math.pow(x, 0.15);
};

var findFontSize = function(x) { // f(x)
    return findMultiplier(letters) * Math.pow(x / 1000, 0.5);
};

val.setFontSizeListener = function() {
    p.style.fontSize = '1px'; // So the text should not overflow the box when measuring.
    var noteStyle = window.getComputedStyle(table);
    var width = noteStyle.getPropertyValue('width');
    var height = noteStyle.getPropertyValue('height');
    var size = width.substring(0, width.length - 2) * height.substring(0, height.length - 2);
    p.style.fontSize = findFontSize(size) + 'px';
};
window.addEventListener('resize', val.setFontSizeListener);

Você provavelmente precisará ajustar esses números de família de fontes para família de fontes. Uma boa maneira de fazer isso é baixar um visualizador gráfico gratuito chamado GeoGebra. Altere o comprimento do texto e o tamanho da caixa. Então você define manualmente o tamanho. Plote os resultados manuais no sistema de coordenadas. Em seguida, você insere as duas equações que eu publiquei aqui e ajusta os números até que o gráfico "meu" se ajuste aos seus próprios pontos plotados manualmente.


1

As soluções iterativas propostas podem ser aceleradas dramaticamente em duas frentes:

1) Multiplique o tamanho da fonte por alguma constante, em vez de adicionar ou subtrair 1.

2) Primeiro, use zero uma constante de curso, por exemplo, o dobro do tamanho de cada loop. Então, com uma idéia aproximada de onde começar, faça o mesmo com um ajuste mais preciso, digamos, multiplique por 1,1. Enquanto o perfeccionista pode querer o tamanho exato de pixel inteiro da fonte ideal, a maioria dos observadores não percebe a diferença entre 100 e 110 pixels. Se você é um perfeccionista, repita uma terceira vez com um ajuste ainda mais refinado.

Em vez de escrever uma rotina ou plug-in específico que responda à pergunta exata, confio apenas nas idéias básicas e escrevo variações do código para lidar com todos os tipos de problemas de layout, não apenas texto, incluindo divs, extensões, imagens. .. por largura, altura, área, ... dentro de um contêiner, combinando com outro elemento ....

Aqui está um exemplo:

  var                           nWindowH_px             = jQuery(window).height();
  var                           nWas                    = 0;
  var                           nTry                    = 5;

  do{
   nWas = nTry;
   nTry *= 2;
   jQuery('#divTitle').css('font-size' ,nTry +'px');
  }while( jQuery('#divTitle').height() < nWindowH_px );

  nTry = nWas;

  do{
   nWas = nTry;
   nTry = Math.floor( nTry * 1.1 );
   jQuery('#divTitle').css('font-size' ,nTry +'px');
  }while( nWas != nTry   &&   jQuery('#divTitle').height() < nWindowH_px );

  jQuery('#divTitle').css('font-size' ,nWas +'px');

1

Esta é a solução mais elegante que eu criei. Ele usa pesquisa binária, fazendo 10 iterações. A maneira ingênua era fazer um loop while e aumentar o tamanho da fonte em 1 até o elemento começar a transbordar. Você pode determinar quando um elemento começa a exceder usando element.offsetHeight e element.scrollHeight . Se scrollHeight for maior que offsetHeight, você terá um tamanho de fonte muito grande.

A pesquisa binária é um algoritmo muito melhor para isso. Também é limitado pelo número de iterações que você deseja executar. Basta ligar para flexFont e insira o id div e ajusta o tamanho da fonte entre 8px e 96px .

Passei algum tempo pesquisando esse tópico e tentando diferentes bibliotecas, mas, no final das contas, acho que essa é a solução mais fácil e direta que realmente funcionará.

Observe que, se você quiser, pode mudar para usar offsetWidthe scrollWidthou adicionar ambos a esta função.

// Set the font size using overflow property and div height
function flexFont(divId) {
    var content = document.getElementById(divId);
    content.style.fontSize = determineMaxFontSize(content, 8, 96, 10, 0) + "px";
};

// Use binary search to determine font size
function determineMaxFontSize(content, min, max, iterations, lastSizeNotTooBig) {
    if (iterations === 0) {
        return lastSizeNotTooBig;
    }
    var obj = fontSizeTooBig(content, min, lastSizeNotTooBig);

    // if `min` too big {....min.....max.....}
    // search between (avg(min, lastSizeTooSmall)), min)
    // if `min` too small, search between (avg(min,max), max)
    // keep track of iterations, and the last font size that was not too big
    if (obj.tooBig) {
        (lastSizeTooSmall === -1) ?
            determineMaxFontSize(content, min / 2, min, iterations - 1, obj.lastSizeNotTooBig, lastSizeTooSmall) :
                determineMaxFontSize(content, (min + lastSizeTooSmall) / 2, min, iterations - 1, obj.lastSizeNotTooBig, lastSizeTooSmall);

    } else {
        determineMaxFontSize(content, (min + max) / 2, max, iterations - 1, obj.lastSizeNotTooBig, min);
    }
}

// determine if fontSize is too big based on scrollHeight and offsetHeight, 
// keep track of last value that did not overflow
function fontSizeTooBig(content, fontSize, lastSizeNotTooBig) {
    content.style.fontSize = fontSize + "px";
    var tooBig = content.scrollHeight > content.offsetHeight;
    return {
        tooBig: tooBig,
        lastSizeNotTooBig: tooBig ? lastSizeNotTooBig : fontSize
    };
}

Obrigado, isso parece ótimo! Eu só estou recebendo ReferenceError: lastSizeTooSmall is not defined. Talvez isso precise ser definido em algum lugar?
ndbroadbent

0

Eu tenho o mesmo problema e a solução é basicamente usar javascript para controlar o tamanho da fonte. Veja este exemplo no codepen:

https://codepen.io/ThePostModernPlatonic/pen/BZKzVR

Este exemplo é apenas para altura, talvez você precise colocar alguns if's sobre a largura.

tente redimensioná-lo

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Documento sem título</title>
<style>
</style>
</head>
<body>
<div style="height:100vh;background-color: tomato;" id="wrap">        
  <h1 class="quote" id="quotee" style="padding-top: 56px">Because too much "light" doesn't <em>illuminate</em> our paths and warm us, it only blinds and burns us.</h1>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
  var multiplexador = 3;
  initial_div_height = document.getElementById ("wrap").scrollHeight;
  setInterval(function(){ 
    var div = document.getElementById ("wrap");
    var frase = document.getElementById ("quotee");
    var message = "WIDTH div " + div.scrollWidth + "px. "+ frase.scrollWidth+"px. frase \n";
    message += "HEIGHT div " + initial_div_height + "px. "+ frase.scrollHeight+"px. frase \n";           
    if (frase.scrollHeight < initial_div_height - 30){
      multiplexador += 1;
      $("#quotee").css("font-size", multiplexador); 
    }
    console.log(message);          
  }, 10);
</script>
</html>

0

eu gostei

let name = "Making statements based on opinion; back them up with references or personal experience."
let originFontSize = 15;
let maxDisplayCharInLine = 50; 
let fontSize = Math.min(originFontSize, originFontSize / (name.length / maxDisplayCharInLine));

0

Só queria adicionar minha versão para contenteditables.

$.fn.fitInText = function() {
  this.each(function() {

    let textbox = $(this);
    let textboxNode = this;

    let mutationCallback = function(mutationsList, observer) {
      if (observer) {
        observer.disconnect();
      }
      textbox.css('font-size', 0);
      let desiredHeight = textbox.css('height');
      for (i = 12; i < 50; i++) {
        textbox.css('font-size', i);
        if (textbox.css('height') > desiredHeight) {
          textbox.css('font-size', i - 1);
          break;
        }
      }

      var config = {
        attributes: true,
        childList: true,
        subtree: true,
        characterData: true
      };
      let newobserver = new MutationObserver(mutationCallback);
      newobserver.observe(textboxNode, config);

    };

    mutationCallback();

  });
}

$('#inner').fitInText();
#outer {
  display: table;
  width: 100%;
}

#inner {
  border: 1px solid black;
  height: 170px;
  text-align: center;
  display: table-cell;
  vertical-align: middle;
  word-break: break-all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
  <div id="inner" contenteditable=true>
    TEST
  </div>
</div>


0

Eu encontrei uma maneira de impedir o uso de loops para reduzir o texto. Ele ajusta o tamanho da fonte multiplicando-o pela taxa entre a largura do contêiner e a largura do conteúdo. Portanto, se a largura do contêiner for 1/3 do conteúdo, o tamanho da fonte será reduzido em 1/3 e a largura do contêiner. Para aumentar, usei um loop while, até que o conteúdo seja maior que o contêiner.

function fitText(outputSelector){
    // max font size in pixels
    const maxFontSize = 50;
    // get the DOM output element by its selector
    let outputDiv = document.getElementById(outputSelector);
    // get element's width
    let width = outputDiv.clientWidth;
    // get content's width
    let contentWidth = outputDiv.scrollWidth;
    // get fontSize
    let fontSize = parseInt(window.getComputedStyle(outputDiv, null).getPropertyValue('font-size'),10);
    // if content's width is bigger than elements width - overflow
    if (contentWidth > width){
        fontSize = Math.ceil(fontSize * width/contentWidth,10);
        fontSize =  fontSize > maxFontSize  ? fontSize = maxFontSize  : fontSize - 1;
        outputDiv.style.fontSize = fontSize+'px';   
    }else{
        // content is smaller than width... let's resize in 1 px until it fits 
        while (contentWidth === width && fontSize < maxFontSize){
            fontSize = Math.ceil(fontSize) + 1;
            fontSize = fontSize > maxFontSize  ? fontSize = maxFontSize  : fontSize;
            outputDiv.style.fontSize = fontSize+'px';   
            // update widths
            width = outputDiv.clientWidth;
            contentWidth = outputDiv.scrollWidth;
            if (contentWidth > width){
                outputDiv.style.fontSize = fontSize-1+'px'; 
            }
        }
    }
}

Este código é parte de um teste que eu carreguei no Github https://github.com/ricardobrg/fitText/


0

Fui com a solução geekMonkey, mas é muito lento. O que ele faz é ajustar o tamanho da fonte ao máximo (maxFontPixels) e depois verificar se ele se encaixa dentro do contêiner. caso contrário, reduz o tamanho da fonte em 1px e verifica novamente. Por que não simplesmente verificar a altura do contêiner anterior e enviar esse valor? (sim, eu sei o porquê, mas agora criei uma solução, que só funciona na altura e também possui uma opção min / max)

Aqui está uma solução muito mais rápida:

var index_letters_resize;
(index_letters_resize = function() {
  $(".textfill").each(function() {
    var
      $this = $(this),
      height = Math.min( Math.max( parseInt( $this.height() ), 40 ), 150 );
    $this.find(".size-adjust").css({
      fontSize: height
    });
  });
}).call();

$(window).on('resize', function() {
  index_letters_resize();
);

e este seria o HTML:

<div class="textfill">
  <span class="size-adjust">adjusted element</span>
  other variable stuff that defines the container size
</div>

Novamente: esta solução verifica apenas a altura do contêiner. É por isso que essa função não precisa verificar se o elemento se encaixa dentro. Mas também implementei um valor min / max (40min, 150max), portanto, para mim, isso funciona perfeitamente bem (e também redimensiona janelas).


-1

Aqui está outra versão desta solução:

shrinkTextInElement : function(el, minFontSizePx) {
    if(!minFontSizePx) {
        minFontSizePx = 5;
    }
    while(el.offsetWidth > el.parentNode.offsetWidth || el.offsetHeight > el.parentNode.offsetHeight) {

        var newFontSize = (parseInt(el.style.fontSize, 10) - 3);
        if(newFontSize <= minFontSizePx) {
            break;
        }

        el.style.fontSize = newFontSize + "px";
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.