kelyvin / ghost-caffeine-theme

A minimalist, Material Design inspired Ghost Theme for optimal desktop and mobile experiences
https://caffeinecoding.com
MIT License
333 stars 97 forks source link

Optimise font loading #4

Closed andrewlock closed 8 years ago

andrewlock commented 8 years ago

I have been experimenting with improving the font loading experience using WebFontLoader, and FontFaceObserver and I think I've got pretty much the best options here now.

In order to avoid the Flash of Invisible Text on Safari, I have added explicit fallback fonts, and only use the web fonts once it's finished loading.

This still leaves the Flash of Unstyled Text but I've tried to optimise that too - session storage is used to store a flag to indicate the fonts have already been loaded, and a small script in the head applies the necessary class immediately if necessary which should prevent the FOUT completely on subsequent loads.

What do you think?

kelyvin commented 8 years ago

If I recall correctly, for firefox and chrome there is no invisible flash of text because the browsers will use a fallback font before the google font is properly loaded. I haven't done extensive testing on Safari so I think this is an awesome workaround for the Safari experience. Does the flash of text also occur for when running safari on portable iOS devices? (e.g. iPhones and iPads). If so, I can defiintely understand the need for this implementation.

However, one of my only concerns involves this commit: https://github.com/kelyvin/caffeine-theme/pull/4/commits/1bd47d064d4c15610e0d9d82829cc89a71aff173 where you move the font call to the head. If i recall correctly, this will block the page from loading any content until the font is completely downloaded onto the page. So wouldn't this inherently prevent the flash of invisible text because the page will stay blank until the font is downloaded? I might be missing something here, so can you go over the reason for this change? Also by moving the font to the head, this will most likely impact the page speed index for the theme, especially for mobile. On mobile devices, user will need to download more assets before the page can render, which will affect "perceived" page speed.

Do you have any thoughts on this and can you help clarify some of my concerns above? Thanks!

andrewlock commented 8 years ago

You are correct, Firefox definitely doesn't have the FOIT, and while all the resources I could find suggest Chrome does, I couldn't reproduce it for me either so I think those are OK.

iOS devices are the big problems, as may of those definitely do have the problem, and as they may well be on slower connections, and the timeout for displaying unstyled text instead of waiting for the fonts is 30s(!), it's definitely worth it for them.

Wrt to your comments about putting the fonts in the head, I agonised over this for quite some time, and couldn't actually find a log of good references about it. The best references I could find for doing this were these, in particular the first one

https://www.filamentgroup.com/lab/font-events.html https://github.com/filamentgroup/font-loading/blob/master/font-events.html https://css-tricks.com/loading-web-fonts-with-the-web-font-loader/ http://bramstein.com/writing/web-font-loading-patterns.html

The key point in my justification for moving it was that although it will block rendering until the initial google fonts api call completes, it does not block until all the fonts are downloaded (when using fontfaceobserver implementation)

My reasoning for moving it to the head was as follows:

As far as I can tell, that is inline with the recommendations from the top link, but as they don't use google fonts, they directly inline the @font-face rules in the head directly. That seems like would actually be the best option, and is inline with the page speed index recommendations, however then we essentially need to directly embed the font-face rules Google fonts returns, accounting for the user-agent sniffing it does.

I was tempted to give that a go, but seems a little fraught with danger!

andrewlock commented 8 years ago

Thinking about this a little more, I wonder if my desire to completely remove the FOUT is due to my experience when developing. Sometimes I would realise I wasn't using the browsersync, and hit refresh to see changes, and would be hit with the ugly FOUT each time. With the changes in this PR, that's gone away completely.

However most users presumably won't be hitting refresh. If they just click around the site, they won't be seeing the FOUT everywhere anyway as we're not doing full server side reloads.

So maybe the best compromise is to remove the last 2 commits of the PR? That way Safari users get the benefit of a FOUT instead of FOIT and the page-speed-index is unaffected, but the FOUT is always there for subsequent full page loads, even though the fonts are technically loaded.

Either that or we look at inlining the google @font-face rules directly in the head to get the overall best results as per here.

andrewlock commented 8 years ago

And in fact, I've just run across which handles the last section for you http://www.localfont.com/:

Which gets this for us:

