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] Address missing aspect of Progressive Enhancement in font loading #450

Closed
jpamental opened this issue Sep 7, 2016 · 24 comments
Labels

Comments

@jpamental
Copy link

Not entirely sure if this is the right place to post, but @litherum suggested this might be a place to start.

The Issue
Loading web fonts creates an uneven experience, either in waiting for fonts to load and displaying fallbacks that don't exactly match (width/height/etc) causing text reflow when the web fonts finally load. Using font-display helps even out loading behavior, but that only allows us to get fallback fonts in use faster; it doesn't let us style them. That's the key to mitigating FOUT: style the fallback text using letter-spacing font-size line-height etc so that the difference when the web fonts finally load is minimized. Currently we have to rely upon JS to switch classes, but if there were some way in CSS to target the state when web fonts haven't loaded we could remove the reliance upon JS and be more fully embracing the concept of Progressive Enhancement (especially since support of JS and @font-face are not necessarily tied).

For the most thorough PE approach, we should strive for:

  1. No CSS, no JS, no web fonts: just the content
  2. CSS loads, but no JS or web fonts: fallbacks should be styled to minimize the difference between them and the eventual web fonts (this is handled in my example below by scoping the fallback styles behind the wf-inactive class)
  3. CSS and web fonts: we get the design rendered as intended (requiring that the wf-inactive class goes away)
  4. CSS, JS & web fonts: we get the smoothest experience by having control over those classes and can also use the session storage variable to get the browser to render the web fonts with less hesitation

font-display when supported helps show the content in the fallback fonts faster, but we’re still stuck relying on JS to either insert or remove the CSS class that would control whether or not the web fonts would be referenced and how we can target the CSS styling based on web fonts being present or not at the time.

Example of using the loading class to style fallbacks:

p {
    font-family: 'Alegreya', Georgia, serif;
    font-size: 1em;
}

.wf-inactive p {
    font-family: Georgia, serif;
    font-size: 0.975em;
    letter-spacing: -0.4px;
}
@frivoal
Copy link
Collaborator

frivoal commented Sep 7, 2016

Not entirely sure if this is the right place to post, but @litherum suggested this might be a place to start.

It is. Welcome down the rabbit hole.

The problem you describe sounds relevant, but as always, the devils' in the details. Leaving aside what mechanism we use to solve it for now, what would you expect/want to happen when the web font does load, but it only support some of the characters used in the page, and the fallback font is used for these. Do you want the various font related settings you've prepared for the fallback to still apply to the characters displayed using it, or is it better to discard them as long as the web font has loaded?

@jpamental
Copy link
Author

Thanks @frivoal !

I think that it's sufficient to apply the style rules across the board. We can't address incomplete fonts now, so no need to complicate this issue by trying to address that as well. I think that's up to the designer to pick better fonts! I think it's better to discard the fallback styles if we know that web fonts have loaded.

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

Currently, the CSS Font Loading API associates a "loading" boolean state with an @font-face block.

It sounds like you're asking for some way to say in CSS: "Apply some style while a specific @font-face block is loading, and remove the style when it stops loading." (Where "apply some style" conceptually means the same thing as an @media query)

Is this accurate?

@jpamental
Copy link
Author

@litherum Exactly! Just would love to be able to reach it almost like an attribute selector or pseudo-selector rather than requiring JS. Between that and font-display we'd have pretty complete control over how things load, what to display (for fonts) and how to style it.

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

It sounds like this is a request to move logic from libraries like TypeKit down into the browser. Do you think you could describe the specific logic TypeKit uses by adding/remove classes when fonts load? How does TypeKit know which elements to add/remove classes to?

@jpamental
Copy link
Author

The way it's done with the Web Font Loader (used by Typekit and Google Web Fonts, and Monotype does the same thing with Fonts.com, but with different classes) is something like this:

  1. Insert 'loading' classes - one general, and one for each font that is loading
  2. Monitor the loading process, swapping out the 'loading' classes for 'loaded' ones as each font loads
  3. Finally swapping the general one once all are loaded.

