列出用户浏览器可以显示的所有字体

133

在JavaScript中有没有一种方法可以获取浏览器能够显示的所有字体(或字体系列)的名称?(我想给用户一个下拉菜单,列出所有可用的字体,并允许用户选择一个字体。)我不想预先硬编码此列表或从服务器发送它。(直观地看,浏览器应该知道它拥有哪些字体,并且这应该以某种方式暴露给JavaScript。)

12个回答

71

有的!我很高兴你问了这个问题,因为现在我也想用它。

http://www.lalit.org/lab/javascript-css-font-detect

代码来自于 http://www.lalit.org/wordpress/wp-content/uploads/2008/05/fontdetect.js?ver=0.3

/**
 * JavaScript code to detect available availability of a
 * particular font in a browser using JavaScript and CSS.
 *
 * Author : Lalit Patel
 * Website: http://www.lalit.org/lab/javascript-css-font-detect/
 * License: Apache Software License 2.0
 *          http://www.apache.org/licenses/LICENSE-2.0
 * Version: 0.15 (21 Sep 2009)
 *          Changed comparision font to default from sans-default-default,
 *          as in FF3.0 font of child element didn't fallback
 *          to parent element if the font is missing.
 * Version: 0.2 (04 Mar 2012)
 *          Comparing font against all the 3 generic font families ie,
 *          'monospace', 'sans-serif' and 'sans'. If it doesn't match all 3
 *          then that font is 100% not available in the system
 * Version: 0.3 (24 Mar 2012)
 *          Replaced sans with serif in the list of baseFonts
 */

/**
 * Usage: d = new Detector();
 *        d.detect('font name');
 */
var Detector = function() {
    // a font will be compared against all the three default fonts.
    // and if it doesn't match all 3 then that font is not available.
    var baseFonts = ['monospace', 'sans-serif', 'serif'];

    //we use m or w because these two characters take up the maximum width.
    // And we use a LLi so that the same matching fonts can get separated
    var testString = "mmmmmmmmmmlli";

    //we test using 72px font size, we may use any size. I guess larger the better.
    var testSize = '72px';

    var h = document.getElementsByTagName("body")[0];

    // create a SPAN in the document to get the width of the text we use to test
    var s = document.createElement("span");
    s.style.fontSize = testSize;
    s.innerHTML = testString;
    var defaultWidth = {};
    var defaultHeight = {};
    for (var index in baseFonts) {
        //get the default width for the three base fonts
        s.style.fontFamily = baseFonts[index];
        h.appendChild(s);
        defaultWidth[baseFonts[index]] = s.offsetWidth; //width for the default font
        defaultHeight[baseFonts[index]] = s.offsetHeight; //height for the defualt font
        h.removeChild(s);
    }

    function detect(font) {
        var detected = false;
        for (var index in baseFonts) {
            s.style.fontFamily = font + ',' + baseFonts[index]; // name of the font along with the base font for fallback.
            h.appendChild(s);
            var matched = (s.offsetWidth != defaultWidth[baseFonts[index]] || s.offsetHeight != defaultHeight[baseFonts[index]]);
            h.removeChild(s);
            detected = detected || matched;
        }
        return detected;
    }

    this.detect = detect;
};

概述

它是如何工作的?

这段代码基于一个简单的原理,即每个字符在不同的字体中显示不同。因此,对于相同字号的相同字符字符串,不同字体将采用不同的宽度和高度。


4
非常狡猾。这太棒了。 - recursive
7
谢谢,是的,一旦我有一个字体列表来测试已安装的字体,这很有用,但问题在于如何首先生成字体名称列表。 - mattsh
57
这只会告诉你字体是否已安装,是/否的答案。 - rektide
3
起初我认为它很棒,但后来发现了一些问题。主要问题是每个浏览器返回不同的结果。显然不可靠。 - Błażej Klisz
18
有趣且有用,但没有回答问题。这并没有检索浏览器中可用字体的名称。因此我不情愿地给出负一分。 - BenjaminGolder
显示剩余8条评论

44

FontFaceSet.check()解决方案

  • 检测所有可用字体是一种常见的浏览器指纹技术,因此不太可能添加任何直接返回列表的JS API。
  • FontFaceSet.check()的支持已经足够好可以使用,但需要一个回退,例如对于旧版本浏览器可参考这个答案
  • 检查以下字体列表需要150毫秒以上,因此只有在必要时才需要运行并缓存结果。

