我在这里不知所措。尝试将 google 变量 webfont (open sans) 添加到我的网站。
staticCSS字体创建
<link>
。 为什么?(分号,没有“300..700”)在网络上使用
要嵌入字体,请将代码复制到 html 中
[x] <link> [ ] @import <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
指定族的 CSS 规则
font-family: 'Open Sans', sans-serif;
整个页面都没有 woff2 的下载(仅来自 API)。 D/L 按钮仅适用于 .ttf。 讽刺的是在关于自托管的文章中,他们使用
woff2
作为示例,即使他们不提供它。此外,甚至该字体的官方 GITHUB 页面也仅提供 .ttf。 为什么?
其他来源提供了各种格式的静态文件(但我在那里没有看到可变的文件),另一个线程中的 ppl 甚至发布了他们自己的工具,例如:
折腾了一整天,终于找到了这个。还提到了另一个(官方)工具,用于将 ttf 转换为 woff2,这对于可变字体来说似乎不容易实现。 SRSLY?这是唯一的方法吗?为什么没有任何文档? (好吧,也许我应该从 API 中获取 woff2,但我注意到浏览器之间的差异,我认为例如 Opera gets 仅提供静态类型而不是变量类型。)
“好”的 API 就是为此服务的。但它只使用
format('woff2')
:
但我读过,对于可变字体,语法应该更像这样,使用
format('woff2 supports variations')
和 format('woff2-variations')
和 @supports (font-variation-settings: normal)
。为什么谷歌不使用这种语法?现在哪个更好?
谷歌:
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
应该如何完成:
@font-face {
font-family: Asap;
src: url('/fonts/Asap-VariableFont_wght.woff2') format('woff2 supports variations'),
url('/fonts/Asap-VariableFont_wght.woff2') format('woff2-variations');
font-weight: 400 700;
font-display: swap;
font-style: normal;
}
旁注: 从谷歌页面我需要手动更改
https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..0,800;1,300..1,800&display=swap
到
https://fonts.googleapis.com/css2?family=Open+Sans:[email protected]&display=swap
甚至获得可变字体。
不幸的是你仍然无法直接下载woff2文件。
像
format('woff2-variations')
这样的格式标识符可以追溯到可变字体支持还处于实验阶段的时代——所以任何像样的“现代”浏览器都不应该需要它们。但是,可能会有例外,例如非常旧的浏览器版本 - 请参阅 caniuse 报告。
由于 google-webfont-helper 仍然不支持可变字体,我想出了一个自定义字体加载器。
async function getAllVariableFonts(
apiKey = "",
format = "woff2",
apiUrlStatic = ""
) {
let apiURL = `https://www.googleapis.com/webfonts/v1/webfonts?capability=VF&capability=${format}&sort=style&key=${apiKey}`;
// switch between API and static src
let listUrl = apiKey ? apiURL : apiUrlStatic;
// fetch font JSON
let listObj = await (await fetch(listUrl)).json();
// get only VF items
let items = listObj.items;
items = items.filter((item) => item.axes && item.axes.length);
return items;
}
async function getGoogleFontUrl(font) {
// replace whitespace
let familyQuery = font.family.replaceAll(" ", "+");
let gfontBase = `https://fonts.googleapis.com/css2?family=`;
// check variants
let variants = [...new Set(font.variants.filter(Boolean))];
let stylesItalic = variants.filter((variant) => variant.includes("italic"));
let stylesRegular = variants.filter((variant) => !variant.includes("italic"));
// sort axes alphabetically - case sensitive ([a-z],[A-Z])
let axes = font.axes;
axes = [
axes.filter((item) => item.tag.toLowerCase() === item.tag),
axes.filter((item) => item.tag.toUpperCase() === item.tag)
].flat();
let ranges = axes.map((val) => {
return val.start + ".." + val.end;
});
let tuples = [];
// italic and regular
if (stylesItalic.length && stylesRegular.length) {
tuples.push("ital");
rangeArr = [];
for (let i = 0; i < 2; i++) {
rangeArr.push(`${i},${ranges.join(",")}`);
}
}
// only italic
else if (stylesItalic.length && !stylesRegular.length) {
tuples.push("ital");
rangeArr = [];
rangeArr.push(`${1},${ranges.join(",")}`);
}
// only regular
else {
rangeArr = [];
rangeArr.push(`${ranges.join(",")}`);
}
// add axes tags to tuples
axes.map((val) => {
return tuples.push(val.tag);
});
query = tuples.join(",") + "@" + rangeArr.join(";") + "&display=swap";
let url = `${gfontBase}${familyQuery}:${query}`;
return url;
}
function updatePreview(item, googleFontUrl) {
legend.textContent = `Preview: ${item.family}`;
// add css
let im = `@impo` + `rt`;
gfCss.textContent = `
${im} '${googleFontUrl}';
.preview{
font-family: "${item.family}";
font-size: 12vmin;
}`;
let axes = item.axes;
styleSelect.innerHTML = "";
let fontVariationSettings = {};
let hasItalic = item.variants.includes("italic");
if (hasItalic) {
let checkbox = document.createElement("input");
let checkboxLabel = document.createElement("label");
checkboxLabel.textContent = "Italic ";
checkbox.type = "checkbox";
checkboxLabel.append(checkbox);
styleSelect.append(checkboxLabel);
checkbox.addEventListener("click", (e) => {
preview.style.fontStyle = checkbox.checked ? "italic" : "normal";
});
}
axes.forEach((axis) => {
let label = document.createElement("label");
let input = document.createElement("input");
input.type = "range";
input.min = axis.start;
input.max = axis.end;
input.value = axis.start;
fontVariationSettings[axis.tag] = axis.start;
label.textContent = `${axis.tag}: ${axis.start}–${axis.end} `;
styleSelect.append(label, input);
// apply style
input.addEventListener("input", (e) => {
let val = e.currentTarget.value;
fontVariationSettings[axis.tag] = val;
let cssVar = [];
for (tag in fontVariationSettings) {
cssVar.push(`"${tag}" ${fontVariationSettings[tag]}`);
}
preview.style.fontVariationSettings = cssVar.join(", ");
});
});
}
function showLink(target, url) {
target.innerHTML = "";
let link = `<a href="${url}">${url}</a>`;
target.insertAdjacentHTML("beforeend", link);
}
function populateDatalist(target, list) {
let fonts = list;
let datalistOptions = "";
fonts.forEach((font) => {
//only VF
if (font.axes) {
datalistOptions += `<option >${font.family}</option>`;
}
});
target.insertAdjacentHTML("beforeend", datalistOptions);
}
/**
* fetch
*/
async function fetchFontsFromCssAndZip(css) {
// find subset identifiers by comments
let regexComments = /\/\*\s*([^*]*(?:\*(?!\/)[^*]*)*)\*\//g;
let subsets = css.match(regexComments).map((sub) => {
return sub.replace(/(\/\*|\*\/)/g, "").trim();
});
//create and parse temporary stylesheet object
let cssSheet = new CSSStyleSheet();
cssSheet.replaceSync(css);
// filter font-face rules
let rules = [...cssSheet.cssRules].filter((rule) => {
return rule.type === 5;
});
// sanitize font-family name
let fontFamily = rules[0].style
.getPropertyValue("font-family")
.replaceAll('"', "");
let fontFamilyFilename = fontFamily.replaceAll(" ", "-");
// create zip object
let zip = new JSZip();
// loop through all rules/fonts
for (let i = 0; i < rules.length; i++) {
// get properties
let fontWeight = rules[i].style.getPropertyValue("font-weight");
let fontStyle = rules[i].style.getPropertyValue("font-style");
let fontStretch = rules[i].style.getPropertyValue("font-stretch");
fontStretch = fontStretch === "normal" ? "" : fontStretch;
let src = rules[i].style.getPropertyValue("src");
src = src.match(/\(([^)]+)\)/)[1].replaceAll('"', "");
//replace cryptic file names with readable local names
let fontName = [fontFamilyFilename, subsets[i], fontWeight, fontStyle, fontStretch]
.filter(Boolean)
.join("_") + ".woff2";
css = css.replaceAll(src, `"${fontFamilyFilename}/${fontName}"`);
// add data to zip
let fontData = await (await fetch(src)).arrayBuffer();
zip.file(`${fontFamilyFilename}/${fontName}`, fontData, {
type: "uint8array"
});
}
// add simple example HTML
let htmlDoc = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
<link rel="stylesheet" href="${fontFamilyFilename}.css">
<body style="font-family:'${fontFamily}\'">
<h1>Sample font</h1>
<p>One morning, when <em>Gregor Samsa</em> woke from <strong>troubled dreams</strong>, he found himself transformed in his bed into a horrible vermin.</p>
<p>He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover it and seemed ready to slide off any moment.</p>
</body></html>`;
zip.file("index.html", htmlDoc);
// add css
fontCss.value = css;
zip.file(`${fontFamilyFilename}.css`, css);
// create object url
let blob = await zip.generateAsync({
type: "blob"
});
blob.name = fontFamilyFilename + ".zip";
return blob;
}
:root {
--loadingImg: url("data:image/svg+xml,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><path d='M12 1A11 11 0 1 0 23 12 11 11 0 0 0 12 1Zm0 19a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z' opacity='.25'/><path d='M10.14 1.16a11 11 0 0 0-9 8.92A1.59 1.59 0 0 0 2.46 12 1.52 1.52 0 0 0 4.11 10.7a8 8 0 0 1 6.66-6.61A1.42 1.42 0 0 0 12 2.69h0A1.57 1.57 0 0 0 10.14 1.16Z'><animateTransform attributeName='transform' type='rotate' dur='0.75s' values='0 12 12;360 12 12' repeatCount='indefinite'/></path></svg>")
}
body {
font-family: sans-serif
}
legend {
font-weight: bold;
}
fieldset {
margin-bottom: 1em;
}
fieldset input,
fieldset textarea {
border: none
}
input[type="text"] {
width: 100%;
display: block;
margin-bottom: 1em;
}
#inputFonts,
.inputUrl,
input[type="search"] {
font-size: 2em;
margin-bottom: 1em;
border: 1px solid #000;
border-radius: 0.3em;
}
input[type="checkbox"] {
width: auto;
display: inline-block;
margin-bottom: 1em;
}
textarea {
width: 100%;
min-height: 20em;
}
.btn-default {
text-decoration: none;
border: 1px solid #000;
background: #ccc;
color: #000;
font-weight: bold;
padding: 0.3em;
font-family: inherit;
font-size: 1em;
margin-right: 0.3em;
cursor: pointer;
}
.inactive {
pointer-events: none;
opacity: 0;
}
.btn-load .icn-loading {
opacity: 0;
}
.btn-load.loading .icn-loading {
opacity: 1;
}
/*
.btn-load.active
.icn-loading{
width:0px;
}
*/
.icn-loading {
transition: 0.3s;
transform: translateY(0.15em);
display: inline-block;
position: relative;
overflow: hidden;
width: 1em;
height: 1em;
background-image: var(--loadingImg);
background-repeat: no-repeat;
background-position: 0%;
color: transparent;
border-color: transparent;
}
<h1>Fetch variable fonts from google</h1>
<style id="gfCss"></style>
<p><button class="btn-default btn-fetch" id="fetchData">Fetch font files</button><a class="btn-default btn-load inactive" id="btnDownload" href="#" download="fontface.css">Download fontkit <span class="icn-loading"></span></a> </p>
<fieldset>
<legend>Search font by name</legend>
<input type="text" list="datalistFonts" id="inputFonts" placeholder="Enter font-family name">
<!-- autocomplete -->
<datalist id="datalistFonts">
</datalist>
<label for="">Or enter CSS URL</label>
<p><input type="text" class="inputUrl" id="inputUrl" value="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900"></p>
</fieldset>
<fieldset>
<legend id="legend">Preview</legend>
<div id="preview" class="preview">
Hamburglefonstiv
</div>
<div id="styleSelect"></div>
</fieldset>
<fieldset>
<legend>New Css</legend>
<textarea id="fontCss"></textarea>
</fieldset>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.js"></script>
<script>
window.addEventListener('DOMContentLoaded', async(e) => {
// enter you own API key
let apiKey = '';
// static copy of developer API response
let apiUrlStatic = "https://cdn.jsdelivr.net/gh/herrstrietzel/fonthelpers@main/json/gfontsAPI.json";
// inputs
const inputUrl = document.getElementById('inputUrl');
const btnDownload = document.getElementById('btnDownload');
// init example
(async() => {
/**
* get all google fonts from API
* filter only variable fonts
*/
let googleFontList = await getAllVariableFonts(apiKey, 'woff2', apiUrlStatic);
// generate autofill
populateDatalist(datalistFonts, googleFontList)
// show first
let item = googleFontList.filter(item => item.family === 'Open Sans')[0];
inputFonts.value = item.family;
//console.log(item);
googleFontUrl = await getGoogleFontUrl(item);
inputUrl.value = googleFontUrl;
//update css
updatePreview(item, googleFontUrl)
// filter fonts
inputFonts.addEventListener('change', async e => {
let family = e.currentTarget.value;
let familyQuery = family.replaceAll(' ', '+');
// filter current family
let item = googleFontList.filter(item => item.family === family)[0];
// update links
googleFontUrl = await getGoogleFontUrl(item);
inputUrl.value = googleFontUrl;
//showLink(cssUrls, [googleFontUrl])
updatePreview(item, googleFontUrl)
});
//updateGoogleCssUrl();
})();
inputUrl.addEventListener("change", async(e) => {
updateGoogleCssUrl()
});
fetchData.addEventListener("click", async(e) => {
btnDownload.classList.remove('inactive');
btnDownload.classList.add('active');
btnDownload.classList.add('loading');
updateGoogleCssUrl()
});
// fetch
async function updateGoogleCssUrl() {
// fetch css content as text
let url = inputUrl.value;
let css = await (await fetch(url)).text();
// fetch font files and zip
let blob = await fetchFontsFromCssAndZip(css);
let objectUrl = URL.createObjectURL(blob);
// update download link
btnDownload.href = objectUrl;
btnDownload.download = blob.name;
btnDownload.classList.replace('inactive', 'active');
btnDownload.classList.remove('loading');
}
})
</script>
下载功能在 SO 片段中不起作用。请参阅工作 codepen 示例
https://fonts.googleapis.com/css2?family=Open+Sans:ital,wdth,wght@0,75..100,300..800;1,75..100,300..800&display=swap
@font-face
规则的CSSarrayBuffer()
并将数据添加到 JSZIP 对象不幸的是,谷歌部署使用代理检测,可能无法向某些浏览器(如 Opera)提供可变字体(尽管支持可变字体)