最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Scale fixed-width font to fit constant number of characters on a line - Stack Overflow

programmeradmin1浏览0评论

I have a websocket based terminal session. I want the font size to increase to fill the div such that it is always 80 characters wide. What's the best way of doing this? I'd love it if there was a CSS-only way of doing this, but I'm already using jQuery, so a vanilla javascript or jQuery solution would be good too.

I have a websocket based terminal session. I want the font size to increase to fill the div such that it is always 80 characters wide. What's the best way of doing this? I'd love it if there was a CSS-only way of doing this, but I'm already using jQuery, so a vanilla javascript or jQuery solution would be good too.

Share Improve this question asked Jan 18, 2015 at 17:42 Phil LelloPhil Lello 8,6392 gold badges27 silver badges36 bronze badges 5
  • doesn't sound like this is the way you want to take care of this but anyways fittextjs.com – Nickfmc Commented Jan 18, 2015 at 17:47
  • how about using em as your width measurement? – jbutler483 Commented Jan 26, 2015 at 10:24
  • You may find this demo on Codepen useful – davidcondrey Commented Jan 28, 2015 at 6:16
  • @jbutler483, originally in typography, an em was defined as the width of the letter "M," but in CSS, it's defined as the height. See w3.org/WAI/GL/css2em.htm: "The meaning of "em" has changed over the years. ... The term has therefore come to mean the height of the font - not the width of the letter "M"." – Rick Hitchcock Commented Jan 28, 2015 at 15:32
  • Just created fixie.js on github, based on the example code in my answer. Its like FitText and BigText, but for fixed-width fonts. FYI, using the ruler approach and choosing em as the font-size unit are key in supporting various fonts in the widest range of browsers. – dperish Commented Jan 29, 2015 at 6:20
Add a comment  | 

7 Answers 7

Reset to default 4 +25

Here's a CSS-only example based on the8472's answer:

div,textarea{
    width:100%;
    max-width: 100%;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    font-family:  "Lucida Console", Monaco, monospace;
    font-size: 1.99vw; /* The text overflows in safari using (int)2 */
    white-space: nowrap;
    overflow-x:hidden;

    /* styling */ 
    background:#09f;
    min-height: 100px;
    margin-bottom: 30px;
}

How does it work?

To get the right font-size, divide 160 by the number of characters you want to fit per line, minus 0.01.

http://jsfiddle.net/mb2L4mee/1/

Tested in Chrome, FF and safari.

For browsers that support fractional font sizes or CSS text-rendering: geometricPrecision, you can get a fixed-width font to perfectly fit within any bounds.

In this screenshot, the fourth line is exactly 80 characters:

For browsers that don't support fractional font sizes , it is impossible to always make x characters fit within a given width.

Here's the same text with a 12px font:

… and with a 13px font:

Using the 12px font, the longest line is 83 characters, so it's too small. Using the 13px font, the longest line is 77 characters, so it's too large.

In this situation, you must shrink or widen the container to match the font:

The code below places an 80-character wide span in divs of varying widths, using a style of word-wrap: break-word;. It does a binary search for the best font size, using getClientRects() to determine upper and lower bounds for the binary search.

In case the browser doesn't support fractional font sizes, it adjusts the container's width to match the font size.

The span is then removed.

$('div').each(function() {
  var x= $('<span>'+Array(80).join('x')+'</span>').appendTo(this),
      lo= 0.1,
      hi= 50,
      mid;
  do {
    mid= (lo+hi)/2;
    $(this).css({
      fontSize: mid
    });
    if(x[0].getClientRects().length > 1) {
      hi= mid;
    }
    else {
      lo= mid;
    }
  } while(Math.abs(hi-lo)>0.001);
  while(x[0].getClientRects().length === 1) {
    $(this).css({
      width: '-=1'
    });
  }
  while(x[0].getClientRects().length === 2) {
    $(this).css({
      width: '+=1'
    });
  }
  x.remove();
});

Tested in Chrome, IE, Firefox, Safari, and Opera.

Fiddle

One approach would be to calculate the size of a 80 char line for a fixed font-size and register a function on resize event to adjust the font-size based on the terminal size.

<div id="terminal">
    <div class="text">
        Some text
    </div>
</div>

function scale(lineWith) { /* lineWidth based on 80char 10px font-size */
    var ratio = $('#terminal').width() / lineWith;
    $('.text').css('font-size', (10 * ratio) + 'px');
}
$(window).resize(function(){
    scale(lineWith)
});

Demo: http://jsfiddle.net/44x56zsu/

This seems to adjust pretty good in Chrome (Versión 39.0.2171.99 m)

