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

suggest font-display optional if font is preloaded, to reduce layout shift #11227

Closed
connorjclark opened this issue Aug 5, 2020 · 28 comments · Fixed by #11255
Closed

suggest font-display optional if font is preloaded, to reduce layout shift #11227

connorjclark opened this issue Aug 5, 2020 · 28 comments · Fixed by #11255

Comments

@connorjclark
Copy link
Collaborator

Starting in Chrome 83, preloaded fonts with font-display: optional will result in no FOIT or layout shifts. See https://web.dev/preload-optional-fonts/

Lighthouse has two related audits on this:

uses-rel-preload recommends requests that should be, but currently aren't, preloaded. This is based on a Lantern simulation.

font-display simply points out fonts that don't define a font-display rule. Our docs suggest they do swap, but any value passes this audit.

To keep this audit simple, I suggest we only look at already preloaded fonts. If a preloaded font is not marked optional, the audit suggests making it so to avoid shifts.

The alternative would be: basically duplicate/reuse uses-rel-preload with a focus on fonts and suggest both preloading AND optional in one go. I think this advice would be too broad, and is certainly more complex. Let's just encourage usage of optional if the developer has already decided to preload a font.

That's my pitch. Open to discussion.

@patrickhulce
Copy link
Collaborator

patrickhulce commented Aug 5, 2020

A few extra thoughts:

  1. How do we handle other font-display values? For example block would also prevent most layout shifts observable in Lighthouse and also makes sense for icon-based fonts. Would we try to get them to use optional? An optional icon font is pretty useless.
  2. Should we adjust our guidance in the font-display audit to just push folks toward optional for all fonts? If we don't, then we have conflicting guidance. If we do, then why bother with a separate audit that only surfaces a subset of the items in font-display?

I'm on the verge of saying a new audit isn't necessary, but I think there's room to say existing font-display is the performance guidance "choose anything but auto" and this audit is the CLS guidance "use optional when you can". Separate opportunities per-metric ringing any bells team? ;)

To keep this audit simple, I suggest we only look at already preloaded fonts. If a preloaded font is not marked optional, the audit suggests making it so to avoid shifts.

+1. I don't think we should duplicate the complicated preload logic here. We already have an audit that tells them to preload their font I like the idea of this being an extra upgrade on top you unlock :)

@connorjclark
Copy link
Collaborator Author

How do we handle other font-display values? For example block would also prevent most layout shifts observable in Lighthouse and also makes sense for icon-based fonts. Would we try to get them to use optional?

Not observable in Lighthouse because, as you know, CLS can't be simulated and is just what we observe. There's still a swap period for block fonts. On an actual mobile, 3g device the text is going to cause a shift ... although if the font is preloaded, that does mitigate the chance.

There's some work being done to detect an icon font. Maybe we can just ignore icon fonts.

An optional icon font is pretty useless.

Not in Chrome >=83, but I see your point for other browsers w/o this same optimization.

@patrickhulce
Copy link
Collaborator

patrickhulce commented Aug 5, 2020

CLS can't be simulated and is just what we observe. On an actual mobile, 3g device the text is going to cause a shift ... although if the font is preloaded, that does mitigate the chance.

Not just simulation. The block period is like 3s for a single request so all but the slowest connections will avoid a layout shift. Lighthouse Slow 4G applied throttling would be hard pressed to fall into the swap period.

Not in Chrome >=83

Hang on now that I've actually read the post on it... :)

I think optional is a mistake in every mobile circumstance you need a custom font. Maybe I'm misunderstanding, but preloading a font in Chrome 83 just converts the 100ms optional block period from rendering invisible text with mistaken dimensions into a global block period where nothing will be painted at all. After 100ms, the font will never be used. In that situation an icon font font-display: optional will be useless on all but the fastest connection types.

Given this reality, the advice IMO should be more like "oh you've set a font to be optional, then you should definitely preload it, regardless of where it is in the discovery tree because Chrome does this special thing" and it's a separate audit that would hypothetically report savings on CLS if we could compute it :) Not you should set every font you preload to be optional

