@financial-times/o-typography
Version:
Typographical styles for FT branded sites. Including typographical fundamentals such as font scales, vertical rhythm, and font fallbacks; plus styles for UI including links, headings, and titles. Other components build on o-typography to provide for more
164 lines (144 loc) • 4.52 kB
JavaScript
import FontFaceObserver from 'fontfaceobserver/fontfaceobserver.standalone.js';
class Typography {
/**
* Class constructor.
*
* @param {HTMLElement} [typographyEl] - The root element to apply typography classes.
* @param {object} [opts={loadOnInit: true, rejectOnFontLoadFailure: false, fontLoadedCookieName: 'o-typography-fonts-loaded'}] - An options object for configuring o-typography.
*/
constructor (typographyEl, opts) {
this.typographyEl = typographyEl;
this.fontLoadingPrefix = 'o-typography--loading-';
this.opts = opts || Typography.getOptions(typographyEl);
if (typeof this.opts.loadOnInit === 'undefined') {
this.opts.loadOnInit = true;
}
if (typeof this.opts.rejectOnFontLoadFailure === 'undefined') {
this.opts.rejectOnFontLoadFailure = false;
}
this.opts = Typography.checkOptions(this.opts);
this.hasRun = false;
this.fontConfigs = [
{
family: 'FinancierDisplayWeb',
weight: 'normal',
label: 'display'
},
{
family: 'MetricWeb',
weight: 'normal',
label: 'sans'
},
{
family: 'MetricWeb',
weight: 600,
label: 'sans-bold'
},
{
family: 'FinancierDisplayWeb',
weight: 700,
label: 'display-bold'
}
];
if (this.opts.loadOnInit) {
this.loadFonts();
}
}
/**
* Get the data attributes from the typographyEl. If typography is being set up
* declaratively, this method is used to extract the data attributes from
* the DOM.
*
* @param {HTMLElement} typographyEl - The typography element in the DOM (Required)
* @returns {Object.<string, any>} An option dictionary
*/
static getOptions(typographyEl) {
const dataset = Object(typographyEl.dataset);
return Object.keys(dataset).reduce((col, key) => { // Phantom doesn't like Object.entries :sob:
if (key === 'oComponent') {
return col; // Bail on data-o-component
}
const shortKey = key.replace(/^oTypography(\w)(\w+)$/, (m, m1, m2) => m1.toLowerCase() + m2);
try {
col[shortKey] = JSON.parse(dataset[key].replace(/\'/g, '"'));
} catch (e) {
col[shortKey] = dataset[key];
}
return col;
}, {});
}
/**
* Check the options passed in are valid, otherwise set defaults
*
* @param {object} opts - An Object with configuration options for typography
* @returns {object} opts
*/
static checkOptions(opts) {
if (!opts.fontLoadedCookieName) {
opts.fontLoadedCookieName = 'o-typography-fonts-loaded';
}
return opts;
}
checkFontsLoaded() {
return new RegExp(`(^|\s)${this.opts.fontLoadedCookieName}=1(;|$)`).test(document.cookie);
}
setCookie() {
const domain = /.ft.com$/.test(location.hostname) ? '.ft.com' : location.hostname;
// set cookie for a week
// TODO - use RUM to work out what a good value for this would actually be
document.cookie = `${this.opts.fontLoadedCookieName}=1;domain=${domain};path=/;max-age=${60 * 60 * 24 * 7}`;
}
removeLoadingClasses() {
this.fontConfigs.forEach((config) => {
this.typographyEl.classList.remove(`${this.fontLoadingPrefix}${config.label}`);
});
}
loadFonts() {
if (this.hasRun) {
return Promise.resolve();
}
if (this.checkFontsLoaded()) {
this.removeLoadingClasses();
this.setCookie();
this.hasRun = true;
return Promise.resolve();
}
const fontPromises = this.fontConfigs.map(fontConfig => {
return new FontFaceObserver(fontConfig.family, { weight: fontConfig.weight })
.load()
.then(() => {
this.typographyEl.classList.remove(`${this.fontLoadingPrefix}${fontConfig.label}`);
});
});
return Promise.all(fontPromises)
.then(() => {
// set value in cookie for subsequent visits
this.setCookie();
this.hasRun = true;
})
.catch(error => {
if (this.opts.rejectOnFontLoadFailure) {
throw error;
}
});
}
/**
* Initialise o-typography.
*
* @param {(HTMLElement | string)} rootElement - The root element to intialise o-typography on, or a CSS selector for the root element
* @param {object} [options={}] - An options object for configuring o-typography
* @returns {Typography} The Typography instance
*/
static init (rootElement, options) {
if (!rootElement) {
rootElement = document.documentElement;
}
if (!(rootElement instanceof HTMLElement)) {
rootElement = document.querySelector(rootElement);
}
if (rootElement instanceof HTMLElement && rootElement.matches('[data-o-component=o-typography]')) {
return new Typography(rootElement, options);
}
}
}
export default Typography;