But I don't think it's possible to adjust the font-size to exactly fit the container size for all browsers because decimal values are rounded. Font size test: http://jsfiddle.net/ahaq49t1/

This rounding issue in some browsers prevents to do an exact adjustment using font properties.

Within limits it might be possible in pure CSS. If you know the fraction of the viewport that the div will occupy then you can scale the font based on the view port width.

.terminal {font-size: 1vw;} would result in a font size (vertical) equivalent to 1% of the view port. Since a monospace font also has a fixed aspect ratio that results in a width depending on the aspect ratio of the font and the view port size.

Not sure if this is the most elegant solution, and it needs a bit more polishing too. But you can measure the font. Create a hidden measure bar to see how wide the font is in pixels. Fill it with the desired amount of characters (using 20 here for demonstration purposes):

<span id="measureBar">12345678901234567890</span>

Keep increasing the font size of the measure bar and find the largest width that's still not wider than the div:

$(
  function() {
    var $measureBar = $('#measureBar');
    var $console = $('#console');
    $measureBar.css('font-size', '1px');
    for (i = 1; $measureBar.width() <= $console.width(); i++) {
      $measureBar.css('font-size', i + 'px');
    }
    $console.css('font-size', (i - 1) + 'px');
  }
)
#console {
  background-color: black;
  color: white;
}
#measureBar {
  display: none;
}
#measureBar,
#console {
  font-family: courier;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id="measureBar">12345678901234567890</span>
<div id="console">12345678901234567890 Text that should be twenty characters wide.</div>

This can be done using pure css using the vw (viewport width), vh (viewport height), vmin (relative to width or height, whichever is smaller), vmax (relative to width or height, whichever is larger) length Units.

For browser compatibility you can check this out.

For a simple tutorial check this out.

I was facing a very similar requirement and came up with a fairly straight forward solution. As soon as I get some down time, I'll be writing a jQuery plugin around this example code.

As was mentioned in some of the other answers here, the key is using em as your font-size unit. Additionally, to achieve a similar layout in all (modern) browsers, for a solution that must cope with various fixed-width fonts, the ruler method is the most reliable approach that I have found.

function fixieRemeasure() {
    var offset =  document.getElementById("fixie").offsetWidth,
        ruler = (document.getElementById("fixieRuler").offsetWidth * 1.01),
        fontSize = (offset / ruler);   
    document.getElementById("fixie").style.fontSize = fontSize + "em";     
}

function fixieInit(columns) {
    document.getElementById("fixieRuler").innerText = 
            new Array(columns + 1).join("X").toString();
    fixieRemeasure();
}

(function () {
    fixieInit(80); 
    window.onresize = function () { fixieRemeasure(); };       
} ());
#fixie {
    padding: 0 2px 0 6px;
    font-size: 1em;
    background-color: #000;
    color: #0F0;
}

#fixieRuler {
    font-size: 1em;
    visibility: hidden;
}
<code id="fixieRuler"></code>

<div id="fixie">
<pre>+------------------------------------------------------------------------------+
|                                                                              |
|                   ,%%%,                                                      |
|                 ,%%%` %==--                                                  |
|                ,%%`( '|                                                      |
|               ,%%@ /\_/                                                      |
|     ,%.-"""--%%% "@@__                                                       |
|    %%/             |__`\                                                     |
|   .%'\     |   \   /  //                                                     |
|   ,%' >   .'----\ |  [/                                                      |
|      < <<`       ||                                                          |
|       `\\\       ||                                                          |
|         )\\      )\                                                          |
| ^^^jgs^^"""^^^^^^""^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        |
|          ASCII Art: http://www.chris.com/ascii/index.php?art=animals/horses  |
+------------------------------------------------------------------------------+</pre>
</div>

This is only a proof of concept and there are a few fairly obvious issues baked in. Mainly, the resize method is called continuously and that should be replaced with something like onResizeEnd and properly attached to the DOM prior to production use. To componentize, I'd use a class selector instead of an id, and would also dynamically insert/remove the ruler element on-the-fly.

Hope it helps.

UPDATE

I created a new project, fixie.js on github, based on this example code. Still has a bit of work to be done in terms of performance and modularization, but it is works well in all browsers and I believe that it provides the simplest means to solve the issue with a minimal amount of boilerplate code.

The only requirements are that you set a font-size to your pre elements in em, and provide them a class name with the format of 'fixie_COLS':

<pre class="fixie_80">I'll be 80 columns</pre>

jQuery & webcomponent implementations to follow. It's MIT licensed and any contributions are welcome: https://github.com/dperish/fixie.js

发布评论

评论列表(0)

  1. 暂无评论