Windows 10字体列表

'Arial',
'Arial Black',
'Bahnschrift',
'Calibri',
'Cambria',
'Cambria Math',
'Candara',
'Comic Sans MS',
'Consolas',
'Constantia',
'Corbel',
'Courier New',
'Ebrima',
'Franklin Gothic Medium',
'Gabriola',
'Gadugi',
'Georgia',
'HoloLens MDL2 Assets',
'Impact',
'Ink Free',
'Javanese Text',
'Leelawadee UI',
'Lucida Console',
'Lucida Sans Unicode',
'Malgun Gothic',
'Marlett',
'Microsoft Himalaya',
'Microsoft JhengHei',
'Microsoft New Tai Lue',
'Microsoft PhagsPa',
'Microsoft Sans Serif',
'Microsoft Tai Le',
'Microsoft YaHei',
'Microsoft Yi Baiti',
'MingLiU-ExtB',
'Mongolian Baiti',
'MS Gothic',
'MV Boli',
'Myanmar Text',
'Nirmala UI',
'Palatino Linotype',
'Segoe MDL2 Assets',
'Segoe Print',
'Segoe Script',
'Segoe UI',
'Segoe UI Historic',
'Segoe UI Emoji',
'Segoe UI Symbol',
'SimSun',
'Sitka',
'Sylfaen',
'Symbol',
'Tahoma',
'Times New Roman',
'Trebuchet MS',
'Verdana',
'Webdings',
'Wingdings',
'Yu Gothic',

macOS/iOS 字体列表

'American Typewriter',
'Andale Mono',
'Arial',
'Arial Black',
'Arial Narrow',
'Arial Rounded MT Bold',
'Arial Unicode MS',
'Avenir',
'Avenir Next',
'Avenir Next Condensed',
'Baskerville',
'Big Caslon',
'Bodoni 72',
'Bodoni 72 Oldstyle',
'Bodoni 72 Smallcaps',
'Bradley Hand',
'Brush Script MT',
'Chalkboard',
'Chalkboard SE',
'Chalkduster',
'Charter',
'Cochin',
'Comic Sans MS',
'Copperplate',
'Courier',
'Courier New',
'Didot',
'DIN Alternate',
'DIN Condensed',
'Futura',
'Geneva',
'Georgia',
'Gill Sans',
'Helvetica',
'Helvetica Neue',
'Herculanum',
'Hoefler Text',
'Impact',
'Lucida Grande',
'Luminari',
'Marker Felt',
'Menlo',
'Microsoft Sans Serif',
'Monaco',
'Noteworthy',
'Optima',
'Palatino',
'Papyrus',
'Phosphate',
'Rockwell',
'Savoye LET',
'SignPainter',
'Skia',
'Snell Roundhand',
'Tahoma',
'Times',
'Times New Roman',
'Trattatello',
'Trebuchet MS',
'Verdana',
'Zapfino',

FontFaceSet.check()

