Red Dot Tracker
facet
facet 是一套为一组网站共用的设计语言:用纯 CSS 和少量原生 JavaScript 写成的设计令牌、组件和版式,不需要构建步骤。每个网站只挑选自己的强调色,其余完全一致。
这里有什么
安装:CDN
把样式表和脚本链接到页面里。先加载 facet.css,再加载 cast/cast-layered.css(只有用到吉祥物时才需要)。社交图标要另外加载 icons.css。固定一个标签,网站就不会随主分支变化。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/h1994st/facet@v0.1.3/dist/facet.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/h1994st/facet@v0.1.3/dist/cast/cast-layered.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/h1994st/facet@v0.1.3/dist/icons.css" /> <script type="module" src="https://cdn.jsdelivr.net/gh/h1994st/facet@v0.1.3/dist/js/facet.js"></script>
jsDelivr 在中国大陆可能不稳定,字体也是相对 facet.css 加载的。面向中国大陆的网站,请改用下面的 submodule,把文件和网站放在一起。
安装:Git submodule
git submodule add https://github.com/h1994st/facet vendor/facet git -C vendor/facet checkout v0.1.3
把 vendor/facet/dist/ 作为静态文件和网站一起发布,然后用相对路径链接。
<link rel="stylesheet" href="/vendor/facet/dist/facet.css" /> <link rel="stylesheet" href="/vendor/facet/dist/cast/cast-layered.css" /> <link rel="stylesheet" href="/vendor/facet/dist/icons.css" /> <script type="module" src="/vendor/facet/dist/js/facet.js"></script>
最小示例
<html lang="en" data-accent="moss">
<head>
<link rel="stylesheet" href="…/dist/facet.css" />
<script type="module" src="…/dist/js/facet.js"></script>
</head>
<body>
<nav class="f-nav">
<span class="f-nav__brand">Duna Feline</span>
<a href="/" aria-current="page">Blog</a>
</nav>
<main class="f-page f-prose">
<h1>Hairball</h1>
<p>Why the red dot always moves.</p>
</main>
</body>
</html>
data-accent选择强调色(十三种,见色彩页);data-theme取auto(默认,跟随系统)、dark或light,主题按钮把选择记在localStorage["facet-theme"]里。两者都只在<html>上生效(:root[data-accent]);要让页面的某一部分换一种强调色,在它上面设置--f-accent和--f-accent-contrast。主卡片(.f-hero)的深色会随之改变;--f-accent-soft(浅色底)在:root上算好,不会跟着变,所以也在这一部分上把它设为color-mix(in oklch, var(--f-accent) 16%, transparent)。- 默认的
.f-nav在页面流中。要让它浮在页面上,请加.f-nav--fixed:页面里只要有它,--f-nav-clear就会自动为内容预留位置。 - 所有样式都在
@layer facet.*里,所以你自己的、没有分层的 CSS 不需要!important就能覆盖它。只有两处例外:[hidden]的display: none !important,以及访客要求减少动效时停掉页面上所有动画和过渡的那条规则(也包括你自己的)。分层的!important比没有分层的 CSS 优先,哪怕后者也写了!important,所以这两条无法覆盖。 - 每个元素都可以写中文:给它或页面设置
lang="zh-Hans",字距、换行和行高会自动调整。
行为
facet 的 JavaScript 是可选的。引入 dist/js/facet.js 就会自动初始化:页面解析完后,每个模块找到用 data-f-* 属性声明的元素并接管它们。再次调用 init() 是安全的:已经初始化过的元素会被跳过;某个模块出错只会在控制台报告,不会挡住其他模块。
| 属性 | 模块 | 作用 |
|---|---|---|
data-f-ga4 (on <html>), data-f-event,
data-f-label
|
analytics.js |
可选的 Google Analytics 4:<html data-f-ga4="G-…"> 加载一次 gtag 并发送一次 config(即一次页面浏览)。浏览器发送全局隐私控制(GPC)时什么都不加载。data-f-event="name" 在点击时发送这个事件(链接带 link_url,data-f-label 作为 label);切换主题时发送 theme_change。不提供 Cookie 同意横幅。要用 GA4 的同意模式,请在 facet.js 之前用一个普通(非模块)的 <head> 脚本定义 window.dataLayer 和 gtag,并调用 gtag("consent", "default", …):analytics.js 会沿用已有的 gtag。不要再保留自己的 gtag("config", …) 代码,否则每次浏览都会被计两次。 |
data-f-theme-toggle |
theme.js |
放在按钮上:在深色、浅色和自动之间切换,把选择存进 localStorage["facet-theme"],并更新按钮的文字和标签。 |
data-f-liquid |
liquid.js |
放在 .f-card 或装着卡片的容器上:一道跟着鼠标在卡片上移动的光,只对鼠标生效。 |
data-f-scrollspy |
scrollspy.js |
放在 .f-toc 上:给正在阅读的那一节的链接加上 aria-current="location"。 |
data-f-nav-cover="#cover" |
nav.js |
放在 .f-nav--over 上:封面在屏幕上时导航保持透明,封面滚走后变成玻璃。 |
data-f-autoplay, data-f-autoplay-toggle |
autoplay.js |
放在静音循环的 video 上:视频在屏幕上时播放。带 data-f-autoplay-toggle 和 aria-controls 的按钮成为它的暂停按钮(文字取自 data-f-pause 和 data-f-play)。 |
data-f-story |
story.js |
放在 .f-story 上:固定画面,并显示正在阅读的那一步对应的图片(img[data-step])。 |
data-f-panels, data-f-panel-toggle |
panels.js |
放在 .f-panels 上:标出停稳的面板,只播放它的视频,并填充空的 nav.f-dots。面板里带 data-f-panel-toggle 的按钮成为视频的暂停按钮。 |
data-f-deck, data-f-piece, data-f-slide,
data-f-from, data-f-deck-prev,
data-f-deck-next
|
deck.js |
放在 .f-deck 上:把幻灯片变成一个画面,画面里的元素在幻灯片之间移动(数据格式见下文)。 |
data-f-type |
typewriter.js |
一组短语(JSON 数组):逐字打出、停留、清空,再打下一句,循环。元素原来的文字是静止状态,减少动效时一直保持。速度和停留时间可用 data-f-type-speed 和 data-f-type-hold(毫秒)调整。放在 aria-hidden 的包裹元素里,旁边用视觉上隐藏的文字写出真实内容。 |
data-f-variants, data-f-variant |
variants.js |
data-f-variants="组" 放在 .f-segmented 上,按钮写 data-variant;其父元素内每个 data-f-variant="组:值" 按所选值显示或隐藏。控件和非默认变体先写 hidden,没有 JavaScript 时只显示第一个,也不会出现无效按钮。值没有对应按钮的变体保持隐藏;没有按钮的控件被跳过、保持隐藏;初始化之后才加入的变体不会被处理(再调用一次 init)。 |
单独使用一个模块
每个模块都可以单独引入,导出 init(root = document),返回一个清理函数,用来移除它加上的监听器和元素。facet.js 也导出 init,以及撤销它自动初始化的 cleanup。
import { init } from "…/dist/js/deck.js";
const stop = init(document.querySelector("#talk"));
// Later, before the deck leaves the page:
stop();
避免主题闪烁
facet.js 在首次绘制之后才运行,保存的主题会晚到一步。把下面这一行放进 ,放在样式表之前:它在页面绘制前应用 localStorage["facet-theme"](只接受 dark、light 和 auto),之后主题按钮只负责显示当前主题。
<script>try{var t=localStorage.getItem("facet-theme");if(/^(dark|light|auto)$/.test(t))document.documentElement.dataset.theme=t}catch(e){}</script>
演示文稿的数据
每个 .f-deck__slide 的 data-f-slide 是一段 JSON,列出这张幻灯片上每个元素(按 data-f-piece 的名字)的状态;没有列出的元素会隐藏(光束缩回起点,再出现时重新长出来)。位置和尺寸都是画板的百分比。
| 元素 | 状态 |
|---|---|
方块 .f-deck__tile |
{"at": [left, top], "c": color, "lit": true}:lit 用它的颜色填满它。 |
光束 .f-deck__beam |
{"beam": [left, top, length]}:加 .f-deck__beam--v 变成竖直方向。 |
方框 .f-deck__shot |
{"box": [left, top, width, height], "fade": true}:fade 让它淡出。 |
元素从它第一次出现的位置开始,或者从它的 data-f-from([left, top] 或一个方框)开始。数据有错(不是 JSON,或某个字段的数字不够)时,演示文稿保持没有 JavaScript 时的样子,并在控制台里指出是哪一张。
<section
class="f-deck__slide"
data-f-slide='{"laser": {"at": [20, 50], "c": "var(--f-deck-ink)"},
"b1": {"beam": [20, 50, 60]},
"target": {"at": [80, 50], "c": "var(--f-accent)", "lit": true}}'
>
<h2>In straight lines.</h2>
</section>
减少动效与没有 JavaScript
- 没有 JavaScript 时,内容照样可读,链接照样可用:故事页把每一步的图片放在文字旁边,全页面板和演示文稿变成上下排列的普通段落,旁注点编号就能展开,封面上的导航是普通的玻璃。
- 访客开启
prefers-reduced-motion: reduce时,动画和过渡全部停下(这条规则覆盖整个页面,见上文),视频停在封面图上,也不显示暂停按钮,全页面板不再吸附,翻页和跳转都是直接到位,没有平滑滚动。