Additionally both methods (Web Font Loader and Fonts.com's version) supply an 'inactive' class, which is added with the 'loading' classes, and is meant to stay there in the event that the web fonts don't ever fully load. If they do, it is removed as well.

While it would be great to have access to a 'loading' state for each font, the (perhaps) more helpful (and easier to set things across the board) is that general 'loading/loaded' state (all or none essentially).

@jpamental
Copy link
Author

I should have clarified that this is generally done by inserting a class like 'wf-loading' on the HTML element during the loading process, and once the web fonts have loaded, removing the 'wf-loading' class and replacing it with 'wf-loaded'

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

https://github.com/bramstein/fontfaceobserver has the same sort of logic.

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

TypeKit / Fonts.com generate (somewhat mangled) class names from the name of each loading font.

Browsers should not generate class names the same way.

However, many authors don't care about each individual font loads, and simply want a big switch saying "everything is done" or "something is not done." These are done with well-known (short) class names.

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

@jpamental: How does a web author know which CSS values to apply during loading? During font loading, an installed font must be selected, but every platform on the web has a different set of fonts installed (indeed, even the same font may have different metrics on different platforms). If the goal is to make the installed font better match the web font, how does a web author do this when she has no idea which installed font is used or what its metrics are?

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

If the goal is to allow arbitrary properties to apply to elements during a font load, we have to be careful of what happens if these arbitrary properties trigger yet another font load. For example, we need to consider the case where, during a font download, an element is styled with "font-family," thereby causing another download. This could be mitigated by either disallowing properties consulted during font selection, or, by browsers simply detecting the above conditions and not following the chain. (Or the browsers could faithfully honor the chain, but I don't think anybody wants that.)

@jpamental
Copy link
Author

@litherum -

There's no way to be 100% perfect, but in general the fallback stack should be fairly well supported, and you'd generally want to optimize for the most widely available (or most likely 'first alternate': Arial or Helvetica on most desktops, Droid on Android - whichever comprises the largest segment of your audience).

So I generally define a set of fallbacks that will cover the basics and optimize for the first one in the stack. It's not perfect, but far better than doing nothing in terms of getting content on screen and minimizing reflow when the fonts load.

If this is tied in some way to the Font Loading API, then the behavior could simply follow however that is working: if the boolean flag is in the 'loading' state for all @font-face blocks then 'loaded=false'; if they're all 'loaded' then 'loaded=true' (I'm imagining this as an attribute selector, but hopefully you get what I mean in a more general sense)

@aaaxx
Copy link

aaaxx commented Sep 7, 2016

Wouldn't this issue be better solved on a higher level, introducing a syntax for specifying font parameters like discussed in #126 and specifically in this post, which Tab Atkins thought was a good idea?

For example:

@font-face { /* load fonts */ }

@fallback-font-sequence --name {
/* ^ should probably be called something simpler, like `@font-stack` */

    Alegreya,

    Georgia { font-size: 0.975em; },

    serif
}

p {
    font-family: Alegreya, Georgia, serif; /* fallback for old browsers */
    font-family: var(--name); /* call to the new  @fallback-font-sequence rule */
}

@jpamental
Copy link
Author

@aaaxx That would definitely be a better solution, because it would also address the ability to specify fallback tweaks for individual fallback fonts (i.e. 'these styles for Arial, these other styles for Droid Sans', etc)

Then if it's wrapped in @supports for both @font-stack and font-display then you could safely scope all of the CSS for the most predictable results.

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

I don't see anywhere else where font-stack is discussed. Is there another discussion you could direct me to?

The model of associating CSS properties with items in the font fallback list doesn't hold, because CSS properties are applied to elements, but each item in the font fallback list is applied to individual glyphs, not elements. So the associated issues would be around what to do if most of the text in an element is rendered with one item in the font-fallback list, but some of it is rendered with another.

@jpamental
Copy link
Author

@litherum I was following the thread @aaaxx mentioned here: #126 (comment) but I see what you're getting at about how the fallbacks are applied, and it certainly shouldn't be applied to individual glyphs like you described.

I like the simplicity of the 'pseudo-code' that @aaaxx laid out above but see that it could be really tricky in how it's applied.

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

One possible idea is something like the following:

@font-face {
  font-family: "MyFont";
  src: url("MyFont.ttf");
  @loading {
    #myfancydiv {
      letter-spacing: -3px;
    }
  }
}

This approach would piggyback on the CSS Font Loading API's notion of an @font-face block being "loading" or "loaded." The style rules would only apply if the block is loading. The style rules inside the @Loading block behave according to the regular priority rules (and, since order of rules within the document matters, the rules behave as if they are in the position of the @font-face block). Because selectors are present inside the @Loading block, the browser knows which elements to modify.

The downside is that this requires nested at-rules, which I'm not sure have ever been done before.

For browsers which don't support the nested at-rules, they would likely treat this as a parse error, and either 1) ignore the @Loading block (which is the most desirable behavior) or 2) ignore the @font-face block, which could be worked around by putting a duplicate @font-face block just before this one (but omitting the @Loading block from the earlier duplicate).

Maybe there is a better solution to this problem; this is just what I've come up with off the top of my head.

@jpamental
Copy link
Author

@litherum that seems to tie nicely to the existing Font Loading API and also a good way to set fallback customization per font, but doesn't give the opportunity to customize per fall-back (as outlined by @aaaxx above). I wonder if there might be a way to combine those approaches. I recognize that this is a greater length than many will go, but if we're going to propose a change we may as well see how thorough we can be!

What I like about this approach is that it puts this control right in the same place where font-display behavior is set.

A downside to this is that it can only be utilized if you have control over the @font-face block itself, but that's solvable on the platform side if they choose to do so (and the Typekit crew would likely do something pretty nifty with it).

@litherum
Copy link
Contributor

litherum commented Sep 7, 2016

Background info to motivate this issue: it appears that every major font loading library allows for styling text differently during font downloads.

@aaaxx
Copy link

aaaxx commented Sep 7, 2016

I don't see anywhere else where font-stack is discussed. Is there another discussion you could direct me to?

Sorry, I should have called it @fallback-font-sequence, like in the linked post. Edited now.

As for the individual-glyphs issue, I think you're looking at it the wrong way. The whole point of this proposed property, as discussed in the other thread, is to visually harmonize the intended font and its fallbacks so you get the desired and consistent look, either when the main font is unavailable or when you get mixed fonts in the same element (e.g. multiple scripts, special characters, emoji). Prevention of "Flash of Unstyled Text" then is something you get for free.

@litherum
Copy link
Contributor

litherum commented Sep 8, 2016

@jpamental The other thread mentions weight and size as being valuable to change depending on which item in the font fallback list is used. Are there other properties you think would be valuable to change?

@jpamental
Copy link
Author

I'd generally be thinking about:
font-weight
font-size
letter-spacing
word-spacing
line-height

I'm sure others might crop up but that would give you a ton of control.

@fantasai
Copy link
Collaborator

fantasai commented Aug 3, 2017

Nested at-rules are a thing, they exist in the Paged Media spec: https://www.w3.org/TR/css3-page/#margin-boxes

@chrishtr
Copy link
Contributor

chrishtr commented Feb 4, 2021

Closing as duplicate of #126.

@chrishtr chrishtr closed this as completed Feb 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants