Change font-size to fit text width to parent element [duplicate] - html

I'm having a hard time getting my head around font scaling.
I currently have a website with a body font-size of 100%. 100% of what though? This seems to compute out at 16 pixels.
I was under the impression that 100% would somehow refer to the size of the browser window, but apparently not because it's always 16 pixels 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. (For example, 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 100pixels decrease in width to control the text.

If the container is not the body, CSS Tricks covers all of your options in 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 do 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 as 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: Viewport Sized Typography
Here's a nice article about setting minimum/maximum sizes and exercising a bit more control over the sizes: Precise control over 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. Molten Leading in CSS

But what if the container is not the viewport (body)?
This question is asked in a comment by Alex under 2507rkt3's 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 content, 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. 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 the Size of the Container Change Dynamically...
If the sizing of the container element ended up changing dynamically its percentage relationship either via #media breakpoints 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 CSS 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 CSS 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.

Solution with SVG:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 75px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 75"
preserveAspectRatio="xMinYMid meet"
style="background-color:green"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<text
x="0"
y="75"
font-size="75"
fill="black"
>█Resize This█</text>
</svg>
</div>
Solution with SVG and text-wrapping using foreignObject:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 200px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 200"
preserveAspectRatio="xMinYMin meet"
>
<foreignObject width="100%" height="100%" xmlns="http://www.w3.org/1999/xhtml">
<div xmlns="http://www.w3.org/1999/xhtml" style="background-color:lightgreen;">
<h1>heading</h1>
<p>Resize the blue box.</p>
</div>
</foreignObject>
</svg>
</div>

In one of my projects I use a "mixture" between vw and vh to adjust the font size to my needs, for example:
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.

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 on the 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 320 pixels, thus I got 24 pixels high and 224 pixels wide, so the 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 bandwidth, 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 JavaScript (and forget about 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 am also quite new to writing Stack Overflow answers.
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.

There is a big philosophy for this issue.
The easiest thing to do would be to give a certain font-size to body (I recommend 10), and then all the other element would have their font in em or rem.
I'll give you an example to understand those units.
Em is always relative to its parent:
body{font-size: 10px;}
.menu{font-size: 2em;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5em;} /* That means 1.5*20 pixels = 30 pixels */
Rem is always relative to body:
body{font-size: 10px;}
.menu{font-size: 2rem;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5rem;} /* that means 1.5*10 pixels = 15 pixels */
And then you could create a script that would modify font-size relative to your container width.
But this isn't what I would recommend. Because in a 900 pixels width container for example you would have a p element with a 12 pixels font-size let's say. And on your idea that would become an 300 pixels wide container at 4 pixels font-size. There has to be a lower limit.
Other solutions would be with media queries, so that you could set font for different widths.
But the solutions that I would recommend is to use a JavaScript library that helps you with that. And fittext.js that I found so far.

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);
}
http://jsfiddle.net/0tpvccjt/

There is a way to do this without JavaScript!
You can use an inline SVG image. You can use CSS on an SVG if it is inline. You have to remember that using this method means your SVG image 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?
SVG images also allow you to do cool stuff with shapes and junk. Check out this great use case for scalable text...
https://jsfiddle.net/k8L4xLLa/14/

CSS Container Queries
A late-2022 addition to the CSS feature set makes scaling font size with containers straightforward.
Container queries come with a new set of CSS units cqw/cqh (container query width/height). To use them you need to set the container-type property on the parent element whose size you want to use. Minimal example:
<div>
<p>Lorem ipsum dolor sit amet</p>
</div>
<style>
div {
container-type: inline-size;
}
p {
font-size: 5cqw;
}
</style>
The font size will increase smoothly as the container grows. At 1000px container width, the p font size will be 1000px / 100 * 5 = 50px.
container-type can be size or inline-size. size tracks both height and width of the container which allows you to use both cqw and cqh.
Most of the time on the web, heights are calculated based on content and you only specify the width. To save the browser some work, you'll generally want to set container-type: inline-size; so the browser only tracks the inline dimension which is usually width (unless you set writing-mode to vertical).
Browser support for container queries has grown rapidly in the 2nd half of 2022 and currently stands at 75% (2023-01-01).

This may not be super practical, but if you want a font to be a direct function of the parent, without having any JavaScript 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 minimum/maximum 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 height 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 JavaScript. 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.
JSFiddle:
https://jsfiddle.net/0jr7rrgm/3/
(click once to lock the red square to the mouse, and click again to release)
Most of the JavaScript in the fiddle is just my custom click-drag function.

Use CSS Variables
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.

Using vw, em & co. works for sure, but IMO it 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.
JSFiddle

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 code to adjust the base font size relative to the window dimensions.

Artistically, if you need to fit two or more lines of text within the same width regardless of their character count then you have nice options.
It's best to find a dynamical solution so whatever text is entered we end up with a nice display.
Let's see how we may approach.
var els = document.querySelectorAll(".divtext"),
refWidth = els[0].clientWidth,
refFontSize = parseFloat(window.getComputedStyle(els[0],null)
.getPropertyValue("font-size"));
els.forEach((el,i) => el.style.fontSize = refFontSize * refWidth / els[i].clientWidth + "px")
#container {
display: inline-block;
background-color: black;
padding: 0.6vw 1.2vw;
}
.divtext {
display: table;
color: white;
font-family: impact;
font-size: 4.5vw;
}
<div id="container">
<div class="divtext">THIS IS JUST AN</div>
<div class="divtext">EXAMPLE</div>
<div class="divtext">TO SHOW YOU WHAT</div>
<div class="divtext">YOU WANT</div>
</div>
All we do is to get the width (els[0].clientWidth) and the font size (parseFloat(window.getComputedStyle(els[0],null).getPropertyValue("font-size"))) of the first line as a reference and then just calculate the subsequent lines font size accordingly.

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>

You may be 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 JavaScript and not a pure CSS solution):
$('body').flowtype({
minFont: 10,
maxFont: 40,
minimum: 500,
maximum: 1200,
fontRatio: 70
});

