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.
转载于:https://stackoverflow.com/questions/16056591/font-scaling-based-on-width-of-container
EDIT: If the container is not the body CSS Tricks covers all of your options here: https://css-tricks.com/fitting-text-to-a-container/
If the container is the body, what you are looking for is Viewport-percentage lengths:
The viewport-percentage lengths are relative to the size of the initial containing block. When the height or width of the initial containing block is changed, they are scaled accordingly. However, when the value of overflow on the root element is auto, any scroll bars are assumed not to exist.
The values are:
vw
(% of the viewport width)vh
(% of the viewport height)vi
(1% of the viewport size in the direction of the root element's inline axis)vb
(1% of the viewport size in the direction of the root element's block axis)vmin
(the smaller of vw
or vh
)vmax
(the larger or vw
or vh
)1 v* is equal to 1% of the initial containing block.
using it looks like this:
p {
font-size: 4vw;
}
As you can see, when the viewport width increases, so does the font-size, without needing to use media queries.
These values are a sizing unit, just like px
or em
, so they can be used to size other elements as well, such was width, margin, or padding.
Browser support is pretty good, but you'll likely need a fallback, such as:
p {
font-size: 16px;
font-size: 4vw;
}
Check out the support statistics: http://caniuse.com/#feat=viewport-units.
Also, check out CSS-Tricks for a broader look: http://css-tricks.com/viewport-sized-typography/
Here's a nice article about setting min/max sizes and exercising a bit more control over the sizes: http://madebymike.com.au/writing/precise-control-responsive-typography/
And here's an article about setting your size using calc() so that the text fills the viewport: http://codepen.io/CrocoDillon/pen/fBJxu
Also, please view this article, which uses a technique dubbed 'molten leading' to adjust the line-height as well. https://css-tricks.com/molten-leading-css/
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.
Inside your CSS try adding this at the bottom changing the 320px width for wherever your design starts breaking:
@media only screen and (max-width: 320px) {
body { font-size: 1em; }
}
Then give the font-size in "px" or "em" as you wish.
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.
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.
Have you tried http://simplefocus.com/flowtype/ ?
This is what I use for my sites and has worked perfectly. Hope it helps.
are you looking for something like this? =>
http://jsfiddle.net/sijav/dGsC9/4/
http://fiddle.jshell.net/sijav/dGsC9/4/show/
I have used flowtype and it's working great (however it's js and not pure css solution)
$('body').flowtype({
minFont : 10,
maxFont : 40,
minimum : 500,
maximum : 1200,
fontRatio : 70
});
There is a big philosophy for this issue. The easiest thing to do would be to give a certain font-size to body (i recommand 10), and then all the other element would have their font in em or rem. I'll give you and example to understand those units. Em is always relative to his parent
body{font-size:10px;}
.menu{font-size:2em;} /* that means 2*10px = 20px */
.menu li{font-size:1.5em;} /* that means 1.5*20px = 30px */
Rem is always relative to body
body{font-size:10px;}
.menu{font-size:2rem;} /* that means 2*10px = 20px */
.menu li{font-size:1.5rem;} /* that means 1.5*10px = 15px */
And than you could create a script that would modify font-size relative to your container width. But this isn't what I would recomand. Because in a 900px width container for example you would have a p
element with 12px font-size let's say. And on your ideea that would become on 300px width container at 4px font-size. There has to be a lower limit. Another solutions would be with media queries, so that you could set font for different widths.
But the solutions that I would recommand is to use a javascript library that helps you with that. And fittext.js that I found so far.
Updated because I got a down vote.
Here is the function:
document.body.setScaledFont = function(f) {
var s = this.offsetWidth, fs = s * f;
this.style.fontSize = fs + '%';
return this
};
Then convert all your documents child element font sizes to em's or %.
Then add something like this to your code to set the base font size.
document.body.setScaledFont(0.35);
window.onresize = function() {
document.body.setScaledFont(0.35);
}
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;
};
This may not be super practical, but if you want font to be a direct function of the parent, without having any JS that listens/loops(interval) to read the size of the div/page, there is a way to do it. Iframes. Anything within the iframe will consider the size of the iframe as the size of the viewport. So the trick is to just make an iframe whose width is the maximum width you want your text to be, and whose height is equal to the maximum height * the particular text's aspect ratio.
Setting aside the limitation that viewport units can't also come along side parent units for text (as in, having the % size behave like everyone else), viewport units do provide a very powerful tool:being able to get the min/max dimension. You can't do that anywhere else - you can't say..make the height of this div be the width of the parent * something.
That being said, the trick is to use vmin, and to set the iframe size so that [fraction] * total height is a good font size when the height is the limiting dimension, and [fraction] * total width when the width is the limiting dimension. This is why the heigh has to be a product of the width and the aspect ratio.
for my particular example, you have
.main iframe{
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: calc(3.5 * 100%);
background: rgba(0,0,0,0);
border-style: none;
transform: translate3d(-50%,-50%,0);
}
The small annoyance with this method is that you have to manually set the CSS of the iframe. If you attach the whole CSS file, that would take up a lot of bandwidth for many text areas. So, what I do is attach the rule that I want directly from my CSS.
var rule = document.styleSheets[1].rules[4];
var iDoc = document.querySelector('iframe').contentDocument;
iDoc.styleSheets[0].insertRule(rule.cssText);
You can write small function that gets the CSS rule / all CSS rules that would affect the text area.
I cannot think of another way to do it without having some cycling/listening JS. The real solution would be for browsers to provide a way to scale text as a function of the parent container AND to also provide the same vmin/vmax type functionality.
JS fiddle: https://jsfiddle.net/0jr7rrgm/3/ (click once to lock the red square to the mouse, click again to release)
Most of the JS in the fiddle is just my custom click-drag function
Solution with SVG:
<div style="width: 60px;">
<svg width="100%" height="100%" viewBox="0 -200 1000 300"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<text font-size="300" fill="black">Text</text>
</svg>
</div>
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);
What I do in one of my projects is a "mixture" between vw and vh to adjust the font size to my needs, eg.:
font-size: calc(3vw + 3vh);
I know this doesn't answer the op's question, but maybe it can be a solution to anyone else.
If the issue is font getting too big on widescreen desktop, I think the easiest css method would be something like this (assuming wrapper max 1000px wide)
.specialText{
font-size:2.4vw;
}
@media only screen and (min-width: 1000px) {
.specialText {
width:24px;
}
}
So it is auto sized for any screen smaller than the max width of your container, and fixed size when screen is wider (like virtually all desktop/laptops)
calc()
, CSS units and mathThis 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.
You will need:
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.
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.
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.
Always have your element with this attribute:
Javascript: element.style.fontSize = "100%";
or
CSS: style = "font-size: 100%;"
When you go fullscreen, you should already have a scale variable calculated (scale > 1 or scale = 1). Then, on fullscreen:
document.body.style.fontSize = (scale * 100) + "%";
It works nicely with little code.
This web component changes the font size so the inner text width matches the container width. Check the demo.
You can use it like this:
<full-width-text>Lorem Ipsum</full-width-text>
As a JavaScript fallback (or your sole solution), you can use my jQuery Scalem plugin, which lets you scale relative to the parent element (container) by passing the reference
option.
I've prepared simple scale function using css transform instead of font-size. You can use it inside of any container, you don't have to set media queries etc :)
Blog post: https://blog.polarbits.co/2017/03/07/full-width-css-js-scalable-header/
The code:
function scaleHeader() {
var scalable = document.querySelectorAll('.scale--js');
var margin = 10;
for (var i = 0; i < scalable.length; i++) {
var scalableContainer = scalable[i].parentNode;
scalable[i].style.transform = 'scale(1)';
var scalableContainerWidth = scalableContainer.offsetWidth - margin;
var scalableWidth = scalable[i].offsetWidth;
scalable[i].style.transform = 'scale(' + scalableContainerWidth / scalableWidth + ')';
scalableContainer.style.height = scalable[i].getBoundingClientRect().height + 'px';
}
}
Working demo: https://codepen.io/maciejkorsan/pen/BWLryj
take look at my code it make the font size smaller
to fit
whatever there
but I think this doesn't led to good user experience
var containerWidth = $("#ui-id-2").width();
var items = $(".quickSearchAutocomplete .ui-menu-item");
var fontSize = 16;
items.each(function(){
//display value depend sometimes on your case you may make it block or inline-table instead of inline-block or whatever value that make the div take overflow width
$(this).css({"whiteSpace":"nowrap","display":"inline-block"});
while ($(this).width() > containerWidth){
console.log("$(this).width()"+ $(this).width() + "containerWidth" + containerWidth)
$(this).css("font-size", fontSize -= 0.5);
}
});
hope this help you
In case it's helpful to anyone, most of the solutions in this thread were wrapping text into multiple lines, form e.
But then I found this, and it worked:
https://github.com/chunksnbits/jquery-quickfit
Example usage:
$('.someText').quickfit({max:50,tolerance:.4})
Try to use fitText plugin, cause Viewport sizes isn't the solution of this problem. Just add library
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script src="jquery.fittext.js"></script>
and change font-size for correct by settings the coefficient of text:
$("#text_div").fitText(0.8);
you can set max and min values of text:
$("#text_div").fitText(0.8, { minFontSize: '12px', maxFontSize: '36px' });
Using vw
, em
& co. works for sure, but imo always needs a human's touch for fine-tuning.
Here's a script I just wrote based on @tnt-rox' answer that tries to automatize that human's touch:
$('#controller').click(function(){
$('h2').each(function(){
var
$el = $(this),
max = $el.get(0),
el = null
;
max =
max
? max.offsetWidth
: 320
;
$el.css({
'font-size': '1em',
'display': 'inline',
});
el = $el.get(0);
el.get_float = function(){
var
fs = 0
;
if (this.style && this.style.fontSize) {
fs = parseFloat(this.style.fontSize.replace( /([\d\.]+)em/g, '$1' ));
}
return fs;
};
el.bigger = function(){
this.style.fontSize = (this.get_float() +0.1) +'em';
};
while ( el.offsetWidth < max ) {
el.bigger();
}
// finishing touch.
$el.css({
'font-size': ((el.get_float() -0.1) +'em'),
'line-height': 'normal',
'display': '',
});
}); // end of ( each )
}); // end of ( font scaling test )
div {
width: 50%;
background-color: tomato;
font-family: 'Arial';
}
h2 {
white-space: nowrap;
}
h2:nth-child(2) {
font-style: italic;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input type="button" id="controller" value="Apply" />
<div>
<h2>Lorem ipsum dolor</h2>
<h2>Test String</h2>
<h2>Sweet Concatenation</h2>
<h2>Font Scaling</h2>
</div>
It basically reduces the font-size to 1em
and then starts incrementing by 0.1 until it reaches maximum width.
</div>
For dynamic text , this plugin is quite useful http://freqdec.github.io/slabText/. Simply add css
.slabtexted .slabtext
{
display: -moz-inline-box;
display: inline-block;
white-space: nowrap;
}
.slabtextinactive .slabtext
{
display: inline;
white-space: normal;
font-size: 1em !important;
letter-spacing: inherit !important;
word-spacing: inherit !important;
*letter-spacing: normal !important;
*word-spacing: normal !important;
}
.slabtextdone .slabtext
{
display: block;
}
and script
$('#mydiv').slabText();
In order to make font-size fit its container, rather than the window see the resizeFont()
function I have shared in this question (a combination of other answers, most of which are already linked here). It is triggered using window.addEventListener('resize', resizeFont);
.
Vanilla JS: Resize font-awesome to fit container
function resizeFont() {
var elements = document.getElementsByClassName('resize');
console.log(elements);
if (elements.length < 0) {
return;
}
_len = elements.length;
for (_i = 0; _i < _len; _i++) {
var el = elements[_i];
el.style.fontSize = "100%";
for (var size = 100; el.scrollHeight > el.clientHeight; size -= 10) {
el.style.fontSize = size + '%';
}
}
}
You could perhaps use vw/vh as a fallback, so you dynamically assign em
or rem
units using javascript, ensuring that the fonts do scale to the window if javascript is disabled.
Apply the .resize
class to all elements containing text you wish to be scaled.
Trigger the function prior to adding the window resize event listener. Then, any text which doesn't fit its container will be scaled down when the page loads, as well as when it is resized.
NOTE: The default font-size
must be set to either em
,rem
or %
to achieve proper results... and if the styling of your elements is too complex then the browser might crash if you start frantically resizing the window.
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
There is a way to do this WITHOUT javascript!
You can use an inline svg. You can use css on an svg if it is inline. You have to remember that using this method means your svg will respond to its container size.
Try using the following solution...
HTML
<div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360.96 358.98" >
<text>SAVE $500</text>
</svg>
</div>
CSS
div {
width: 50%; /* set your container width */
height: 50%; /* Set your container height */
}
svg {
width: 100%;
height: auto;
}
text {
transform: translate(40px, 202px);
font-size: 62px;
fill: #000;
}
example: https://jsfiddle.net/k8L4xLLa/32/
Want something more flashy?
SVGs also allow you to do cool stuff with shapes and junk. Check out this great use case for scalable text...
I don't see any answer with reference to CSS flex property, but it can be very useful too.
Straight up media queries seems like a much simpler and more understandable solution to resizing font based on container sizes that may be dynamic.
The below resizes the font to the size of the container, whether the container is scaled to the size of the viewport, or if it's reached its max value. If you have non-100% wide containers, you could adjust the vw
accordingly.
.container {
width: 100%;
max-width: 600px;
}
.headline {
font-size: 20vw;
}
.subhead {
font-size: 5vw;
}
@media (min-width:600px) {
.headline {
font-size: 120px;
}
.subhead {
font-size: 32px;
}
}
No one has mentioned CSS variables yet, and this approach worked best for me, so:
Let's say you've got a column on your page that is 100% of the width of a mobile user's screen, but has a max-width
of 800px, so on desktop there's some space on either side of the column. Put this at the top of your page:
<script> document.documentElement.style.setProperty('--column-width', Math.min(window.innerWidth, 800)+'px'); </script>
And now you can use that variable (instead of the built-in vw
unit) to set the size of your font. E.g.
p {
font-size: calc( var(--column-width) / 100 );
}
It's not a pure CSS approach, but it's pretty close.