Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-fonts-4] Feature for making text always fit the width of its parent #2528

Open
tobireif opened this issue Apr 11, 2018 · 28 comments
Open

Comments

@tobireif
Copy link

This thread shows that it's a widely required feature:
https://twitter.com/sindresorhus/status/979363460411609091

Example of a workaround: Open https://tobireif.com/ and (eg using dev tools responsive mode) resize the page down to 250px width while watching the text "Welcome".

@litherum
Copy link
Contributor

This requires performing layout in a loop, which we generally have avoided. Requiring a round-trip through JS is valuable because authors are more likely to realize it has a large perf cost

@litherum litherum added the css-fonts-4 Current Work label Apr 11, 2018
@tobireif
Copy link
Author

In the JS at https://tobireif.com/ I perform two passes - that's plenty for a good-enough result, and it doesn't impact perf in any noticeable way (the text-fitting is only done once in addition to the first main run). That could be a great option for browser implementers as well, and it shows that supporting such a CSS feature is very feasible.

@tobireif tobireif changed the title Feature for making text always fit the width of its parent [css-fonts-4] Feature for making text always fit the width of its parent Apr 11, 2018
@timothyis
Copy link

If this were a feature, I think it'd be best if it was a CSS function. (similar to calc or minmax)

Something like font-size: fit(8px, 48px); where 8px is the minimum font-size and 48px is the maximum font-size.

I think using a function, other than being useful for minimum and maximum sizes, relays the gravity of using the feature since surely it'll have some performance issues in extreme cases.

I'd love to see this in CSS!

@tobireif
Copy link
Author

Great suggestions!

A lower limit and an upper limit both make sense.

Instead of font-size: fit(8px, 48px) it might be better to name it eg font-size: fit-width(8px, 48px).

@SergeyMalkin
Copy link

Changing font-stretch, especially using variable fonts, is another way to fit text into parent. Or compression during justification . So if there is a css property that instructs layout engine to fit, it should allow different methods and so likely be separate from font-size.

And this kind of functionality may not only be on line operation. It may be useful for more advanced functionality, like optimal paragraph layout, line clamping, or simple ellipsis.

@tobireif
Copy link
Author

Changing font-stretch, especially using variable fonts, is another way to fit text into parent. Or compression during justification . So if there is a css property that instructs layout engine to fit, it should allow different methods and so likely be separate from font-size.

True! (also eg letter-spacing)

And this kind of functionality may not only be on line operation. It may be useful for more advanced functionality, like optimal paragraph layout, line clamping, or simple ellipsis.

Let's start simple 😀 If we're asking for too much we might not get anything. The basic simple use case of fitting one line of text (eg a heading) into its responsive parent is so common that a solution for that would cover a lot (and more could get added/specd later).

@tobireif
Copy link
Author

tobireif commented Apr 12, 2018

Yes it's feasible to implement the functionality using JS, and yes there are workarounds, and I think there even is a lib, but it sure would be very handy to be able to simply write one single line of CSS instead.

My implementation in the source of https://tobireif.com/ is more than 50 lines of JS - if people could instead write a single line of CSS then that would save a lot of typing.

By the way @litherum : If the implementation is smart enough, perhaps one pass would be sufficient → no loop / double-pass.

Perhaps the syntax could look like this:

fit-width: font-size(20px, 100px);
fit-width: letter-spacing(-0.1em, 1.5em);
fit-width: any-text-width-affecting-property(min, max);

The sizing/fitting should honour the (potential) padding of the container.

@litherum
Copy link
Contributor

Using a small number of passes is unlikely to work in the general case, because if we get it wrong, the text will overflow its container and wrap, which would be catastrophic. Any generalized implementation would have to iterate until the algorithm converges. Such an algorithm would be a great way to make a browser hang.

@tobireif
Copy link
Author

tobireif commented Apr 14, 2018

If you do want to provide this widely requested feature - perhaps you could try it out 😀 If your algorithm is smart regarding calculating the estimated target value, it will not need many passes, and it might need only one or two passes. For all and any cases.

Such an algorithm would be a great way to make a browser hang.

When you try it out, and limit the maximum number of passes to 2 == no browser hang at all, and if your algo can estimate the correct value pretty well, then there's a good chance that the result will be sufficiently good. You'd have to try it out though.

If you do not want to provide that feature no matter what, and thus do not want to create a quick "beta" implementation for seeing what's feasible, then there's not much reason to continue the discussion. In that case please close the ticket.

I did create a quick implementation using JS and found that it works sufficiently well using only two passes. The code is at https://tobireif.com/ -> source -> "var topLevelHeadings". It's just a quick (but good enough for that case) implementation - I'm 100% sure that you could come up with a much better (and generally applicable) algo 😀

