简体   繁体   中英

Scale font size based on content (CSS or JS)

Note, I am not asking to scale font based on size of viewport - my question is different.

My HTML/CSS/JS app essentially presents a state diagram to the user. There's a message on the screen with a number of buttons. Pressing each button leads to a different screen - and so on - very simple notion, indeed.

The text for each screen as each button comes from the database.

The design is such that the button sizes are fixed (I don't have any control over it). They are large enough to fit pretty much anything that the application can encounter - in English.

Recently, I needed to add Swahili support - and all of a sudden something that's 2 words, 15 characters in English became 5 words with almost 50 characters in Swahili - and it doesn't fit into the button. This is rather bad - yet there are only a handful of such long button labels. Unfortunately, as they are dynamic (from the DB), I can't do much in terms of predicting what it will be.

The thing I can do is to decrease the font size to fit the text inside the button. Using vw , vh , etc. units isn't going to work, as the text should be scaled to a fixed size button regardless of the viewport size.

I can't think of any CSS-only solution. I can think of a couple of JS solution to the problem (eg place the text, then keep scaling it down until it fits - although I'm not quite sure how to determine that it fits).

I just can't quite figure this one out. Any ideas? I'm happy with CSS3 and HTML5 and only need this to work in latest versions of Chrome and Safari.

Check out this jsbin: http://jsbin.com/sunamej/1/edit?html,css,js,output

the text scales according to a calculation done on a dummy text container with white-space: nowrap . This allows for the total width of the text to be compared to that of the button, and be scaled down proportionally. Keep in mind that there is some error due to not taking into account where the words will wrap. This is largely mitigated by the error factor included, but it may need to be fine tuned for your application. Actually calculating out something to completely take into account the location of word wrapping would be arduous and not work the time for an application like this with only a few words/lines. Here is the code from the bin in full as well:

HTML:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <button class="btn">
    <span class="dummy-text">
      Testing with some large text that should scale
    </span>
    <span class="text">
      Testing with some large text that should scale
    </span>
  </button>
  <button class="btn">
    <span class="dummy-text">
      Testing with some
    </span>
    <span class="text">
      Testing with some
    </span>
  </button>
  <button class="btn">
    <span class="dummy-text">
      Testing with some large text
    </span>
    <span class="text">
      Testing with some large text
    </span>
  </button>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
</body>
</html>

CSS:

.btn {
  /* hardcoded dimens */
  height: 35px;
  width: 75px;
}

.dummy-text {
  /* hide dummy, don't wrap */
  display: none;
  white-space: nowrap;
}

Javascript:

console.clear();
// NOTE: use of jQuery is arbitrary; all functions
// are available in vanilla js

function scaleText(btn) {
  // select the dummy text
  var dtxt = btn.find('.dummy-text');
  // select the real text
  var txt = btn.find('.text');

  // get te with of the button and the dummy
  var tw = dtxt.width();
  var bw = btn.width();

  // number of lines of text the button allows
  var lines = 2;
  // factoring in error caused by wrapping; this
  // may need fine tuning for your application
  var err = 0.85;
  // get the fonts scale
  var scale = (bw * lines * err)/tw;

  // get the current font size
  var sz = parseInt(dtxt.css('font-size').replace('px', ''));
  // scale appropiately
  txt.css('font-size', sz * scale + 'px');
}

// apply to each button
var btns = $('.btn');
btns.each(function(i, btn) {
  scaleText($(btn));
});

Inspired by Adjust size of an SVG-element to its content , it would require more fine tuning and a way to split long text to appropriate number of lines, maybe wrapping with foreignObject if you can guess width/height from the length of text... But here is a possible start:

 [...document.getElementsByTagName("svg")].forEach(svg => { const bbox = svg.getBBox(); svg.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")); }) 
 button { width: 100px; height: 50px; padding: 0; } svg { width: 100%; height: 100%; } 
 <button> <svg> <text>Short</text> </svg> </button> <button> <svg> <text>Longer text</text> <text y='1em'>split externally</text> <text y='2em'>to balanced lines</text> </svg> </button> 

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