@font-face {
  font-family: 'Raleway';
  font-weight: 400;
  font-style: normal;
  src: url('http://fonts.gstatic.com/s/raleway/v10/JDau3G46nqY5-B-S9E_nwg.eot');
  src: url('http://fonts.gstatic.com/s/raleway/v10/JDau3G46nqY5-B-S9E_nwg.eot?#iefix') format('embedded-opentype'),
       local('Raleway'),
       local('Raleway-regular'),
       url('http://fonts.gstatic.com/s/raleway/v10/QAUlVt1jXOgQavlW5wEfxQLUuEpTyoUstqEm5AMlJo4.woff2') format('woff2'),
       url('http://fonts.gstatic.com/s/raleway/v10/cIFypx4yrWPDz3zOxk7hIQLUuEpTyoUstqEm5AMlJo4.woff') format('woff'),
       url('http://fonts.gstatic.com/s/raleway/v10/bIcY3_3JNqUVRAQQRNVteQ.ttf') format('truetype'),
       url('http://fonts.gstatic.com/l/font?kit=IP_NSg73bUZGuyb3zF6qcQ&skey=30a27f2564731c64&v=v10#Raleway') format('svg');
}

@font-face {
  font-family: 'Raleway';
  font-weight: 700;
  font-style: normal;
  src: url('http://fonts.gstatic.com/s/raleway/v10/JbtMzqLaYbbbCL9X6EvaI1QlYEbsez9cZjKsNMjLOwM.eot');
  src: url('http://fonts.gstatic.com/s/raleway/v10/JbtMzqLaYbbbCL9X6EvaI1QlYEbsez9cZjKsNMjLOwM.eot?#iefix') format('embedded-opentype'),
       local('Raleway Bold'),
       local('Raleway-700'),
       url('http://fonts.gstatic.com/s/raleway/v10/JbtMzqLaYbbbCL9X6EvaIwzyDMXhdD8sAj6OAJTFsBI.woff2') format('woff2'),
       url('http://fonts.gstatic.com/s/raleway/v10/JbtMzqLaYbbbCL9X6EvaI73hpw3pgy2gAi-Ip7WPMi0.woff') format('woff'),
       url('http://fonts.gstatic.com/s/raleway/v10/JbtMzqLaYbbbCL9X6EvaIy3USBnSvpkopQaUR-2r7iU.ttf') format('truetype'),
       url('http://fonts.gstatic.com/l/font?kit=JbtMzqLaYbbbCL9X6EvaI6WUboTb-jS2tyCOQMtm97g&skey=e507c3e2b7915ad1&v=v10#Raleway') format('svg');
}

@font-face {
  font-family: 'Roboto Slab';
  font-weight: 300;
  font-style: normal;
  src: url('http://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJbXcjzEax2LfQAlK8DdMzhA.eot');
  src: url('http://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJbXcjzEax2LfQAlK8DdMzhA.eot?#iefix') format('embedded-opentype'),
       local('Roboto Slab Light'),
       local('Roboto-Slab-300'),
       url('http://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJdTIkQYohD4BpHvJ3NvbHoA.woff2') format('woff2'),
       url('http://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJfR_54zmj3SbGZQh3vCOwvY.woff') format('woff'),
       url('http://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJbfB31yxOzP-czbf6AAKCVo.ttf') format('truetype'),
       url('http://fonts.gstatic.com/l/font?kit=dazS1PrQQuCxC3iOAJFEJT4dnM32POWavC8T_whbZeI&skey=5b48fccf0dc70db6&v=v6#RobotoSlab') format('svg');
}

@font-face {
  font-family: 'Roboto Slab';
  font-weight: 400;
  font-style: normal;
  src: url('http://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZmfQcKutQXcIrRfyR5jdjY8.eot');
  src: url('http://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZmfQcKutQXcIrRfyR5jdjY8.eot?#iefix') format('embedded-opentype'),
       local('Roboto Slab Regular'),
       local('Roboto-Slab-regular'),
       url('http://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zogp9Q8gbYrhqGlRav_IXfk.woff2') format('woff2'),
       url('http://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZrrIa-7acMAeDBVuclsi6Gc.woff') format('woff'),
       url('http://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zp0EAVxt0G0biEntp43Qt6E.ttf') format('truetype'),
       url('http://fonts.gstatic.com/l/font?kit=y7lebkjgREBJK96VQi37ZnNsigHlcrQGNV8nkUSFQfc&skey=a9ad6a4717e923f0&v=v6#RobotoSlab') format('svg');
}

Given that the theme doesn't support < IE9, we could ditch the eot fonts, and just leave the last 4 formats. The only aspect of this I'm unclear on is whether these urls would include international fonts e.g. latin-ext, greek etc, as looking at the css google returns for a chrome request gives:

/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  src: local('Raleway'), local('Raleway-Regular'), url(https://fonts.gstatic.com/s/raleway/v10/YZaO6llzOP57DpTBv2GnyFKPGs1ZzpMvnHX-7fPOuAc.woff2) format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  src: local('Raleway'), local('Raleway-Regular'), url(https://fonts.gstatic.com/s/raleway/v10/QAUlVt1jXOgQavlW5wEfxQLUuEpTyoUstqEm5AMlJo4.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  src: local('Raleway Bold'), local('Raleway-Bold'), url(https://fonts.gstatic.com/s/raleway/v10/WmVKXVcOuffP_qmCpFuyzQsYbbCjybiHxArTLjt7FRU.woff2) format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  src: local('Raleway Bold'), local('Raleway-Bold'), url(https://fonts.gstatic.com/s/raleway/v10/JbtMzqLaYbbbCL9X6EvaIwzyDMXhdD8sAj6OAJTFsBI.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJUExlR2MysFCBK8OirNw2kM.woff2) format('woff2');
  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJWdsm03krrxlabhmVQFB99s.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJSJ0caWjaSBdV-xZbEgst_k.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJWMSHb9EAJwuSzGfuRChQzQ.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJepRBTtN4E2_qSPBnw6AgMc.woff2) format('woff2');
  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJdDnm4qiMZlH5rhYv_7LI2Y.woff2) format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 300;
  src: local('Roboto Slab Light'), local('RobotoSlab-Light'), url(https://fonts.gstatic.com/s/robotoslab/v6/dazS1PrQQuCxC3iOAJFEJdTIkQYohD4BpHvJ3NvbHoA.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZvZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2');
  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
/* cyrillic */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zl4sYYdJg5dU2qzJEVSuta0.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZlBW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2');
  unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zgt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2');
  unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZtDiNsR5a-9Oe_Ivpu8XWlY.woff2) format('woff2');
  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37ZqE8kM4xWR1_1bYURRojRGc.woff2) format('woff2');
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Roboto Slab';
  font-style: normal;
  font-weight: 400;
  src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(https://fonts.gstatic.com/s/robotoslab/v6/y7lebkjgREBJK96VQi37Zogp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
kelyvin commented 8 years ago

Hi Andrew,

Sorry for the late reply and this is an AWESOME analysis + rundown of the font loading concerns you are experiencing. I completely 100% agree that the flash of invisible text must go. So I will definitely at least take your first two commits.

However, there are many trains of thought on what is the right approach (i.e. whether to remove FOUT completely or leave it for better perceived page performance, etc.). From what I've gathered from your thorough research as well as some research on my own, it seems to all be around preference. As a user, I personally would not want to see the flash of unstyled text. It sort of looks... "buggy". If I were a non-technical user, I would think that was a bug! On the flip side, I think you bring up a super valid point in the following comment:

However most users presumably won't be hitting refresh. If they just click around the site, they won't be seeing the FOUT everywhere anyway as we're not doing full server side reloads.

So even though that flash of unstyled text is not ideal, our users will presumably only see this once and in exchange get a faster page load. It's a tough call to make. So what I will do for now is pull your changes locally, test around a bit, and make a call on whether I should also incorporate your last two commits.

Does that sound okay?

Thanks!

andrewlock commented 8 years ago

No problem on the delay. I totally agree with the fact that a lot of this is down to preference.

That all sounds good to me, whatever you're comfortable with, it is your website's theme after all!:smile:

I was playing a little more and I think I've got a pretty good solution going now using the direct font-face embedding approach, and setting a flag for when fonts are already downloaded. It works well for me in that I know I'm not going to be using Cyrillic or Greek fonts or anything, so embedding just the top 2 @font-face declarations from my previous post directly in the html does the trick, and eliminates the need to hit google in the head (which your rightly concerned about).

I've pushed up an alternative branch to this one as optimise_font_loading_direct_embed which uses the first 2 commits as you mentioned, but then embeds the font faces directly to remove the blocking http request, and completely removes FOUT on subsequent hits.

That one may well be more palatable!

kelyvin commented 8 years ago

Awesome! Do you mind submitting a separate pull request that points to that alternative branch instead?

andrewlock commented 8 years ago

Sure, just added (and rebased)

kelyvin commented 8 years ago

Closing this pull request because a newer version has been pulled from: https://github.com/kelyvin/caffeine-theme/pull/6