Font scaling based on width of container

2018-12-31 01:21发布

I'm having a hard time getting my head around font scaling.

I currently have this site with a body font-size of 100%. 100% of what though? This seems to compute out at 16px.

I was under the impression that 100% would somehow refer to the size of the browser window, but apparently not because it's always 16px whether the window is resized down to a mobile width or full blown widescreen desktop.

How can I make the text on my site scale in relation to its container? I tried using em but this doesn't scale either.

My reasoning is that things like my menu become squished when you resize, so I need to reduce the px font-size of .menuItem among other elements in relation to the width of the container. (E.g in the menu on a large desktop, 22px works perfectly. Move down to tablet width and 16px is more appropriate.)

I'm aware I can add breakpoints, but I really want the text to scale as WELL as having extra breakpoints, otherwise I'll end up with hundreds of breakpoints for every 100px decrease in width to control the text.

30条回答
柔情千种
2楼-- · 2018-12-31 02:07

But what if the container is not the viewport (body)?

This question is asked in comment by Alex under the accepted answer.

That fact does not mean vw cannot be used to some extent to size for that container. Now to see any variation at all one has to be assuming that the container in some way is flexible in size. Whether through a direct percentage width or through being 100% minus margins. The point becomes "moot" if the container is always set to, let's say, 200px wide--then just set a font-size that works for that width.

Example 1

With a flexible width container, however, it must be realized that in some way the container is still being sized off the viewport. As such, it is a matter of adjusting a vw setting based off that percentage size difference to the viewport, which means taking into account the sizing of parent wrappers. Take this example:

div {
    width: 50%;
    border: 1px solid black;
    margin: 20px;
    font-size: 16px;
    /* 100 = viewport width, as 1vw = 1/100th of that
       So if the container is 50% of viewport (as here)
       then factor that into how you want it to size.
       Let's say you like 5vw if it were the whole width,
       then for this container, size it at 2.5vw (5 * .5 [i.e. 50%])
    */
    font-size: 2.5vw; 
}

Assuming here the div is a child of the body, it is 50% of that 100% width, which is the viewport size in this basic case. Basically, you want to set a vw that is going to look good to you. As you can see in my comment in the above css, you can "think" through that mathematically with respect to the full viewport size, but you don't need to do that. The text is going to "flex" with the container, because the container is flexing with the viewport resizing. UPDATE: here's an example of two differently sized containers.

Example 2

You can help ensure viewport sizing by forcing the calculation based off that. Consider this example:

html {width: 100%;} /* force html to be viewport width */
body {width: 150%; } /* overflow the body */

div {
    width: 50%;
    border: 1px solid black;
    margin: 20px;
    font-size: 16px;
    /* 100 = viewport width, as 1vw = 1/100th of that
       Here, the body is 150% of viewport, but the container is 50% 
       of viewport, so both parents factor  into how you want it to size.
       Let's say you like 5vw if it were the whole width,
       then for this container, size it at 3.75vw 
       (5 * 1.5 [i.e. 150%]) * .5 [i.e. 50%]
    */
    font-size: 3.75vw; 
}

The sizing is still based off viewport, but is in essence set up based off the container size itself.

Should Sizing of the Container Change Dynamically...

If sizing of the container element ended up changing dynamically its percentage relationship either via @media break points or via javascript, then whatever the base "target" was would need recalculation to maintain the same "relationship" for text sizing.

Take Example #1 above. If the div was switched to 25% width by either @media or javascript, then at the same time, the font-size would need to adjust in either the media query or by javascript to the new calculation of 5vw * .25 = 1.25. This would put the text size at the same size it would have been had the "width" of the original 50% container been reduced by half from viewport sizing, but has now been reduced due to a change in its own percentage calculation.

A Challenge

With the CSS3 calc() function in use, it would become difficult to adjust dynamically, as that function does not work for font-size purposes at this time. So you could not do a pure CSS3 adjustment if your width is changing on calc(). Of course, a minor adjustment of width for margins may not be enough to warrant any change in font-size, so it may not matter.

查看更多
回忆,回不去的记忆
3楼-- · 2018-12-31 02:07

Pure-CSS solution with calc(), CSS units and math

This is precisely not what OP asks, but may make someone's day. This answer is not spoon-feedingly easy and needs some researching in developer end.

I came finally to get a pure-CSS solution for this using calc() with different units. You will need some basic mathematical understanding of formulas to work out your expression for calc().

When I worked this out, I had to get a full-page-width responsive header with some padding few parents up in DOM. I'll use my values here, replace them with your own.

To mathematics

You will need:

  • nicely adjusted ratio in some viewport, I used 320px, thus I got 24px high and 224px wide so ratio is 9.333... or 28 / 3
  • the container width, I had padding: 3em and full width so this got to 100wv - 2 * 3em

X is the width of container so replace it with your own expression or adjust the value to get full-page text. R is the ratio you will have. You can get it by adjusting the values in some viewport, inspecting element width and height and replacing them with your own values. Also, it is width / heigth ;)

x = 100vw - 2 * 3em = 100vw - 6em
r = 224px/24px = 9.333... = 28 / 3

y = x / r
  = (100vw - 6em) / (28 / 3)
  = (100vw - 6em) * 3 / 28
  = (300vw - 18em) / 28
  = (75vw - 4.5rem) / 7

And bang! It worked! I wrote

font-size: calc((75vw - 4.5rem) / 7)

to my header and it adjusted nicely in every viewport.

But how does it work?

We need some constants up here. 100vw means the full width of viewport, and my goal was to establish full-width header with some padding.