@tdresser
Copy link

tdresser commented Aug 12, 2020

"Starting in Chrome 83, preloaded fonts with font-display: optional will result in no FOIT or layout shifts."
That doesn't require the font to be preloaded, does it? I thought font-display optional results in no FOIT or layout shifts always now.

I don't think this should be tied to preload.

"I think optional is a mistake in every mobile circumstance you need a custom font."
I agree. It's a mistake if you need a custom font, but should be used almost universally otherwise.

We can potentially have this audit identify icon fonts, and not propose migrating to font-display:optional in those cases.

@patrickhulce
Copy link
Collaborator

patrickhulce commented Aug 12, 2020

That doesn't require the font to be preloaded, does it?

The linked article says specifically

Starting in Chrome 83, link rel="preload" and font-display: optional can be combined to remove layout jank completely
Optimizations have landed in Chrome 83 to entirely remove the first render cycle for optional fonts that are preloaded with <link rel="preload'>

I interpret those statements to mean this optimization that the first render cycle is skipped only applies if the font is preloaded.

Later on though it says...

Although it is not necessary to preload an optional font, it greatly improves the chance for it to load before the first render cycle, especially if it is not yet stored in the browser's cache.

@housseindjirdeh would you mind helping us out here by clarifying your intent? :)

My practical experiments suggest the optional font will only be used if <link rel="preload"> is used, even if the preload doesn't actually result in the request happening any earlier.

@patrickhulce
Copy link
Collaborator

Alternatively @tdresser if you were just phrasing your insider Chrome knowledge as a question to be nicer and not actually wondering what happens like we are, then please continue to share your teachings are welcome! 😆

@xiaochengh
Copy link

Hi, I'm the one who implemented the change, so feel free to ping me if you want to know about the technical details.

I think it's good idea to suggest preloading for optional fonts. Without preloading, the font will pretty much not be used unless you just refreshed the page (so that you have a warm memory cache). Besides:

  • Using document.fonts.load() probably should count as a preload
  • We just landed a change into M86 to increase the chance that an optional font gets used, while still ensuring no layout shift. Its main purpose is to reduce breakage, so it's not an alternative of preloading, and developers shouldn't rely too much on it.

And I'm not sure we should suggest setting font-display: optional for preloaded fonts, regardless of icon font or not. Setting optional means the font won't be used when the user has a cold cache and slow network. So we should never set optional on a font that's critical to the page. This includes icon font and some other cases.

Alternative thoughts:

  • Maybe we can suggest optional for non-critical fonts. Not sure how to identify non-critical fonts, though
  • Maybe we can suggest optional with a warning that this comes at a cost that the font may not be used, and hope it won't be misused

@patrickhulce
Copy link
Collaborator

Awesome, thanks for chiming in @xiaochengh! It sounds like we should proceed as planned with your existing implementation in #11255 @lemcardenas 👍 (and perhaps add a note about allowing document.fonts.load in the future as well)

For clarity @xiaochengh, does preloading an optional font have any impact on the render cycle or prevent layout shifts? Or is it just about ensuring that the font is used?

Re-reading @housseindjirdeh's article and from your and @tdresser 's comments it sounds like the layout shifts issue specifically are avoided with all optional fonts in m83+ regardless of preload status?

@xiaochengh
Copy link

xiaochengh commented Aug 12, 2020

For clarity @xiaochengh, does preloading an optional font have any impact on the render cycle or prevent layout shifts? Or is it just about ensuring that the font is used?

It's about maximizing the chance that the font is used. This should cover cases like disk cache hit and fast network.

Unfortunately, there's no way to ensure an optional font being used. That's the cost of eliminating layout shift.

At a more technical level, font preloading (for all font-display values) does have a bit impact on the render cycle -- it slightly delays the first render cycle by no more than 50ms, so that font preloading has a better chance to finish (from disk cache, fast network, etc).

For developers, this isn't much more informative than just saying "preloading is good, do that!". So maybe let's not dump that many technical details to developers.