const fontCheck = new Set([
  // Windows 10
'Arial', 'Arial Black', 'Bahnschrift', 'Calibri', 'Cambria', 'Cambria Math', 'Candara', 'Comic Sans MS', 'Consolas', 'Constantia', 'Corbel', 'Courier New', 'Ebrima', 'Franklin Gothic Medium', 'Gabriola', 'Gadugi', 'Georgia', 'HoloLens MDL2 Assets', 'Impact', 'Ink Free', 'Javanese Text', 'Leelawadee UI', 'Lucida Console', 'Lucida Sans Unicode', 'Malgun Gothic', 'Marlett', 'Microsoft Himalaya', 'Microsoft JhengHei', 'Microsoft New Tai Lue', 'Microsoft PhagsPa', 'Microsoft Sans Serif', 'Microsoft Tai Le', 'Microsoft YaHei', 'Microsoft Yi Baiti', 'MingLiU-ExtB', 'Mongolian Baiti', 'MS Gothic', 'MV Boli', 'Myanmar Text', 'Nirmala UI', 'Palatino Linotype', 'Segoe MDL2 Assets', 'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Historic', 'Segoe UI Emoji', 'Segoe UI Symbol', 'SimSun', 'Sitka', 'Sylfaen', 'Symbol', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana', 'Webdings', 'Wingdings', 'Yu Gothic',
  // macOS
  'American Typewriter', 'Andale Mono', 'Arial', 'Arial Black', 'Arial Narrow', 'Arial Rounded MT Bold', 'Arial Unicode MS', 'Avenir', 'Avenir Next', 'Avenir Next Condensed', 'Baskerville', 'Big Caslon', 'Bodoni 72', 'Bodoni 72 Oldstyle', 'Bodoni 72 Smallcaps', 'Bradley Hand', 'Brush Script MT', 'Chalkboard', 'Chalkboard SE', 'Chalkduster', 'Charter', 'Cochin', 'Comic Sans MS', 'Copperplate', 'Courier', 'Courier New', 'Didot', 'DIN Alternate', 'DIN Condensed', 'Futura', 'Geneva', 'Georgia', 'Gill Sans', 'Helvetica', 'Helvetica Neue', 'Herculanum', 'Hoefler Text', 'Impact', 'Lucida Grande', 'Luminari', 'Marker Felt', 'Menlo', 'Microsoft Sans Serif', 'Monaco', 'Noteworthy', 'Optima', 'Palatino', 'Papyrus', 'Phosphate', 'Rockwell', 'Savoye LET', 'SignPainter', 'Skia', 'Snell Roundhand', 'Tahoma', 'Times', 'Times New Roman', 'Trattatello', 'Trebuchet MS', 'Verdana', 'Zapfino',
].sort());

(async() => {
  await document.fonts.ready;

  const fontAvailable = new Set();

  for (const font of fontCheck.values()) {
    if (document.fonts.check(`12px "${font}"`)) {
      fontAvailable.add(font);
    }
  }

  console.log('Available Fonts:', [...fontAvailable.values()]);
})();


谢谢,这正是我寻找的内容,可以用于最终的网页设计,同时使用本地系统字体来获得更高的可靠性,在显示内容或解析页面时不会占用太多CPU。 - user5781320
4
2022年的此刻,这是最佳答案。 - 1valdis
1
最佳答案,在Angular中也易于使用 - Brain
1
这应该是这个问题的被接受的答案。我正在构建一个简单的记事本web应用程序,这是一个很好的解决方案,可以为文本编辑区域显示用户可以选择的字体。+1 - TerranRich
1
这很好,但我认为fonts.check只在Chrome上有效 - 标准情况下,如果字体不可用,fonts.check()也应该返回true,参考来源:https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/check。我检查了一下,在Safari中,它也会为您传递的任何虚构字体返回`true`。 - olivarra1

40

有一种方法可以使用 document.fonts 来实现这个。

返回的值是文档的 FontFaceSet 接口。FontFaceSet 接口对于加载新字体、检查先前加载字体的状态等非常有用。

  • 返回的值会包含字重、样式等详细信息。
function listFonts() {
  return [...document.fonts.values()];
}

仅返回字体家族。
function listFontFamilies() {
  const fontFaces = [...document.fonts.values()];
  const families = fontFaces.map(font => font.family);

  // converted to set then to array to remove duplicates
  return [...new Set(families)];
}

我在HTML中没有链接任何字体进行了测试,然后链接了Roboto字体,再次测试发现它被添加到了结果中。

