Skip to content

Commit

Permalink
Prevent flicker on font changes
Browse files Browse the repository at this point in the history
* Remove the preview stylesheet before adding the declarations for the new active font
* Add the previous active font as fallback font of the new one
  • Loading branch information
samuelmeuli committed Mar 31, 2019
1 parent 8e9d0e3 commit ca2b3f9
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 55 deletions.
37 changes: 17 additions & 20 deletions src/font-manager/FontManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import getFontId from "../shared/fontId";
import { Font, FontList, Options, Script, Variant } from "../shared/types";
import getFontList from "./google-fonts/fontList";
import loadFonts from "./loadFonts";
import { applyActiveFont, applyFontPreview } from "./styles/declarations";
import { loadActiveFont, loadFontPreviews } from "./loadFonts";
import validatePickerId from "./utils/pickerId";

/**
Expand Down Expand Up @@ -64,7 +63,6 @@ export default class FontManager {
this.onChange = onChange;

// Download default font and add it to the empty font list
this.activeFontFamily = defaultFamily;
this.addFont(defaultFamily, false);
this.setActiveFont(defaultFamily);
}
Expand Down Expand Up @@ -102,21 +100,13 @@ export default class FontManager {
}
// Download previews for all fonts in list except for default font (its full font has already
// been downloaded)
this.downloadFontPreviews(Array.from(this.fonts.values()).slice(1));
const fontsToLoad = new Map(this.fonts);
fontsToLoad.delete(this.activeFontFamily);
loadFontPreviews(fontsToLoad, this.options.scripts, this.options.variants, this.selectorSuffix);

return this.fonts;
}

/**
* Download and apply characters required for writing out all font names of the provided fonts
*/
private downloadFontPreviews(fonts: Font[]): void {
loadFonts(fonts, this.options.scripts, this.options.variants, true);
fonts.forEach(font => {
applyFontPreview(font, this.selectorSuffix);
});
}

/**
* Return font map
*/
Expand All @@ -133,8 +123,12 @@ export default class FontManager {
id: getFontId(fontFamily),
};
this.fonts.set(fontFamily, font);

// Download font preview unless specified not to
if (downloadPreview) {
this.downloadFontPreviews([font]);
const fontMap: FontList = new Map<string, Font>();
fontMap.set(fontFamily, font);
loadFontPreviews(fontMap, this.options.scripts, this.options.variants, this.selectorSuffix);
}
}

Expand All @@ -161,12 +155,15 @@ export default class FontManager {
console.error(`Cannot update active font: "${fontFamily}" is not in the font list`);
return;
}
const previousFontFamily = this.activeFontFamily;
const activeFont = this.fonts.get(fontFamily);
this.activeFontFamily = fontFamily;

loadFonts([activeFont], this.options.scripts, this.options.variants, false).then(() =>
this.onChange(activeFont),
);
applyActiveFont(activeFont, this.selectorSuffix);
loadActiveFont(
activeFont,
previousFontFamily,
this.options.scripts,
this.options.variants,
this.selectorSuffix,
).then(() => this.onChange(activeFont));
}
}
91 changes: 58 additions & 33 deletions src/font-manager/loadFonts.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,77 @@
import { Font, Script, Variant } from "../shared/types";
import { Font, FontList, Script, Variant } from "../shared/types";
import extractFontStyles from "./google-fonts/extractFontStyles";
import getStylesheet from "./google-fonts/fontStylesheet";
import { applyActiveFont, applyFontPreview } from "./styles/declarations";
import { createStylesheet, deleteStylesheet, stylesheetExists } from "./styles/stylesheets";