Here's another JS implementation:
http://fittextjs.com/
https://github.com/davatron5000/FitText.js

None of the above implementations causes any noticeable performance issue. And: The latter is a general lib.

@tobireif
Copy link
Author

As for your own site, the type inside your headers is simple enough that you'd have performance gains in just using vw inside a breakpoint, reducing 50 lines of runtime js to possible 4 lines of css.

I'd prefer CSS that's based on the parent element width, not on the viewport width. (Because generally the element width might change without the viewport changing.)

The feature is a (very popular) wish - the specification of that feature (including all relevant details) would be up to the CSS WG.

@tobireif
Copy link
Author

(Oh, and if that feature would be only feasible to spec/implement for a defined set of simple types of cases, I'm sure that simple feature would be widely appreciated as well 😀 The syntax still could be fit-width: any-text-width-affecting-property(min-value, max-value), I think.)

@tobireif
Copy link
Author

If and when there will be an ew unit ("element width", as in EQCSS), and if and when there will be clamp() , then the functionality in this feature wish ticket here could be expressed sufficiently succinct, eg:

font-size: clamp(30ew, 20px, 80px);

@jonjohnjohnson
Copy link

Where are you getting 30 in the 30ew? Are you matching that to the length of the string in the element? Or is 1ew just 1% of the elements width, meaning a 100px wide element would set its font-size to 30px?

@tomhodgins
Copy link

Here's the definition of ew, eh, emin, and emax from jsincss-element-units:

switch(unit) {

  case 'ew':
    return tag.offsetWidth / 100 * number + 'px'

  case 'eh':
    return tag.offsetHeight / 100 * number + 'px'

  case 'emin':
    return Math.min(tag.offsetWidth, tag.offsetHeight) / 100 * number + 'px'

  case 'emax':
    return Math.max(tag.offsetWidth, tag.offsetHeight) / 100 * number + 'px'

}

I was thinking of isolating just these tests into their own package (and maybe the element query tests from jsincss-element-query) so other plugin builders could more easily re-use the same tests in their plugins.

@tobireif
Copy link
Author

tobireif commented Apr 14, 2018

Yeah, it'd not be the real deal where the implementation figures out the value required for making the text fit its container. It'd just be a pragmatic way to get the feature with just one line of CSS.

(And yes, 30ew means 30% of the element width. The exact number is just an example, it could be eg 45.5ew .)

@tobireif
Copy link
Author

(I was replying to @jonjohnjohnson , just so there's no misunderstanding @tomhodgins 😀)

@tobireif
Copy link
Author

Ideally we could state in CSS "always fit this word/line of text inside its parent (by auto-adjusting the property "foo" eg font-size or letter-spacing), no matter what font is used".

@Crissov
Copy link
Contributor

Crissov commented Feb 6, 2021

We now do have clamp() to specify lower and upper bounds. Alas, we can only reference character width (ch, ic) or height (em, cap) and viewport dimensions (vw, vh etc.), not line or box width, as units. (Units for container dimensions have been proposed in #5888.) So you could only approximate the result for an assumed number of characters per line.

For the desired capability we would need new keywords or functions indeed.

@tobireif
Copy link
Author

tobireif commented Feb 8, 2021

For the desired capability we would need new keywords or functions indeed.

Yep 😀

@faceless2
Copy link

No one has mentioned the SVG textLength property, which already does this. The functionality is also part of AH formatter: https://www.antenna.co.jp/AHF/help/en/ahf-ext.html#axf.overflow-condense

Their algorithm applies to a block, not a line - I expect that the text is progressively adjusted and layout retried until it fit. It's certainly going to be multi-pass and expensive - you can take a guess at a start value easily enough, but word-breaks at the end of the line necessarily make the algorithm iterative to find the best value. Doing it once for print layout is one thing, but it would be horrendous if you were resizing a window with this on.

We've been asked for similar functionality a few times over the years, but I believe only ever for "fit to line" rather than "fit to block". I think it's more of an issue in print, at least until they start selling paper with a horizontal scrollbar.

If you restricted it to just scaling either font size or font-stretch, and you restrict it to just scaling text to fit a single line, then it's theoretically a single pass - it's just a multiplier applied to the property. But it gets rapidly more complex when you've got only part of the line scaling this way, or you have multiple items on the line doing this with different layout properties - for instance, imagine a float and two spans with different initial font-sizes on the line, all trying to scale themselves to fill the line. It's all stuff that would need defining.

@tobireif
Copy link
Author

@faceless2 wrote:

If you restricted it to just scaling either font size or font-stretch, and you restrict it to just
scaling text to fit a single line [inside a box]

That would be sensible (with font size as default).

@Crissov
Copy link
Contributor

Crissov commented Feb 10, 2021

I’ve seen cases where this has been applied to each word, for some definition of word. Nonetheless, I guess it would be fine to do this by fitting the whole textual box content on a single line.

(An l or line element would have been better than br in HTML.)

@jimmy-guo
Copy link

jimmy-guo commented Sep 20, 2022

Hi all, I'd like to revive the conversation and provide another perspective on the utility of supporting a feature like this in CSS.

There are many designs that leverage careful placement and styling of text. A lot of time is spent by designers and engineers to implement these designs, but often only just in English. As soon as the text gets translated to another language, especially if the translation is much longer or shorter, applying the same CSS to the text that worked for English often causes issues such as text overflowing, truncating, breaking mid-word, widows, etc. As a result, this feature would make it easier to localize text while preserving design intent.

This requires performing layout in a loop, which we generally have avoided. Requiring a round-trip through JS is valuable because authors are more likely to realize it has a large perf cost

There are several JS libraries that attempt to implement this resizing. However, one limitation of a JS implementation is that it causes layout shifts for server-side rendered (SSR) pages. Since the server does not reliably know the dimensions of the client's device, the text needs to wait for the page to be hydrated before resizing. If supported in CSS, text would be able to render at just the right size even on initial render of SSR'ed pages.

In addition, while performance is certainly a consideration, other expensive CSS features such as animating height also exist and the performance implications are well known. Given the benefits of a "FitText" feature, it would be nice to be able to support this and allow developers weigh the performance cost against the benefits for their use case.

@kizu
Copy link
Contributor

kizu commented Mar 23, 2023

I want us to return to this issue — we now have inline-size containment, which could be used to solve the potential issues regarding the circularity.

The list of things a potential solution for “fit to width” text should handle the following, in my opinion:

  1. It should work only inside an element with size or inline-size containment.
  2. We need to have an ability to set a min/max font-size.
    • We could use the existing font-size as the minimum — this would guarantee a) readable font-size when too much content/too narrow context, b) better graceful degradation.
    • I'd argue that introducing a new property instead of using something like fit-width(8px, 48px) value for the font-size could be more preferable: easier to detect the intent, easier to fall back to the regular font-size when you'd forget the containment/when the feature is not supported.
    • I think it is better to have a single property that would trigger the fit-to-width and set the maximum. Better to encourage having some limit, and someone who don't want to be limited could still set it to an arbitrary big number as a work-around.
  3. Should work with multi-line text:
    • If multiple lines of text are present (with hard-breaks, like with <br/> or in pre context) the longest line should be used for this limit.
    • By default should fit as many text as can fit until meeting the min font-size, then wrap.
    • Maybe there could be an option to force the “fit-to-width” on all the wrapped lines until they fit again — but only as an option, as both behaviors have use-cases.
    • Optionally, not sure if easy to implement — should work nicely with text-wrap: balance. Logically, with the simplest form of balancing, it sounds not super complicated: calculate the initial wrapping opportunity based on the min font-size, then balance things using the text-wrap: balance, then bump the size either based of the longest line for everything, or for each line separately, based on the preference from the previous item.

@kizu
Copy link
Contributor

kizu commented Jun 21, 2023

For those who could want to experiment with CSS-only way of achieving this, I just wrote an article about how we can use scroll-driven animations (at the moment available in Chrome Canary) to do just that — https://kizu.dev/fit-to-width-text/

Compared to what is proposed in this article, the main limitation of that method is the absence of the min value for the font-size, alongside overall hackiness of the method, so I would still want to see this implemented in CSS natively :)

@brandonmcconnell
Copy link

brandonmcconnell commented Jun 21, 2023

Would it be useful to introduce this as a function which could optionally accept a percentage arg signifying how much of the available space the text should span?

For any bounds need, we could just use the built-in clamp function along with a new “fit” keyword, like font-size: clamp(8px, fit(), 48px) where fit() would have a default arg value of 100%.

@sarajw
Copy link

sarajw commented Jun 22, 2023

I was reading this thread from the top and was thinking exactly that, that some kind of fit-to-width option inside clamp would be awesome.

Alternatively if we could specify the size of a font by its average width (like by the ch unit?), then it might get us a step closer to this functionality, even if not perfectly so.

@nathanchase
Copy link

I want us to return to this issue — we now have inline-size containment, which could be used to solve the potential issues regarding the circularity.

I started a Codepen example to help solve this: https://codepen.io/nathanchase/pen/rNKqYoX

Could we somehow utilize ch or ic units inside a calc(), and then clamp the font-size based on the container inline-size?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests