From ca2b3f9930c077ac8152ccd8c818dbf224dfdbee Mon Sep 17 00:00:00 2001 From: Samuel Meuli Date: Sun, 31 Mar 2019 21:35:11 +0200 Subject: [PATCH] Prevent flicker on font changes * 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 --- src/font-manager/FontManager.ts | 37 +++++----- src/font-manager/loadFonts.ts | 91 ++++++++++++++++--------- src/font-manager/styles/declarations.ts | 8 ++- 3 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/font-manager/FontManager.ts b/src/font-manager/FontManager.ts index dc280d5..6e2613e 100644 --- a/src/font-manager/FontManager.ts +++ b/src/font-manager/FontManager.ts @@ -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"; /** @@ -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); } @@ -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 */ @@ -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(); + fontMap.set(fontFamily, font); + loadFontPreviews(fontMap, this.options.scripts, this.options.variants, this.selectorSuffix); } } @@ -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)); } } diff --git a/src/font-manager/loadFonts.ts b/src/font-manager/loadFonts.ts index fbeb75c..9badbcc 100644 --- a/src/font-manager/loadFonts.ts +++ b/src/font-manager/loadFonts.ts @@ -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 { - 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 { + // 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); + } +} diff --git a/src/font-manager/styles/declarations.ts b/src/font-manager/styles/declarations.ts index ac2507c..3d098ef 100644 --- a/src/font-manager/styles/declarations.ts +++ b/src/font-manager/styles/declarations.ts @@ -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);