Re-reading @housseindjirdeh's article and from your and @tdresser 's comments it sounds like the layout shifts issue specifically are avoided with all optional fonts in m83+ regardless of preload status?

Exactly.

@patrickhulce
Copy link
Collaborator

Got it, great! Thank you so much @xiaochengh !

@paulirish
Copy link
Member

Wow this rabbit hole goes deep. :)

A few links I found along the way:

@paulirish
Copy link
Member

paulirish commented Aug 13, 2020

Boiling down webfonts and perf.. here's my take:

  1. Folks that want visual fidelity (incl icon font) ⇒ that's block behavior
  2. Folks that OK with compromising on custom font in the name of perf ⇒ swap and optional.
  3. Folks that want a best effort in-between middle-ground of the above two ⇒ the fallback boys.

If I had to characterize the diff between users of swap and optional.. Both care about peformance, and are okay with a fallback font on screen. But optional folks are okay with the fallback font remaining on page, whereas swap folks would prefer the custom webfont.

Block/swap/fallback all incur layout shifts at the some point in loading.


If they didn't chose any font-display value (aka auto), our existing font-display audit just tells devs "PICK A SIDE", which seems good. 😁

If they chose block, well... it's not my cup of tea but whatever.
If they chose optional, they should def add preload (assuming the font is actually used in the initial load)
If they chose swap, then they should def also add preload. And they should consider if they're comfortable going all the way to optional.
If they chose fallback, preloading is still valuable.

I don't see why we wouldn't recommend preload for all these cases, instead of just optional. Optional has a few special powers, but we'd have to sell people on optional to do that.


Two side notes...

One, hardly any font-display docs for optional mention the case of warm loads where the font is in disk/memory cache, but it was designed with that case in mind. Without that mentioned, I see why many folks don't really consider it.

Two, spending more time looking at all these, I'm increasingly convinced that fallback and optional are superior to block and swap. There is a 100ms difference in "text on the screen" between swap and fallback, but 100ms is nothing.

@paulirish
Copy link
Member

Concretely,

  1. Not a lot of people use optional already.. more should. I think this is it's own audit. eg use-performant-font-display-values.
  2. I don't see why new_audit: add preload-fonts audit #11255 should only recommend using preload just for optional folks. Seems better for all webfonts. An added wrinkle of it being a optional-users-plz-add-preload audit is that all the optimizations that are linked up are explicitly for optional, which they already are getting. How about we rescope the new audit to all webfonts?

@connorjclark
Copy link
Collaborator Author

I don't see why #11255 should only recommend using preload just for optional folks. Seems better for all webfonts. .. How about we rescope the new audit to all webfonts?

I think sites that preload all of their fonts (up to 5 or 6) end up less performant than preloading none or just a couple. I think recommending preloading everything would make things worse.

@paulirish
Copy link
Member

I don't see why #11255 should only recommend using preload just for optional folks. Seems better for all webfonts. .. How about we rescope the new audit to all webfonts?

I think sites that preload all of their fonts (up to 5 or 6) end up less performant than preloading none or just a couple. I think recommending preloading everything would make things worse.

Well, not every font in their CSS. Just the ones that get used during initial load, which are the ones we flag. Preloading those wouldn't make things worse.

@connorjclark
Copy link
Collaborator Author

Preloading those wouldn't make things worse.

How can we know with certainty? I've always assumed that more preloading = more contention with other possibly higher priority resources that the browser happens to request just after it sees and acts on the preload directives.

@benschwarz
Copy link
Contributor

Well, not every font in their CSS. Just the ones that get used during initial load, which are the ones we flag. Preloading those wouldn't make things worse.

A blanket rule of fonts that should be preloaded could be "text rendered using webfonts that appears in the viewport during load" would be a somewhat reasonable tip. Of course there's cases where a strapline might not be as important as major headings etc. Still… if it's a non scored audit, then it's still a useful tip.