I've prepared a 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:
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

Inside your CSS, try adding this at the bottom changing the 320 pixels 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.

Try http://simplefocus.com/flowtype/. This is what I use for my sites, and it has worked perfectly.

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 is 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 (i.e. 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;
};

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 reduced 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);

Try to use the fitText plugin, because Viewport sizes isn't the solution of this problem.
Just add the library:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
And change font-size for correct by settings the coefficient of text:
$("#text_div").fitText(0.8);
You can set maximum and minimum values of text:
$("#text_div").fitText(0.8, { minFontSize: '12px', maxFontSize: '36px' });

Here is a pure CSS solution with the understanding that you admit breakpoints are necessary but also want text scaling:
I'm aware I can add breakpoints, but I really want the text to scale
as well as having extra breakpoints, otherwise....
Here is an approach using:
Custom properties
Media queries for breakpoints
clamp() (browser support in Feb 2022 is pretty good at 93%)
calc()
If one common scaling factor can be used to control ALL the text scaling within a container per screen max-width, all you need to do is scale a custom property per max-width, and apply this factor to 1 calculation.
A basic setup starts like this:
:root {
--scaling-factor: 1
}
.parent {
font-size: 30px
}
.largest {
font-size: clamp(60%, calc(var(--scaling-factor) * 100%), 100%);
}
.middle {
font-size: clamp(60%, calc(var(--scaling-factor) * 85%), 100%);
}
.smallest {
font-size: clamp(60%, calc(var(--scaling-factor) * 70%), 100%);
}
Then nest your media queries something like this (or whatever you need for your breakpoints):
#media (max-width: 1200px) {
:root {
--scaling-factor: 0.9
}
#media (max-width: 800px) {
:root {
--scaling-factor: 0.8
}
#media (max-width: 600px) {
:root {
--scaling-factor: 0.5 /* nope, because the font-size is floored at 60% thanks to clamp() */
}
}
}
}
This minimizes your media query markup.
Advantages
One custom property controls ALL scaling ... no need to add multiple declarations per media breakpoint
The use of clamp() sets a lower-limit on what the font-size should be, so you ensure your text is never too small (here the floor is 60% of the parent's font-size)
Please see this JSFiddle for a demo. Resize the window until at the smallest widths, the paragraphs are all the same font-size.

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.

Take look at my code. It makes the font size smaller to fit whatever there.
But I think this doesn't lead to a good user experience
var containerWidth = $("#ui-id-2").width();
var items = $(".quickSearchAutocomplete .ui-menu-item");
var fontSize = 16;
items.each(function(){
// Displaying a value depends 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);
}
});

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 the script:
$('#mydiv').slabText();

This worked for me:
I try to approximate font-size based on a width/height got from setting `font-size: 10px`. Basically, the idea is "if I have 20 pixels width and 11 pixels height with `font-size: 10px`, so what would it be the maximum font-size to math a container of 50 pixels width and 30 pixels 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, and 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 JavaScript does
// if the real measure had been 12.49 px => JavaScript 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 which is smaller than its parent, otherwise if children and parent have both the same width/height function will fail.

let textElement = document.getElementById('text1');
let parentElement = textElement.parentElement;
const parentClientHeight = parentElement.clientHeight;
const parentClientWidth = parentElement.clientWidth;
textElement.style.padding = "unset";
textElement.style.margin = "auto";
let fontSize = parentClientHeight;
let minFS = 3,
maxFS = fontSize;
while (fontSize != minFS) {
textElement.style.fontSize = `${fontSize}px`;
if (
parentElement.scrollHeight <= parentClientHeight &&
parentElement.scrollWidth <= parentClientWidth
) {
minFS = fontSize;
} else {
maxFS = fontSize;
}
fontSize = Math.floor((minFS + maxFS) / 2);
}
textElement.style.fontSize = `${minFS}px`;
<div style="height:200px; width:300px;">
<div id='text1'>
test
</div>
</div>

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.

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})

Related

Dynamic css font-size on a html div [duplicate]