这段代码片段完美运行,谢谢!listFonts() { let fonts = document['fonts']; const it = fonts.entries(); let arr = []; let done = false; while (!done) { const font = it.next(); if (!font.done) { arr.push(font.value[0].family); } else { done = font.done; } } // 转换为集合再转回数组以过滤重复值 return [...new Set(arr)]; } - rufreakde
3
当我在Firefox中运行这个时,它只显示网页字体(如FontAwesome)。 - Tim Davis
4
在Chrome浏览器中(在此页面的控制台中),我运行了Array.from(document.fonts)命令,结果显示我有两种字体,都属于“Roboto Slab”字体家族。显然,我在计算机上安装了更多的字体。 - Trade-Ideas Philip
这个可行,应该是被接受的答案。谢谢。 - Really Nice Code
8
似乎它只显示从服务器下载的字体。 - I wrestled a bear once.
你能告诉我如何在React JS中使用它来获取所有可用字体并显示在下拉菜单中,以便可以更改文本区域内的字体吗? - Sharjeel Faiq

35

JavaScript版本有些不稳定。它通过遍历已知的字体并进行测试来获取字体。

最准确的方法(虽然需要使用专有插件)是使用Flash。在这里,您可以获取字体列表,而无需使用尺寸逐个测试字体。

你将需要决定是为了得到精确列表而牺牲一些设备的支持(iDevices、没有Flash插件的浏览器等),还是仅通过JavaScript获得更好支持的部分列表


31
@Jared提到Flash?我没有说它是唯一的解决方案,我提到它是检测字体最准确的方式。 - alex
5
@alex 是的。这可能会给开发人员,特别是新手,留下错误的印象。我建议您编辑您的回答,更好地解释使用Flash的优缺点,例如只需写上“不建议使用,但是...”或类似的内容。 - Jared
21
@Jared,我需要从头开始编写所有答案来提供信息给那些可能是新手的读者吗?我确实解释了Flash需要专有插件,但我也提到它目前是获取所有可用字体的唯一途径(JavaScript方法只能检测字体的子集,这对大多数用例可能已经足够好了)。我也不喜欢使用Flash,但目前这是我们完成此任务的唯一选择。 - alex
7
看到最后一段了吗?你可能希望再读一遍。 - alex
10
那段话一直存在。 - alex
显示剩余14条评论

12
简而言之,2020 年浏览器中关于字体检测方面的变化不大,除了现在使用 Flash 是一个更糟糕的选择。目前没有浏览器本地系统可以“列出”所有可用的字体。这是有意为之的,以避免浏览器指纹识别及其安全影响。然而,浏览器将允许您使用 FontFaceSet API 检查字体是否已加载/准备就绪。它在现代浏览器中得到了很好的支持。这旨在显示 Web 字体是否已完全下载,但它也适用于系统字体。关键是,您必须提供要检查的字体列表。
所以,结合一个不总是准确的用户代理test,您可以为每种设备类型生成常见系统字体列表。然后针对这些字体和任何加载的Web字体进行测试。
注意:这不会为您提供完整的可用字体列表,但您可以检查MS Office或Adobe产品通常安装的字体。

9
您可以使用新的本地字体访问API枚举所有字体:

Local Font Access API

console.log(await queryLocalFonts());

您也可以检查用户是否已授予权限:

const {state} = await navigator.permissions.query({name: 'local-fonts'});

console.log(state); // Either 'granted', 'prompt' or 'denied'

API的形状现在略有改变,它现在是一个选择器,您可以通过const pickedFonts = await navigator.fonts.query()访问它,并且您可以通过for (const metadata of pickedFonts) { }迭代其选定的选项。相应地,文章已进行更新。 - DenverCoder9
1
在Chrome中,它会弹出一个权限弹窗,要求“使用您计算机上的字体,以便您可以创建高保真内容”。除此之外,它完美地工作,并且似乎是现代浏览器中唯一可靠的答案。 - rebane2001

6

字体访问API在Chrome 87中可用:

// Query for all available fonts and log metadata.
const fonts = navigator.fonts.query();
try {
  for await (const metadata of fonts) {
    console.log(`${metadata.family} (${metadata.fullName})`);
  }
} catch (err) {
  console.error(err);
}

// Roboto (Roboto Black)
// Roboto (Roboto Black Italic)
// Roboto (Roboto Bold)

更多信息请点击这里


5
我在Lalit Patel上面的检测器中添加了两个方法:

  • addFont(family, stylesheetUrl, ruleString) -> 检测字体“family”是否存在,如果不存在,则使用stylesheetUrl(如果给定)或ruleString添加一个样式表加载字体
  • addFontsArr(arr) -> 添加一组字体

使用此可以做到:

fonts = [ 'Arial', 'Arial Black', { family: 'Lato', stylesheetUrl: 'https://fonts.googleapis.com/css?family=Lato'}, 'Leelawadee UI']
(new FontDetector()).addFontsArr(fonts);

代码:

/**
 * JavaScript code to detect available availability of a
 * particular font in a browser using JavaScript and CSS.
 *
 * Author : Lalit Patel
 * Website: http://www.lalit.org/lab/javascript-css-font-detect/
 * License: Apache Software License 2.0
 *          http://www.apache.org/licenses/LICENSE-2.0
 * Version: 0.15 (21 Sep 2009)
 *          Changed comparision font to default from sans-default-default,
 *          as in FF3.0 font of child element didn't fallback
 *          to parent element if the font is missing.
 * Version: 0.2 (04 Mar 2012)
 *          Comparing font against all the 3 generic font families ie,
 *          'monospace', 'sans-serif' and 'sans'. If it doesn't match all 3
 *          then that font is 100% not available in the system
 * Version: 0.3 (24 Mar 2012)
 *          Replaced sans with serif in the list of baseFonts
 */

/**
 * Usage: d = new Detector();
 *        d.detect('font name');
 */
function FontDetector() {
    this.detect = detect;
    this.addFont = addFont;
    this.addFontsArr = addFontsArr;

    // a font will be compared against all the three default fonts.
    // and if it doesn't match all 3 then that font is not available.
    var baseFonts = ['monospace', 'sans-serif', 'serif'];

    //we use m or w because these two characters take up the maximum width.
    // And we use a LLi so that the same matching fonts can get separated
    var testString = "mmmmmmmmmmlli";

    //we test using 72px font size, we may use any size. I guess larger the better.
    var testSize = '72px';

    var h = document.getElementsByTagName("body")[0];

    // create a SPAN in the document to get the width of the text we use to test
    var s = document.createElement("span");
    s.style.fontSize = testSize;
    s.innerHTML = testString;
    var defaultWidth = {};
    var defaultHeight = {};
    for (var index in baseFonts) {
        //get the default width for the three base fonts
        s.style.fontFamily = baseFonts[index];
        h.appendChild(s);
        defaultWidth[baseFonts[index]] = s.offsetWidth; //width for the default font
        defaultHeight[baseFonts[index]] = s.offsetHeight; //height for the defualt font
        h.removeChild(s);
    }

    function detect(font) {
        var detected = false;
        for (var index in baseFonts) {
            s.style.fontFamily = font + ',' + baseFonts[index]; // name of the font along with the base font for fallback.
            h.appendChild(s);
            var matched = (s.offsetWidth != defaultWidth[baseFonts[index]] || s.offsetHeight != defaultHeight[baseFonts[index]]);
            h.removeChild(s);
            detected = detected || matched;
        }
        return detected;
    }

    function addFont(family, stylesheetUrl, ruleString) {
        if (detect(family)) {
            //console.log('using internal font '+family);
            return true;
        }
        if (stylesheetUrl) {
            console.log('added stylesheet '+stylesheetUrl);
            var head = document.head, link = document.createElement('link');
            link.type = 'text/css';
            link.rel = 'stylesheet';
            link.href = stylesheetUrl;
            head.appendChild(link);
            return true;          
        }

        if (ruleString) {
            console.log('adding font rule:'+rule);
            var newStyle = document.createElement('style');
            newStyle.appendChild(document.createTextNode(rule));
            document.head.appendChild(newStyle);
            return true;
        }

        console.log('could not add font '+family);
    }

    function addFontsArr(arr) {
        arr.forEach(a => typeof a==='string' ? addFont(a) : addFont(a.family, a.stylesheetUrl, a.ruleString));
    }
};

3
<SCRIPT>
    function getFonts()
    {
        var nFontLen = dlgHelper.fonts.count;
        var rgFonts = new Array();
        for ( var i = 1; i < nFontLen + 1; i++ )
            rgFonts[i] = dlgHelper.fonts(i); 

        rgFonts.sort();
        for ( var j = 0; j < nFontLen; j++ )
            document.write( rgFonts[j] + "<BR>" );
    }
</SCRIPT>

<BODY onload="getFonts()">
<OBJECT id=dlgHelper CLASSID="clsid:3050f819-98b5-11cf-bb82-00aa00bdce0b" width="0px" height="0px">
</OBJECT>

2
@Robert Sköld,是的,它似乎只适用于IE。然而,在许多情况下仍然很有用,但是当严肃使用时,您应该进行一些功能检测,以便使用其他浏览器的人可以理解;请参见例如http://www.cs.tut.fi/~jkorpela/listfonts1.html。 - Jukka K. Korpela
它在Windows Phone的IE11上不起作用吗?还需要添加其他内容吗? - jats

3
在我搜索过程中,我还发现了Font.js,它添加了一个类似于Image的Font对象,因此可以检查字体何时真正可用。它也适用于已安装/系统字体。缺点是IE9+仅支持Object.defineProperty(其他浏览器都支持),但如果您正在进行现代网页设计,这似乎是更好的选择。(很遗憾,我将暂时接受上面的答案并进行投票。 :))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接