The ratio. Getting a width and height in one viewport got me a ratio to play with, and with ratio I know what the height should be in other viewport width. Calculating them with hand would take plenty of time and at least take lots of bandwith so it's not a good answer.

Conclusion

I wonder why no-one has figured this out and some people are even telling that this would be impossible to tinker with CSS. I don't like to use Javascript in adjusting elements, so I don't accept JS or even jQuery answers without digging more. All in all, it's good that this got figured out and this is one step to pure-CSS implementations in website design.

I apologize of any unusual convention in my text, I'm not native speaker in english and also quite new to writing SO answers.

Edit: it should also be noted that we have evil scrollbars in some browsers. For example, when using Firefox I noticed that 100vw means the full width of viewport, extending under scrollbar (where content cannot expand!), so the fullwidth text has to be margined carefully and preferably get tested with many browsers and devices.

查看更多
刘海飞了
4楼-- · 2018-12-31 02:09

My own solution, jQuery based, works by gradually increasing the font size until the container gets a big increase in height (meaning it got a line break). It's pretty simple but works fairly well and it very easy to use. You don't have to know ANYTHING about the font being used, everything is taken care of by the browser.

You can play with it on http://jsfiddle.net/tubededentifrice/u5y15d0L/2/

The magic happens here:

var setMaxTextSize=function(jElement) {
    //Get and set the font size into data for reuse upon resize
    var fontSize=parseInt(jElement.data(quickFitFontSizeData)) || parseInt(jElement.css("font-size"));
    jElement.data(quickFitFontSizeData,fontSize);

    //Gradually increase font size until the element gets a big increase in height (ie line break)
    var i=0;
    var previousHeight;
    do
    {
        previousHeight=jElement.height();
        jElement.css("font-size",""+(++fontSize)+"px");
    }
    while(i++<300 && jElement.height()-previousHeight<fontSize/2)

    //Finally, go back before the increase in height and set the element as resized by adding quickFitSetClass
    fontSize-=1;
    jElement.addClass(quickFitSetClass).css("font-size",""+fontSize+"px");

    return fontSize;
};
查看更多
千与千寻千般痛.
5楼-- · 2018-12-31 02:10

My problem was similar but related to scaling text within a heading. I tried Fit Font but I needed to toggle the compressor to get any results, since it was solving a slightly different problem, as was Text Flow. So I wrote my own little plugin that reduces the font size to fit the container, assuming you have overflow: hidden and white-space: nowrap so that even if reducing the font to the minimum doesn't allow showing the full heading, it just cuts off what it can show.

(function($) {

  // Reduces the size of text in the element to fit the parent.
  $.fn.reduceTextSize = function(options) {
    options = $.extend({
      minFontSize: 10
    }, options);

    function checkWidth(em) {
      var $em = $(em);
      var oldPosition = $em.css('position');
      $em.css('position', 'absolute');
      var width = $em.width();
      $em.css('position', oldPosition);
      return width;
    }

    return this.each(function(){
      var $this = $(this);
      var $parent = $this.parent();
      var prevFontSize;
      while (checkWidth($this) > $parent.width()) {
        var currentFontSize = parseInt($this.css('font-size').replace('px', ''));
        // Stop looping if min font size reached, or font size did not change last iteration.
        if (isNaN(currentFontSize) || currentFontSize <= options.minFontSize ||
            prevFontSize && prevFontSize == currentFontSize) {
          break;
        }
        prevFontSize = currentFontSize;
        $this.css('font-size', (currentFontSize - 1) + 'px');
      }
    });

  };

})(jQuery);
查看更多
萌妹纸的霸气范
6楼-- · 2018-12-31 02:11

This worked for me:

I try to approx font-size based on a width/heght got from setting font-size:10px. Basically the idea is "if i have 20px width and 11px height with font-size:10px, so what would it be the max font-size to math a container of 50px width and 30px height?"

The answer is a double proportion system:

{ 20:10=50:X, 11:10=30:Y } = { X= (10*50)/20 , Y= (10*30)/11 }

Now X is a font-size that will match width, Y is a font-size that will match height; take the smallest value

function getMaxFontSizeApprox(el){
    var fontSize = 10;
    var p = el.parentNode;

    var parent_h = p.offsetHeight ? p.offsetHeight : p.style.pixelHeight;
    if(!parent_h)parent_h = 0;

    var parent_w = p.offsetHeight ? p.offsetWidth : p.style.pixelWidth;
    if(!parent_w)parent_w = 0;

    el.style.fontSize = fontSize + "px";

    var el_h = el.offsetHeight ? el.offsetHeight : el.style.pixelHeight;
    if(!el_h)el_h = 0;

    var el_w = el.offsetHeight ? el.offsetWidth : el.style.pixelWidth;
    if(!el_w)el_w = 0;

    //0.5 is the error on the measure that js does
    //if real measure had been 12.49 px => js would have said 12px
    //so we think about the worst case when could have, we add 0.5 to 
    //compensate the round error
    var fs1 = (fontSize*(parent_w + 0.5))/(el_w + 0.5);
    var fs2 = (fontSize*(parent_h) + 0.5)/(el_h + 0.5);

    fontSize = Math.floor(Math.min(fs1,fs2));
    el.style.fontSize = fontSize + "px";
    return fontSize;
}

NB: the argument of the function must be a span element or an element wich is smaller than it's parent, otherwise if children and parent have both the same width/height function will fail

查看更多
人间绝色
7楼-- · 2018-12-31 02:12

100% is relative to the base font size, which if you haven't set it would be the browser's user-agent default.

To get the effect you're after I would use a piece of javascript to adjust the base font size relative to the window dimensions.

查看更多
登录 后发表回答