If sites are using more than 3 webfonts/weights/variants, an audit rule to advise them to reduce the number of webfonts would also probably be useful imo.

@patrickhulce
Copy link
Collaborator

I also don't think Lighthouse should tell devs to preload all their fonts.

  1. I don't see the need for utmost urgency with swap and fallback. If you're already OK with the layout shifts then why create more early network contention with fonts? It's not really holding up any perf metrics and the jank coming ~600ms later isn't a gamechanger. It's a good idea if you don't have anything else going on, but a blanket "preload all of your fonts" would be quite bad advice for sites that use a lot of fonts.
  2. It's kind of a major pain to try and preload fonts coming from a third-party from URLs you don't know are stable. Knowing this I'd definitely prefer to be judicious with this advice and save it for when it has a large impact.
  3. Very related to 2, but uses-rel-preload has a 1st party requirement for a reason and violating it should have a significant benefit (like avoiding a situation where your font isn't used at all). If we'd like to revisit that 1st party decision then we should modify the existing uses-rel-preload audit rather than a separate audit entirely.

@patrickhulce
Copy link
Collaborator

patrickhulce commented Aug 14, 2020

Also @paulirish I just saw

  1. the documented optimizations don't have much to do with preload. :/

What led you to believe this? From my observations even when the font is already at the exact same depth in the discovery tree such that you would normally never preload it, it still has the effect of the optional font actually getting used if initial parsing takes any amount of time.

https://melodic-class.glitch.me/font-preload-optional.html

image

If you're referring to the m83 changes having nothing to do with preload, I agree there, but at the very least they inspired the web.dev article and the work here :)

@paulirish
Copy link
Member

@patrickhulce and I spent an hour on voice today going through this.

There was some contention (lol) on the value of bytes. "Are preloaded font bytes of equal value to renderblocking js/css bytes?" I think yes and Patrick not so much. My POV is that preloaded font bytes will move any webfont-triggered layout shift earlier (which is good) or nullify the need for a shift entirely (also good) and these bytes also carry benefits for the aesthetic fidelity (good).

We concluded that we are on opposite sides of one key question:

What is the probability that preloading a font means it makes the cut before the first render?


Secondarily, Patrick brought up the 3P Font Provider scenario (aka google fonts), which unfortunately makes this whole preloading thing VERY challenging. This requires further investigation. Perhaps we can find out what % of webfonts are from the 3rd parties (cases where the exact font URL (to preload) is unknowable).

We're going to followup on these questions separately to determine if we can broaden our font-preload recommendations.

@patrickhulce
Copy link
Collaborator

patrickhulce commented Aug 15, 2020

This test illustrates the challenges of preloading Google Fonts :/

The font format referenced by Google Fonts stylesheet changes subtly based on the UA with woff to desktop and woff2 to mobile (unclear why?)

@xiaochengh
Copy link

A note: The challenges of preloading with <link rel=preload> can be avoided with document.fonts.load(). We just need a font value that contains the font family name, weight and style. No need to know the actual URL.

@patrickhulce
Copy link
Collaborator

Thanks @xiaochengh that's great to know! Perhaps the associated codelab should be updated to follow that style as well then.

@patrickhulce
Copy link
Collaborator

@paulirish given that this work as originally described was completed, would you be comfortable leaving this closed and filing a new issue describing any followup work you wanted to see happen?

@patrickhulce
Copy link
Collaborator

I'm going to go ahead and close this out, but if there is any follow up work that needs to be done here, please feel free to file another issue or reopen.

@ryanhallinger
Copy link

ryanhallinger commented Dec 26, 2020

Hi there,

As it's related to font-display:optional, let me know if it needs to be opened as a new issue. It seems that https://web.dev/measure/ now validates if fonts with font-display:optional are preloaded.

It fails to however recognize link headers or link elements e.g. link rel="preload". Although these are specified, https://web.dev/measure/ continues to return Fonts with font-display: optional are not preloaded

https://imgur.com/a/ShQPYFf

Is this a known issue?

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

Successfully merging a pull request may close this issue.

10 participants