I'm having a hard time getting my head around font scaling.
I currently have a website with a body font-size of 100%. 100% of what though? This seems to compute out at 16 pixels.
I was under the impression that 100% would somehow refer to the size of the browser window, but apparently not because it's always 16 pixels 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. (For example, 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 100pixels decrease in width to control the text.
If the container is not the body, CSS Tricks covers all of your options in 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 do 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 as 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: Viewport Sized Typography
Here's a nice article about setting minimum/maximum sizes and exercising a bit more control over the sizes: Precise control over 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. Molten Leading in CSS
But what if the container is not the viewport (body)?
This question is asked in a comment by Alex under 2507rkt3's 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 content, 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. 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 the Size of the Container Change Dynamically...
If the sizing of the container element ended up changing dynamically its percentage relationship either via #media breakpoints 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 CSS 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 CSS 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.
Solution with SVG:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 75px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 75"
preserveAspectRatio="xMinYMid meet"
style="background-color:green"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<text
x="0"
y="75"
font-size="75"
fill="black"
>█Resize This█</text>
</svg>
</div>
Solution with SVG and text-wrapping using foreignObject:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 200px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 200"
preserveAspectRatio="xMinYMin meet"
>
<foreignObject width="100%" height="100%" xmlns="http://www.w3.org/1999/xhtml">
<div xmlns="http://www.w3.org/1999/xhtml" style="background-color:lightgreen;">
<h1>heading</h1>
<p>Resize the blue box.</p>
</div>
</foreignObject>
</svg>
</div>
In one of my projects I use a "mixture" between vw and vh to adjust the font size to my needs, for example:
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.
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 on the 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 320 pixels, thus I got 24 pixels high and 224 pixels wide, so the 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 bandwidth, 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 JavaScript (and forget about 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 am also quite new to writing Stack Overflow answers.
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.
There is a big philosophy for this issue.
The easiest thing to do would be to give a certain font-size to body (I recommend 10), and then all the other element would have their font in em or rem.
I'll give you an example to understand those units.
Em is always relative to its parent:
body{font-size: 10px;}
.menu{font-size: 2em;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5em;} /* That means 1.5*20 pixels = 30 pixels */
Rem is always relative to body:
body{font-size: 10px;}
.menu{font-size: 2rem;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5rem;} /* that means 1.5*10 pixels = 15 pixels */
And then you could create a script that would modify font-size relative to your container width.
But this isn't what I would recommend. Because in a 900 pixels width container for example you would have a p element with a 12 pixels font-size let's say. And on your idea that would become an 300 pixels wide container at 4 pixels font-size. There has to be a lower limit.
Other solutions would be with media queries, so that you could set font for different widths.
But the solutions that I would recommend is to use a JavaScript library that helps you with that. And fittext.js that I found so far.
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);
}
http://jsfiddle.net/0tpvccjt/
There is a way to do this without JavaScript!
You can use an inline SVG image. You can use CSS on an SVG if it is inline. You have to remember that using this method means your SVG image 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?
SVG images also allow you to do cool stuff with shapes and junk. Check out this great use case for scalable text...
https://jsfiddle.net/k8L4xLLa/14/
CSS Container Queries
A late-2022 addition to the CSS feature set makes scaling font size with containers straightforward.
Container queries come with a new set of CSS units cqw/cqh (container query width/height). To use them you need to set the container-type property on the parent element whose size you want to use. Minimal example:
<div>
<p>Lorem ipsum dolor sit amet</p>
</div>
<style>
div {
container-type: inline-size;
}
p {
font-size: 5cqw;
}
</style>
The font size will increase smoothly as the container grows. At 1000px container width, the p font size will be 1000px / 100 * 5 = 50px.
container-type can be size or inline-size. size tracks both height and width of the container which allows you to use both cqw and cqh.
Most of the time on the web, heights are calculated based on content and you only specify the width. To save the browser some work, you'll generally want to set container-type: inline-size; so the browser only tracks the inline dimension which is usually width (unless you set writing-mode to vertical).
Browser support for container queries has grown rapidly in the 2nd half of 2022 and currently stands at 75% (2023-01-01).
This may not be super practical, but if you want a font to be a direct function of the parent, without having any JavaScript 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 minimum/maximum 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 height 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 JavaScript. 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.
JSFiddle:
https://jsfiddle.net/0jr7rrgm/3/
(click once to lock the red square to the mouse, and click again to release)
Most of the JavaScript in the fiddle is just my custom click-drag function.
Using vw, em & co. works for sure, but IMO it 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.
JSFiddle
Use CSS Variables
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.
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 code to adjust the base font size relative to the window dimensions.
Artistically, if you need to fit two or more lines of text within the same width regardless of their character count then you have nice options.
It's best to find a dynamical solution so whatever text is entered we end up with a nice display.
Let's see how we may approach.
var els = document.querySelectorAll(".divtext"),
refWidth = els[0].clientWidth,
refFontSize = parseFloat(window.getComputedStyle(els[0],null)
.getPropertyValue("font-size"));
els.forEach((el,i) => el.style.fontSize = refFontSize * refWidth / els[i].clientWidth + "px")
#container {
display: inline-block;
background-color: black;
padding: 0.6vw 1.2vw;
}
.divtext {
display: table;
color: white;
font-family: impact;
font-size: 4.5vw;
}
<div id="container">
<div class="divtext">THIS IS JUST AN</div>
<div class="divtext">EXAMPLE</div>
<div class="divtext">TO SHOW YOU WHAT</div>
<div class="divtext">YOU WANT</div>
</div>
All we do is to get the width (els[0].clientWidth) and the font size (parseFloat(window.getComputedStyle(els[0],null).getPropertyValue("font-size"))) of the first line as a reference and then just calculate the subsequent lines font size accordingly.
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>
You may be 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 JavaScript and not a pure CSS solution):
$('body').flowtype({
minFont: 10,
maxFont: 40,
minimum: 500,
maximum: 1200,
fontRatio: 70
});
I've prepared a 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:
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
Inside your CSS, try adding this at the bottom changing the 320 pixels 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.
Try http://simplefocus.com/flowtype/. This is what I use for my sites, and it has worked perfectly.
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 is 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 (i.e. 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;
};
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 reduced 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);
Try to use the fitText plugin, because Viewport sizes isn't the solution of this problem.
Just add the library:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
And change font-size for correct by settings the coefficient of text:
$("#text_div").fitText(0.8);
You can set maximum and minimum values of text:
$("#text_div").fitText(0.8, { minFontSize: '12px', maxFontSize: '36px' });
Here is a pure CSS solution with the understanding that you admit breakpoints are necessary but also want text scaling:
I'm aware I can add breakpoints, but I really want the text to scale
as well as having extra breakpoints, otherwise....
Here is an approach using:
Custom properties
Media queries for breakpoints
clamp() (browser support in Feb 2022 is pretty good at 93%)
calc()
If one common scaling factor can be used to control ALL the text scaling within a container per screen max-width, all you need to do is scale a custom property per max-width, and apply this factor to 1 calculation.
A basic setup starts like this:
:root {
--scaling-factor: 1
}
.parent {
font-size: 30px
}
.largest {
font-size: clamp(60%, calc(var(--scaling-factor) * 100%), 100%);
}
.middle {
font-size: clamp(60%, calc(var(--scaling-factor) * 85%), 100%);
}
.smallest {
font-size: clamp(60%, calc(var(--scaling-factor) * 70%), 100%);
}
Then nest your media queries something like this (or whatever you need for your breakpoints):
#media (max-width: 1200px) {
:root {
--scaling-factor: 0.9
}
#media (max-width: 800px) {
:root {
--scaling-factor: 0.8
}
#media (max-width: 600px) {
:root {
--scaling-factor: 0.5 /* nope, because the font-size is floored at 60% thanks to clamp() */
}
}
}
}
This minimizes your media query markup.
Advantages
One custom property controls ALL scaling ... no need to add multiple declarations per media breakpoint
The use of clamp() sets a lower-limit on what the font-size should be, so you ensure your text is never too small (here the floor is 60% of the parent's font-size)
Please see this JSFiddle for a demo. Resize the window until at the smallest widths, the paragraphs are all the same font-size.
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.
Take look at my code. It makes the font size smaller to fit whatever there.
But I think this doesn't lead to a good user experience
var containerWidth = $("#ui-id-2").width();
var items = $(".quickSearchAutocomplete .ui-menu-item");
var fontSize = 16;
items.each(function(){
// Displaying a value depends 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);
}
});
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 the script:
$('#mydiv').slabText();
This worked for me:
I try to approximate font-size based on a width/height got from setting `font-size: 10px`. Basically, the idea is "if I have 20 pixels width and 11 pixels height with `font-size: 10px`, so what would it be the maximum font-size to math a container of 50 pixels width and 30 pixels 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, and 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 JavaScript does
// if the real measure had been 12.49 px => JavaScript 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 which is smaller than its parent, otherwise if children and parent have both the same width/height function will fail.
let textElement = document.getElementById('text1');
let parentElement = textElement.parentElement;
const parentClientHeight = parentElement.clientHeight;
const parentClientWidth = parentElement.clientWidth;
textElement.style.padding = "unset";
textElement.style.margin = "auto";
let fontSize = parentClientHeight;
let minFS = 3,
maxFS = fontSize;
while (fontSize != minFS) {
textElement.style.fontSize = `${fontSize}px`;
if (
parentElement.scrollHeight <= parentClientHeight &&
parentElement.scrollWidth <= parentClientWidth
) {
minFS = fontSize;
} else {
maxFS = fontSize;
}
fontSize = Math.floor((minFS + maxFS) / 2);
}
textElement.style.fontSize = `${minFS}px`;
<div style="height:200px; width:300px;">
<div id='text1'>
test
</div>
</div>
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.
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})

Dynamically adjust font-size with height and width of the container [duplicate]

I'm having a hard time getting my head around font scaling.
I currently have a website with a body font-size of 100%. 100% of what though? This seems to compute out at 16 pixels.
I was under the impression that 100% would somehow refer to the size of the browser window, but apparently not because it's always 16 pixels 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. (For example, 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 100pixels decrease in width to control the text.
If the container is not the body, CSS Tricks covers all of your options in 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 do 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 as 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: Viewport Sized Typography
Here's a nice article about setting minimum/maximum sizes and exercising a bit more control over the sizes: Precise control over 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. Molten Leading in CSS
But what if the container is not the viewport (body)?
This question is asked in a comment by Alex under 2507rkt3's 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 content, 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. 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 the Size of the Container Change Dynamically...
If the sizing of the container element ended up changing dynamically its percentage relationship either via #media breakpoints 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 CSS 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 CSS 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.
Solution with SVG:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 75px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 75"
preserveAspectRatio="xMinYMid meet"
style="background-color:green"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<text
x="0"
y="75"
font-size="75"
fill="black"
>█Resize This█</text>
</svg>
</div>
Solution with SVG and text-wrapping using foreignObject:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 200px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 200"
preserveAspectRatio="xMinYMin meet"
>
<foreignObject width="100%" height="100%" xmlns="http://www.w3.org/1999/xhtml">
<div xmlns="http://www.w3.org/1999/xhtml" style="background-color:lightgreen;">
<h1>heading</h1>
<p>Resize the blue box.</p>
</div>
</foreignObject>
</svg>
</div>
In one of my projects I use a "mixture" between vw and vh to adjust the font size to my needs, for example:
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.
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 on the 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 320 pixels, thus I got 24 pixels high and 224 pixels wide, so the 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 bandwidth, 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 JavaScript (and forget about 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 am also quite new to writing Stack Overflow answers.
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.
There is a big philosophy for this issue.
The easiest thing to do would be to give a certain font-size to body (I recommend 10), and then all the other element would have their font in em or rem.
I'll give you an example to understand those units.
Em is always relative to its parent:
body{font-size: 10px;}
.menu{font-size: 2em;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5em;} /* That means 1.5*20 pixels = 30 pixels */
Rem is always relative to body:
body{font-size: 10px;}
.menu{font-size: 2rem;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5rem;} /* that means 1.5*10 pixels = 15 pixels */
And then you could create a script that would modify font-size relative to your container width.
But this isn't what I would recommend. Because in a 900 pixels width container for example you would have a p element with a 12 pixels font-size let's say. And on your idea that would become an 300 pixels wide container at 4 pixels font-size. There has to be a lower limit.
Other solutions would be with media queries, so that you could set font for different widths.
But the solutions that I would recommend is to use a JavaScript library that helps you with that. And fittext.js that I found so far.
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);
}
http://jsfiddle.net/0tpvccjt/
There is a way to do this without JavaScript!
You can use an inline SVG image. You can use CSS on an SVG if it is inline. You have to remember that using this method means your SVG image 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?
SVG images also allow you to do cool stuff with shapes and junk. Check out this great use case for scalable text...
https://jsfiddle.net/k8L4xLLa/14/
CSS Container Queries
A late-2022 addition to the CSS feature set makes scaling font size with containers straightforward.
Container queries come with a new set of CSS units cqw/cqh (container query width/height). To use them you need to set the container-type property on the parent element whose size you want to use. Minimal example:
<div>
<p>Lorem ipsum dolor sit amet</p>
</div>
<style>
div {
container-type: inline-size;
}
p {
font-size: 5cqw;
}
</style>
The font size will increase smoothly as the container grows. At 1000px container width, the p font size will be 1000px / 100 * 5 = 50px.
container-type can be size or inline-size. size tracks both height and width of the container which allows you to use both cqw and cqh.
Most of the time on the web, heights are calculated based on content and you only specify the width. To save the browser some work, you'll generally want to set container-type: inline-size; so the browser only tracks the inline dimension which is usually width (unless you set writing-mode to vertical).
Browser support for container queries has grown rapidly in the 2nd half of 2022 and currently stands at 75% (2023-01-01).
This may not be super practical, but if you want a font to be a direct function of the parent, without having any JavaScript 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 minimum/maximum 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 height 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 JavaScript. 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.
JSFiddle:
https://jsfiddle.net/0jr7rrgm/3/
(click once to lock the red square to the mouse, and click again to release)
Most of the JavaScript in the fiddle is just my custom click-drag function.
Using vw, em & co. works for sure, but IMO it 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.
JSFiddle
Use CSS Variables
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.
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 code to adjust the base font size relative to the window dimensions.
Artistically, if you need to fit two or more lines of text within the same width regardless of their character count then you have nice options.
It's best to find a dynamical solution so whatever text is entered we end up with a nice display.
Let's see how we may approach.
var els = document.querySelectorAll(".divtext"),
refWidth = els[0].clientWidth,
refFontSize = parseFloat(window.getComputedStyle(els[0],null)
.getPropertyValue("font-size"));
els.forEach((el,i) => el.style.fontSize = refFontSize * refWidth / els[i].clientWidth + "px")
#container {
display: inline-block;
background-color: black;
padding: 0.6vw 1.2vw;
}
.divtext {
display: table;
color: white;
font-family: impact;
font-size: 4.5vw;
}
<div id="container">
<div class="divtext">THIS IS JUST AN</div>
<div class="divtext">EXAMPLE</div>
<div class="divtext">TO SHOW YOU WHAT</div>
<div class="divtext">YOU WANT</div>
</div>
All we do is to get the width (els[0].clientWidth) and the font size (parseFloat(window.getComputedStyle(els[0],null).getPropertyValue("font-size"))) of the first line as a reference and then just calculate the subsequent lines font size accordingly.
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>
You may be 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 JavaScript and not a pure CSS solution):
$('body').flowtype({
minFont: 10,
maxFont: 40,
minimum: 500,
maximum: 1200,
fontRatio: 70
});
I've prepared a 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:
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
Inside your CSS, try adding this at the bottom changing the 320 pixels 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.
Try http://simplefocus.com/flowtype/. This is what I use for my sites, and it has worked perfectly.
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 is 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 (i.e. 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;
};
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 reduced 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);
Try to use the fitText plugin, because Viewport sizes isn't the solution of this problem.
Just add the library:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
And change font-size for correct by settings the coefficient of text:
$("#text_div").fitText(0.8);
You can set maximum and minimum values of text:
$("#text_div").fitText(0.8, { minFontSize: '12px', maxFontSize: '36px' });
Here is a pure CSS solution with the understanding that you admit breakpoints are necessary but also want text scaling:
I'm aware I can add breakpoints, but I really want the text to scale
as well as having extra breakpoints, otherwise....
Here is an approach using:
Custom properties
Media queries for breakpoints
clamp() (browser support in Feb 2022 is pretty good at 93%)
calc()
If one common scaling factor can be used to control ALL the text scaling within a container per screen max-width, all you need to do is scale a custom property per max-width, and apply this factor to 1 calculation.
A basic setup starts like this:
:root {
--scaling-factor: 1
}
.parent {
font-size: 30px
}
.largest {
font-size: clamp(60%, calc(var(--scaling-factor) * 100%), 100%);
}
.middle {
font-size: clamp(60%, calc(var(--scaling-factor) * 85%), 100%);
}
.smallest {
font-size: clamp(60%, calc(var(--scaling-factor) * 70%), 100%);
}
Then nest your media queries something like this (or whatever you need for your breakpoints):
#media (max-width: 1200px) {
:root {
--scaling-factor: 0.9
}
#media (max-width: 800px) {
:root {
--scaling-factor: 0.8
}
#media (max-width: 600px) {
:root {
--scaling-factor: 0.5 /* nope, because the font-size is floored at 60% thanks to clamp() */
}
}
}
}
This minimizes your media query markup.
Advantages
One custom property controls ALL scaling ... no need to add multiple declarations per media breakpoint
The use of clamp() sets a lower-limit on what the font-size should be, so you ensure your text is never too small (here the floor is 60% of the parent's font-size)
Please see this JSFiddle for a demo. Resize the window until at the smallest widths, the paragraphs are all the same font-size.
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.
Take look at my code. It makes the font size smaller to fit whatever there.
But I think this doesn't lead to a good user experience
var containerWidth = $("#ui-id-2").width();
var items = $(".quickSearchAutocomplete .ui-menu-item");
var fontSize = 16;
items.each(function(){
// Displaying a value depends 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);
}
});
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 the script:
$('#mydiv').slabText();
This worked for me:
I try to approximate font-size based on a width/height got from setting `font-size: 10px`. Basically, the idea is "if I have 20 pixels width and 11 pixels height with `font-size: 10px`, so what would it be the maximum font-size to math a container of 50 pixels width and 30 pixels 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, and 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 JavaScript does
// if the real measure had been 12.49 px => JavaScript 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 which is smaller than its parent, otherwise if children and parent have both the same width/height function will fail.
let textElement = document.getElementById('text1');
let parentElement = textElement.parentElement;
const parentClientHeight = parentElement.clientHeight;
const parentClientWidth = parentElement.clientWidth;
textElement.style.padding = "unset";
textElement.style.margin = "auto";
let fontSize = parentClientHeight;
let minFS = 3,
maxFS = fontSize;
while (fontSize != minFS) {
textElement.style.fontSize = `${fontSize}px`;
if (
parentElement.scrollHeight <= parentClientHeight &&
parentElement.scrollWidth <= parentClientWidth
) {
minFS = fontSize;
} else {
maxFS = fontSize;
}
fontSize = Math.floor((minFS + maxFS) / 2);
}
textElement.style.fontSize = `${minFS}px`;
<div style="height:200px; width:300px;">
<div id='text1'>
test
</div>
</div>
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.
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})

How do I autoshrink a dynamic h1 element for different text length?

I have an h1 element that changes its HTML dynamically and I want it to auto shrink itself for longer text but return to its fixed font size for shorter text.
Here is the markup:
<div class="frame" dir="ltr">
<div class="frame-view">
<h1> //dynamic text </h1>
</div>
</div>
and the styling:
.frame {
z-index: 2;
position: relative;
top: 100px;
}
.frame-view {
text-align: center;
}
.frame-view h1 {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-style: normal;
font-variant: normal;
font-weight: 200;
font-size: 40px;
color: white;
padding: 0 20px;
}
Here's what the h1 looks like when a long text is in it, on a mobile screen:
rather than staying within the 20px left, right padding and just auto shrinking the text, it stretches past the HTML.
in landscape it looks fine of course because there is enough room to contain it:
So how do I make this h1 auto shrink for longer text and stay within the boundaries ?
UPDATE
to further understand what I am trying to achieve, think of it like this:
I want the container of the h1 to be static and the text adjust its size when it gets closer to the container edges. Imagine a Label being 50 px, now if you put the word "hello" in that label at 20px font size, it would be 20px font size with no problem.. but if you put the words "Hello how are you" it wouldnt fit within the boundaries at 20px font size so it would have to auto shrink itself to stay within the boundaries of the container.
you should start with a given text width in pixels, then measure the width of the given text using the measuretext function. If width is approaching the width of your container, then shrink the font size and remeasure. You should obviously have a limit on how small the text can go before it becomes illegible. Once text gets too small, you have to truncate and add dots onto the end.
Here is a sample that shows roughly how to do it. You need javascript, and I have included jquery to make it easier. As you add more text, and the text starts getting near the edge of the box, the fontsize will shrink, down to a minimum of 10 pixels. After that text that is too long will be truncated.
You could also make the text grow again if it got shorter using similar functions but in reverse.. growing the font size till it just overflowed, then back one step. However I have not included code for that.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
</head>
<body>
<br>Enter your text:
<p></p>
<input type=text id=input_id style='width: 300px'></input>
<p></p>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var txt = "Hello World more"
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var fontsize = 30
ctx.font = fontsize.toString() + "px Arial";
// ctx.fillText("width:" + ctx.measureText(txt).width, 10, 50);
// ctx.fillText(txt, 10, 100);
$('#input_id').val(txt)
ObserveInputValue('')
ObserveInputValue(txt)
// a bit crude but simple and it works: watch the input object every tenth sec
setInterval(function() {
ObserveInputValue($('#input_id').val());
}, 100);
function ObserveInputValue(iv) {
if (iv != txt) {
console.log('input value changed to: ' + iv)
txt = iv
ctx.clearRect(0, 0, 300, 150)
textwid = ctx.measureText(txt).width.toFixed(0)
// adjust font size so that text just fits
var maxfont = 50
var minfont = 10
while (textwid > 290 && fontsize > minfont) {
sizedecrease = Math.max(1, fontsize * 0.1).toFixed(0) // decrease by 10% or 1 pixel whichever is greater
fontsize -= sizedecrease
ctx.font = fontsize.toString() + "px Arial";
textwid = ctx.measureText(txt).width.toFixed(0)
}
// if text at min and still too long truncate text
while (textwid > 290 && fontsize <= minfont) {
// chop off last characters of text
txt = txt.substring(0, txt.length - 1)
textwid = ctx.measureText(txt).width.toFixed(0)
$('#input_id').val(txt)
}
// exercise to the reader.. increase font size again if user makes text shorter
ctx.fillText("width:" + textwid, 10, 50);
ctx.fillText("font:" + fontsize, 10, 100);
ctx.fillText(txt, 10, 150);
}
}
</script>
</body>
</html>
The easiest way to go about this is to use vw. vw is short for "viewport width". You would go about it kind of like this:
h1 {
font-size: 4vw;
}
In other words, one letter of your <h1> takes up 4% of the width of the screen, since 1vw = 1% of the screen's width.
You will need to play around with it and figure out what number you need to use to make it work for you. If you find that you simply cannot achieve your goal with vw, you may also try CSS3 calc() (see an example here: http://codepen.io/CrocoDillon/pen/fBJxu).
Warning #1:
There is a word of caution, however. Depending on how your elements are set up, it is possible that this will cause some undesirable side effects, since vw bases the size off of the screen's width, not the element (if any) that contains the <h1>.
Warning #2:
Not all browsers have adopted support for vw yet. Make sure to put a fallback (font-size:24px etc.), just in case.

Is it possible to define font-size in terms of the width or height of the containing div? [duplicate]

I'm having a hard time getting my head around font scaling.
I currently have a website with a body font-size of 100%. 100% of what though? This seems to compute out at 16 pixels.
I was under the impression that 100% would somehow refer to the size of the browser window, but apparently not because it's always 16 pixels 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. (For example, 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 100pixels decrease in width to control the text.
If the container is not the body, CSS Tricks covers all of your options in 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 do 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 as 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: Viewport Sized Typography
Here's a nice article about setting minimum/maximum sizes and exercising a bit more control over the sizes: Precise control over 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. Molten Leading in CSS
But what if the container is not the viewport (body)?
This question is asked in a comment by Alex under 2507rkt3's 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 content, 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. 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 the Size of the Container Change Dynamically...
If the sizing of the container element ended up changing dynamically its percentage relationship either via #media breakpoints 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 CSS 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 CSS 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.
Solution with SVG:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 75px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 75"
preserveAspectRatio="xMinYMid meet"
style="background-color:green"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<text
x="0"
y="75"
font-size="75"
fill="black"
>█Resize This█</text>
</svg>
</div>
Solution with SVG and text-wrapping using foreignObject:
.resizeme {
resize: both;
margin: 0;
padding: 0;
height: 200px;
width: 500px;
background-color: lightblue;
overflow: hidden;
}
<div class="resizeme">
<svg
width="100%"
height="100%"
viewBox="0 0 500 200"
preserveAspectRatio="xMinYMin meet"
>
<foreignObject width="100%" height="100%" xmlns="http://www.w3.org/1999/xhtml">
<div xmlns="http://www.w3.org/1999/xhtml" style="background-color:lightgreen;">
<h1>heading</h1>
<p>Resize the blue box.</p>
</div>
</foreignObject>
</svg>
</div>
In one of my projects I use a "mixture" between vw and vh to adjust the font size to my needs, for example:
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.
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 on the 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 320 pixels, thus I got 24 pixels high and 224 pixels wide, so the 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 bandwidth, 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 JavaScript (and forget about 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 am also quite new to writing Stack Overflow answers.
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.
There is a big philosophy for this issue.
The easiest thing to do would be to give a certain font-size to body (I recommend 10), and then all the other element would have their font in em or rem.
I'll give you an example to understand those units.
Em is always relative to its parent:
body{font-size: 10px;}
.menu{font-size: 2em;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5em;} /* That means 1.5*20 pixels = 30 pixels */
Rem is always relative to body:
body{font-size: 10px;}
.menu{font-size: 2rem;} /* That means 2*10 pixels = 20 pixels */
.menu li{font-size: 1.5rem;} /* that means 1.5*10 pixels = 15 pixels */
And then you could create a script that would modify font-size relative to your container width.
But this isn't what I would recommend. Because in a 900 pixels width container for example you would have a p element with a 12 pixels font-size let's say. And on your idea that would become an 300 pixels wide container at 4 pixels font-size. There has to be a lower limit.
Other solutions would be with media queries, so that you could set font for different widths.
But the solutions that I would recommend is to use a JavaScript library that helps you with that. And fittext.js that I found so far.
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);
}
http://jsfiddle.net/0tpvccjt/
There is a way to do this without JavaScript!
You can use an inline SVG image. You can use CSS on an SVG if it is inline. You have to remember that using this method means your SVG image 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?
SVG images also allow you to do cool stuff with shapes and junk. Check out this great use case for scalable text...
https://jsfiddle.net/k8L4xLLa/14/
CSS Container Queries
A late-2022 addition to the CSS feature set makes scaling font size with containers straightforward.
Container queries come with a new set of CSS units cqw/cqh (container query width/height). To use them you need to set the container-type property on the parent element whose size you want to use. Minimal example:
<div>
<p>Lorem ipsum dolor sit amet</p>
</div>
<style>
div {
container-type: inline-size;
}
p {
font-size: 5cqw;
}
</style>
The font size will increase smoothly as the container grows. At 1000px container width, the p font size will be 1000px / 100 * 5 = 50px.
container-type can be size or inline-size. size tracks both height and width of the container which allows you to use both cqw and cqh.
Most of the time on the web, heights are calculated based on content and you only specify the width. To save the browser some work, you'll generally want to set container-type: inline-size; so the browser only tracks the inline dimension which is usually width (unless you set writing-mode to vertical).
Browser support for container queries has grown rapidly in the 2nd half of 2022 and currently stands at 75% (2023-01-01).
This may not be super practical, but if you want a font to be a direct function of the parent, without having any JavaScript 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 minimum/maximum 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 height 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 JavaScript. 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.
JSFiddle:
https://jsfiddle.net/0jr7rrgm/3/
(click once to lock the red square to the mouse, and click again to release)
Most of the JavaScript in the fiddle is just my custom click-drag function.
Using vw, em & co. works for sure, but IMO it 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.
JSFiddle
Use CSS Variables
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.
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 code to adjust the base font size relative to the window dimensions.
Artistically, if you need to fit two or more lines of text within the same width regardless of their character count then you have nice options.
It's best to find a dynamical solution so whatever text is entered we end up with a nice display.
Let's see how we may approach.
var els = document.querySelectorAll(".divtext"),
refWidth = els[0].clientWidth,
refFontSize = parseFloat(window.getComputedStyle(els[0],null)
.getPropertyValue("font-size"));
els.forEach((el,i) => el.style.fontSize = refFontSize * refWidth / els[i].clientWidth + "px")
#container {
display: inline-block;
background-color: black;
padding: 0.6vw 1.2vw;
}
.divtext {
display: table;
color: white;
font-family: impact;
font-size: 4.5vw;
}
<div id="container">
<div class="divtext">THIS IS JUST AN</div>
<div class="divtext">EXAMPLE</div>
<div class="divtext">TO SHOW YOU WHAT</div>
<div class="divtext">YOU WANT</div>
</div>
All we do is to get the width (els[0].clientWidth) and the font size (parseFloat(window.getComputedStyle(els[0],null).getPropertyValue("font-size"))) of the first line as a reference and then just calculate the subsequent lines font size accordingly.
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>
You may be 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 JavaScript and not a pure CSS solution):
$('body').flowtype({
minFont: 10,
maxFont: 40,
minimum: 500,
maximum: 1200,
fontRatio: 70
});
I've prepared a 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:
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
Inside your CSS, try adding this at the bottom changing the 320 pixels 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.
Try http://simplefocus.com/flowtype/. This is what I use for my sites, and it has worked perfectly.
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 is 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 (i.e. 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;
};
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 reduced 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);
Try to use the fitText plugin, because Viewport sizes isn't the solution of this problem.
Just add the library:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
And change font-size for correct by settings the coefficient of text:
$("#text_div").fitText(0.8);
You can set maximum and minimum values of text:
$("#text_div").fitText(0.8, { minFontSize: '12px', maxFontSize: '36px' });
Here is a pure CSS solution with the understanding that you admit breakpoints are necessary but also want text scaling:
I'm aware I can add breakpoints, but I really want the text to scale
as well as having extra breakpoints, otherwise....
Here is an approach using:
Custom properties
Media queries for breakpoints
clamp() (browser support in Feb 2022 is pretty good at 93%)
calc()
If one common scaling factor can be used to control ALL the text scaling within a container per screen max-width, all you need to do is scale a custom property per max-width, and apply this factor to 1 calculation.
A basic setup starts like this:
:root {
--scaling-factor: 1
}
.parent {
font-size: 30px
}
.largest {
font-size: clamp(60%, calc(var(--scaling-factor) * 100%), 100%);
}
.middle {
font-size: clamp(60%, calc(var(--scaling-factor) * 85%), 100%);
}
.smallest {
font-size: clamp(60%, calc(var(--scaling-factor) * 70%), 100%);
}
Then nest your media queries something like this (or whatever you need for your breakpoints):
#media (max-width: 1200px) {
:root {
--scaling-factor: 0.9
}
#media (max-width: 800px) {
:root {
--scaling-factor: 0.8
}
#media (max-width: 600px) {
:root {
--scaling-factor: 0.5 /* nope, because the font-size is floored at 60% thanks to clamp() */
}
}
}
}
This minimizes your media query markup.
Advantages
One custom property controls ALL scaling ... no need to add multiple declarations per media breakpoint
The use of clamp() sets a lower-limit on what the font-size should be, so you ensure your text is never too small (here the floor is 60% of the parent's font-size)
Please see this JSFiddle for a demo. Resize the window until at the smallest widths, the paragraphs are all the same font-size.
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.
Take look at my code. It makes the font size smaller to fit whatever there.
But I think this doesn't lead to a good user experience
var containerWidth = $("#ui-id-2").width();
var items = $(".quickSearchAutocomplete .ui-menu-item");
var fontSize = 16;
items.each(function(){
// Displaying a value depends 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);
}
});
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 the script:
$('#mydiv').slabText();
This worked for me:
I try to approximate font-size based on a width/height got from setting `font-size: 10px`. Basically, the idea is "if I have 20 pixels width and 11 pixels height with `font-size: 10px`, so what would it be the maximum font-size to math a container of 50 pixels width and 30 pixels 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, and 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 JavaScript does
// if the real measure had been 12.49 px => JavaScript 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 which is smaller than its parent, otherwise if children and parent have both the same width/height function will fail.
let textElement = document.getElementById('text1');
let parentElement = textElement.parentElement;
const parentClientHeight = parentElement.clientHeight;
const parentClientWidth = parentElement.clientWidth;
textElement.style.padding = "unset";
textElement.style.margin = "auto";
let fontSize = parentClientHeight;
let minFS = 3,
maxFS = fontSize;
while (fontSize != minFS) {
textElement.style.fontSize = `${fontSize}px`;
if (
parentElement.scrollHeight <= parentClientHeight &&
parentElement.scrollWidth <= parentClientWidth
) {
minFS = fontSize;
} else {
maxFS = fontSize;
}
fontSize = Math.floor((minFS + maxFS) / 2);
}
textElement.style.fontSize = `${minFS}px`;
<div style="height:200px; width:300px;">
<div id='text1'>
test
</div>
</div>
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.
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})