/**
* Get the Google Fonts stylesheet for the specified fonts (in the specified scripts and variants),
* split up the returned CSS rules into stylesheets per font and add these to the document head.
* If previewsOnly is set to true, only download the font parts for writing all characters contained
* in the fonts' names.
* Get the Google Fonts stylesheet for the specified font (in the specified scripts and variants,
* only the characters needed for creating the font previews), add the necessary CSS declarations to
* apply them and add the fonts' stylesheets to the document head
*/
export default async function loadFonts(
fonts: Font[],
export async function loadFontPreviews(
fonts: FontList,
scripts: Script[],
variants: Variant[],
previewsOnly: boolean,
selectorSuffix: string,
): Promise<void> {
let fontsToFetch: Font[];
if (previewsOnly) {
// Only load previews of fonts which don't have a stylesheet (for preview or full font) yet
fontsToFetch = fonts.filter(
font => !stylesheetExists(font.id, false) && !stylesheetExists(font.id, true),
);
} else {
// Only load fonts which don't have a stylesheet (for full font) yet
fontsToFetch = fonts.filter(font => !stylesheetExists(font.id, false));
}
// Only load previews of fonts which don't have a stylesheet (for preview or full font) yet
const fontsArray: Font[] = Array.from(fonts.values());
const fontsToFetch = fontsArray
.map((font: Font) => font.id)
.filter(fontId => !stylesheetExists(fontId, false) && !stylesheetExists(fontId, true));

// Get Google Fonts stylesheet containing all requested styles
const response = await getStylesheet(fonts, scripts, variants, previewsOnly);
const response = await getStylesheet(fontsArray, scripts, variants, true);
// Parse response and assign styles to the corresponding font
const fontStyles = extractFontStyles(response);

// Create separate stylesheets for the fonts
fontsToFetch.forEach(font => {
// Make sure response contains styles for the font
if (!(font.id in fontStyles)) {
console.error(
`Missing styles for font "${font.family}" (fontId "${font.id}") in Google Fonts response`,
);
return;
}
if (!previewsOnly) {
// Delete preview stylesheet if exists
if (stylesheetExists(font.id, true)) {
deleteStylesheet(font.id);
fontsArray.forEach(font => {
applyFontPreview(font, selectorSuffix);

// Add stylesheets for fonts which need to be downloaded
if (fontsToFetch.includes(font.id)) {
// Make sure response contains styles for the font
if (!(font.id in fontStyles)) {
console.error(
`Missing styles for font "${font.family}" (fontId "${font.id}") in Google Fonts response`,
);
return;
}
createStylesheet(font.id, fontStyles[font.id], true);
}
// Create stylesheet with the font's corresponding styles from the response
createStylesheet(font.id, fontStyles[font.id], previewsOnly);
});
}

/**
* Get the Google Fonts stylesheet for the specified font (in the specified scripts and variants),
* add the necessary CSS declarations to apply it and add the font's stylesheet to the document head
*/
export async function loadActiveFont(
font: Font,
previousFontFamily: string,
scripts: Script[],
variants: Variant[],
selectorSuffix: string,
): Promise<void> {
// Only load font if it doesn't have a stylesheet yet
if (stylesheetExists(font.id, false)) {
// Add CSS declaration to apply the new active font
applyActiveFont(font, previousFontFamily, selectorSuffix);
} else {
// Get Google Fonts stylesheet containing all requested styles
const fontStyle = await getStylesheet([font], scripts, variants, false);

// Delete preview stylesheet if exists
if (stylesheetExists(font.id, true)) {
deleteStylesheet(font.id);
}

// Add CSS declaration to apply the new active font
applyActiveFont(font, previousFontFamily, selectorSuffix);

// Create stylesheet with the font's corresponding styles from the response
createStylesheet(font.id, fontStyle, false);
}
}
8 changes: 6 additions & 2 deletions src/font-manager/styles/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ export function applyFontPreview(previewFont: Font, selectorSuffix: string): voi
/**
* Add/update declaration for applying the current active font
*/
export function applyActiveFont(activeFont: Font, selectorSuffix: string): void {
export function applyActiveFont(
activeFont: Font,
previousFontFamily: string,
selectorSuffix: string,
): void {
const style = `
.apply-font${selectorSuffix} {
font-family: "${activeFont.family}";
font-family: "${activeFont.family}"${previousFontFamily ? `, "${previousFontFamily}"` : ""};
}
`;
const styleNode = document.createTextNode(style);
Expand Down

0 comments on commit ca2b3f9

Please sign in to comment.