Allowing images to shrink, but not stretch

I have a site with 4,000+ pages and 10 or more jpeg images per page, of varying sizes. I'm trying to make the site more mobile friendly. To that end, i want to make it possible for the images to shrink to fit on smaller screens. I know that i can do something like this to signal that the images can shrink:
img.bodyImg
{
width: 100%;
max-width: 357px;
height: auto;
}
But what if not all images have a width of 357 (or whatever), and i don't want smaller images stretched beyond their true dimensions? And just to make things more fun, what if the images tags don't have height and width attributes specified?
My goal is to find a solution that doesn't require me to adjust tens of thousands of image calls manually, but i can do a search and replace. Images are currently wrapped in a div container and have a class, like so:
<div class="imgDiv"><img class="bodyImg" src="http://www.example.com/image.jpg"></div>
I'm also open to the possibility that i'm going about this in the wrong way entirely.
Using max-width is simple, effective, and requires no JavaScript.
The CSS below creates responsive images that shrink to fit the container's width but won't expand beyond their native sizes.
img.bodyImg {
max-width:100%
}
In the demonstration below, both images are 300px X 75px. The first container is 200px wide and the second one is 400px wide. Notice that the first image shrinks to fit in the container, but the second image does not expand beyond its native size. Also note that the proportions of each image remain accurate.
div {
background-color: #CCC;
margin:0 0 .5em;
padding:.25em;
}
div.one {
width: 200px;
}
div.two {
width: 400px;
}
img {
display:block;
max-width: 100%;
}
<div class="one">
<img src="http://lorempixel.com/300/75/abstract/4/" />
</div>
<div class="two">
<img src="http://lorempixel.com/300/75/abstract/4/" />
</div>
Additional Notes
I've included display:block to remove the descender space below the image.
If your images have specific height and width attributes (as they arguably should), you can add height:auto and/or width:auto to override those attributes.
Bootstrap uses this method for responsive images.
You can use a little jQuery to figure out each image's native width, and set perscriptive max-widths for each image afterward:
$('.bodyImg').each(function() {
// Create new offscreen image to test
var theImage = new Image();
theImage.src = $(this).attr("src");
// Get accurate measurements from that.
var imageWidth = theImage.width;
$(this).css({
"max-width" : imageWidth
});
}
UPDATE: And if you want each image to have a uniform width, you can store the smallest max width and apply it to all of the images:
var smallMax;
$('.bodyImg').each(function() {
// Create new offscreen image to test
var theImage = new Image();
theImage.src = $(this).attr("src");
// Get accurate measurements from that.
var imageWidth = theImage.width;
// if the variable exists and is bigger than
// the current width, use the new max width
if (smallMax !== undefined && smallMax > imageWidth) {
smallMax = imageWidth;
}
// set the variable if it hasn't been set yet
else if (smallMax == undefined) {
smallMax = imageWidth;
}
// keep the old variable if it is defined and smaller
else {}
$(this).css({
"max-width" : smallMax
});
}
Why not just:
max-width:100%; /*Ensure the width scales to the width of the parent container*/
width:auto; /* Not required, but sometimes is nice to ensure the width not being overridden by other factors like browser quirks */
height: auto; /*Ensure the image keeps its ratio*/
Try using max-width:100% and height: auto in your css. If you want to make your site mobile friendly I would suggest looking into bootstrap framework for more flexibility.