MoreRSS

site iconZhangXinXu | 张鑫旭修改

出版《CSS选择器世界》,喜欢钓鱼、写作。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

ZhangXinXu | 张鑫旭的 RSS 预览

CSS新单位dvh、lvmin、vi、cqb等初解

2026-07-17 17:01:24

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12289
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、CSS又出新单位了

一不留神,CSS又多了几十个我不认识的单位。

来来来,排排站,给大家露个脸。

dvb, dvh, dvi, dvmax, dvmin, dvw,
svb, svh, svi, svmax, svmin, svw,
lvb, lvh, lvi, lvmax, lvmin, lvw,
vi, vb
cqw, cqh, cqi, cqb, cqmin, cqmax

赶快花时间学习一番。

从dvh说起

dvh这个单位应该是最为常用的,全称 Dynamic Viewport Height,即动态视区高度。

我们平时用的比较多的是vh这个单位,表示视区高度。

但是在iOS Safari浏览器下,页面浏览的时候,如果你滚动,会发现菜单啊,导航啊这些会被滚走,此时,视区的高度变化了。比方说下面录屏视频底部的地址栏,滚动的时候高度会变小。

如果还是使用传统的vh单位,布局就会有问题。

此时,就可以使用dvh这个CSS单位,1dvh就是1%的动态视区高度。

比方说,我们构建一个全屏覆盖的黑色半透明遮罩层,则可以使用下面的CSS代码:

.fullscreen {
  height: 100dvh;
  background: #0009;
}

至于对应的宽度单位dvw就没什么应用价值了,因为网页视窗的宽度一般是不存在所谓的动态变化的。

svh和lvh又是什么?

svh是Small Viewport Height的缩写,表示假定浏览器所有工具栏全部展开(可见区域最小)的高度。
lvh则是Large Viewport Height的缩写,是假定浏览器所有工具栏全部收起(可见区域最大)的高度。

个中区别参见下表:

单位 全称 行为模式 最佳适用场景

svh
Small Viewport Height 假定浏览器所有工具栏全部展开(可见区域最小)的高度。 固定的底部常驻菜单,确保绝对不会被遮挡。

lvh
Large Viewport Height 假定浏览器所有工具栏全部收起(可见区域最大)的高度。 大气的背景大图,追求极致的视觉延伸感。

dvh
Dynamic Viewport Height 随工具栏的显示或隐藏动态实时变化的高度。 全屏 App 界面、侧边栏菜单、全屏表单。

二、那些以i, b结尾的单位

本文开头出现的几十个单位中,有1/3的单位要么以i结尾,要么以b结尾。

这些单位都是CSS逻辑单位。

  • iinline的首字母,默认表示水平方向,例如单位vi在绝大多数场景下,其实和vw的表现一样。
  • bblock的首字母,默认表示垂直方向。

其实类似的CSS逻辑属性还挺多,比方说用得比较多的:

margin-inline: 20px;
padding-block: 1rem;

从实用角度来看,这些CSS逻辑单位大家了解下即可,因为平时很少会用到需要改变水平或垂直排版流的场景,没有这样的场景,自然也就没有使用的必要。

所以,类似与vi这样的单位,除了装逼以外,没有任何作用,还是老老实实使用vw吧。

三、cq家族单位

说起cq家族,不知道为何脑中突然出现葬爱家族,哈哈哈。

葬爱家族 精神小妹

cq是container query的简介,中文译为容器查询。

本博已经多次介绍过:

是非常实用的特性。

其中用的最多的单位就是cqw1cqw就是1%的对应容器的宽度大小,这个在弹性布局中很有用。

至于其他单位,也就好理解了,参加下表:

单位 全称 计算规则 适用场景
cqw Container Query Width 查询容器宽度的 1% 水平排版下,基于容器宽度自适应尺寸、字体
cqh Container Query Height 查询容器高度的 1% 垂直排版、卡片高度驱动布局、竖向自适应组件
cqi Container Query Inline 查询容器内联轴尺寸的 1%,随书写模式自动切换轴向;横排等价 cqw,竖排等价 cqh 多语言横竖排版兼容组件,无需修改样式自动适配流向
cqb Container Query Block 查询容器块轴尺寸的 1%,随书写模式自动切换轴向;横排等价 cqh,竖排等价 cqw 文字块垂直高度自适应、图文流布局,兼容横竖书写
cqmin Container Query Min 取 cqw 与 cqh 中更小值的 1% 正方形卡片、头像、图标,保证元素不会拉伸变形
cqmax Container Query Max 取 cqw 与 cqh 中更大值的 1% 全屏填充容器、横幅大图,铺满容器最长边

四、打完收工,打道回府

噢啦,正文到此结束,最后,附上兼容性截图。

dvh的兼容性

容器查询家族单位的兼容性与d家族单位相比,大差不差,Chrome早一些,Safari慢一些,见:

cqw的兼容性

OK,碎碎念时间。

最近团队发生了一件不大不小的事情,那就是运转了十多年的前端双周分享和每周小测停止了。

还能为啥,没人参与了呗!

那为什么没人参与呢?

最直接的原因就是AI太强了,大家对这种低效又痛苦的学习方式已经没了兴趣。

这种心理很好理解,我哼次哼次辛辛苦苦学了这些技术点,回过头一看,很AI相比,连个脚趾头都比不上,道心直接破碎,还学个毛线!

总而言之,此事对我还颇有触动,想了不少东西。

有兴趣的可以访问这里,看看我的一些思考。

配图1

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12289

(本篇完)

前端快讯:setHTML()和Element.startViewTransition已支持

2026-07-09 10:54:11

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12246
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、先说setHTML方法

之前有介绍过setHTMLUnsafe()方法,稍等,让我找一下。

找到了,就是这篇:介绍两个DOM新方法setHTMLUnsafe和getHTML

是去年4月份介绍的,距今有15个月。

当时setHTML()方法还未支持,谁知才没过多久,Chrome浏览器就已经支持了此特性。

setHTML兼容性

关于setHTML

setHTML()方法设置HTML字符串内容的时候,会自动进行XSS过滤,会自动移除<script><frame><iframe><embed><object><use>以及事件处理程序属性。

setHTMLUnsafe()方法则不会,setHTMLUnsafe()方法的行为表现更接近于innerHTML方法。

举个例子:

<div id="container"></div>

<p>
  <button id="b1">setHTML()方法</button>
  <button id="b2">setHTMLUnsafe()方法</button>
</p>
const str = `<script>console.log('糟了,中了刀盾');</script>
<img src="book.webp" onload="this.style.border= 'red solid'">
<details>
    <summary>HTML并不简单</summary>
    <p>感谢大家正版支持,无论环境如何变化,学习总是不要停止的。</p>
</details>
<p>普通文字也不显示?</p>`;

b1.onclick = () => {
  container.setHTML(str);
}

b2.onclick = () => {
  container.setHTMLUnsafe(str);
}

结果最终的运行结果远超自己的预期!

我一开始预估的是:

  • <script>元素被过滤;
  • onload属性过滤,但是图片元素保留。
  • <details>元素正常显示,再退一步,可能交互功能过滤,只有纯文本

setHTML(str)方法执行后,container容器里面唯一存活的元素就是<p>元素,而前面的<script><img><details>元素通通消失不见了。

setHTML过滤运行结果示意

这特么过滤的也太狠了,根本就没法使用了!

setHTMLUnsafe()方法则符合预期,图片正常显示,onload方法也正常执行了,<details>元素的展开与收起交互也是正常的。

setHTMLUnsafe运行结果示意

峰回路转,可选参数

后来我一看文档,发现setHTML()方法支持可选参数,可以指定过滤的元素和属性。

我就说嘛,怎么会有设计这么鸡肋的方法。

不过这个可选参数还不太好理解,称为Sanitizer 或者 SanitizerConfig 对象。

嗯……这个时候最快的学习方法就是案例。

比方说允许的可选参数示意:

const sanitizer = new Sanitizer({
  elements: ["div", "p", "script"],
  attributes: ["id"],
  replaceWithChildrenElements: ["b"],
  comments: true,
  dataAttributes: false,
});

移除的参数示意:

const sanitizer = new Sanitizer({
  removeElements: ["span", "script"],
  removeAttributes: ["lang", "id"],
  comments: false,
});

回到这里,如果我希望setHTML的时候,仅仅script元素和onload属性被过滤,可以这么设置:

const sanitizer = new Sanitizer({
  removeElements: ["script"],
  removeAttributes: ["onload"],
});

container.setHTML(str, {
  sanitizer
});

此时,container里面的元素呈现就符合预期了!

符合预期的setHTML渲染结果

眼见为实,您可以狠狠地点击这里:setHTML和setHTMLUnsafe方法效果对比demo

二、Element.startViewTransition指的是?

startViewTransition其实之前也介绍过,是这篇文章:页面级可视动画View Transitions API初体验

看了下发布日期,是两年前的作品了。

不过那个时候startViewTransition只能在document文档对象中调用,也就是只能是页面级的可视过渡效果。

现在startViewTransition API的应用范围再次升级,可以在任意的HTML元素DOM上调用。

先看兼容性,同样目前仅Chrome浏览器支持:

Element.startViewTransition兼容性

startViewTransition可以让元素状态发生变化的时候天然产生动画效果,而不需要额外进行CSS设置(当然,CSS设置也是可以的,但不是必须的)。

举个简单的例子,点击某个按钮,删除元素。

如果我们直接使用Elememt.remove()方法,元素会突然噶掉,就像跌落的股价令人猝不及防。

但是在startViewTransition()回调中调用,则自然而然会有过渡动画效果。

眼见为实,您可以狠狠地点击这里:Element.startViewTransition方法demo演示

container.startViewTransition(() => {
  img.remove();
});

效果GIF:

remove时候有淡入淡出效果

需要注意的是,startViewTransition()方法只能设置在需要进行动画效果的容器元素上,设置在当前元素上是无效的。

相比全局的code>document.startViewTransition()方法,Element.startViewTransition()控制范围更精准,通常而言,性能也更高。

同样的,此处的可视过渡效果也支持使用::view-transition-group等伪元素进行类似于过渡时长,类型等效果的设置。

具体参见之前撰写的这篇文章,这里不再赘述。

三、总结一下

本文介绍了两个DOM新特性都还算有不错的应用价值,不过受制于兼容性,短期还是看不到在生产环境应用的可能性。

目前大家了解下即可。

最后还有点闲功夫,分享下上周自驾游一件幸运的事情。

去盐城中华麋鹿园看麋鹿的,结果破天荒偶遇鹿王争霸,目睹全程近10分钟的火拼,鹿角碰撞的时候哐哐作响,最后打到双眼血红,双双口吐鲜血,真是往死里干啊!

鹿王争霸

不过人品是守恒的,我已经开始担心我接下来的钓鱼收获会不会变得惨淡了。

最后,南宫阙师姐压轴!

南宫阙

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12246

(本篇完)

AI时代网站智能体无障碍访问开发指南

2026-07-07 18:15:08

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12289
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一些公司信息提前说明:

  • 本文会大量出现Cloudflare这家公司名称,这是一家美国旧金山跨国互联网基础设施厂商,大家应该见过这家公司的静态资源CDN、DDoS防御产品,近期在推动网站智能体的标准和规范上特别的积极。
  • Mintlify则是2021 年由工程师 Han Wang、Hahnbee Lee 在美国创立,总部位于旧金山的一家出场公司,面向人与 AI 智能体的智能知识平台(Intelligent Knowledge Platform),主打 Docs-as-Code 代码即文档工作流,兼顾开发者阅读与 AI 工具(Claude Code、Cursor 等)自动读取解析文档需求。
  • Ahrefs则是面向 SEO 从业者、数字营销团队、外贸企业、营销代理商的全球搜索与 AI 营销情报 SaaS 平台公司,自研独立爬虫 AhrefsBot,自建全球网页索引库,是行业外链数据标杆。

一、我们到底要解决什么问题?

说白了,就是这么个问题:你的网站,AI 能不能看懂?

现在的互联网世界,搜索引擎已经不是唯一的流量入口了。

用户把网址往 Workbuddy 或者豆包的输入框一贴,AI 咔咔就读你的页面。

CodeBuddy 这样的编程助手需要看你的文档,才能给开发者正确的代码建议。

甚至有些 AI 代理自己会主动去网上找资料,然后总结给用户。

举两个真实的例子(均是国外的案例)你就明白了:

案例一:Evil Martians 是一家开发咨询公司。有一次一个 AI 创业公司的创始人问 Claude:”给我推荐一个架构能力强、有扩展经验的开发团队。”Claude 直接把 Evil Martians 排在了第一。人家怎么做到的?因为他们的网站内容结构清晰,AI 能看懂。

案例二:2025 年,美国用户的 Google 搜索量同比下降近 20%。从 2024 年 11 月到 2025 年 11 月,Google 给出版商的流量下降了 33%。用户越来越少用搜索框,越来越多用 AI 对话框。

所以问题来了——当 AI 扮演”用户的第一道信息过滤器”时,你的网站能不能被它正确理解?这就叫「智能体就绪度(Agent Readiness)」,或者叫「网站对 LLM 的可访问性」

好消息是,这件事不需要什么高深技术,很多手段其实就是我们早就该做的”良好 Web 习惯”。

下面我们一步步来。

二、一个总原则:LLM 不是人,它读的是”文本”

先记住一个核心原则——

LLM 理解清晰、结构良好的文本,比其他任何东西都好。

什么意思呢?你想想,一个典型的 HTML 页面里,大概 80% 是导航、脚本和样板代码,只有 20% 是真正有用的内容。

一篇博客文章,HTML 版本可能消耗大约 15,000 个 token,但转换成 Markdown 后只需要约 3,000 个 token——足足减少了 80%

对于工作在有限上下文窗口里的 LLM 来说,这个差距意味着”理解你的页面”与”直接放弃”之间的区别。

所以,我们所有操作的目标就一个:以最干净、最纯粹的格式,把最好的内容递给 AI。

三、开工!六步让 AI 看懂你的网站

我把需要做的事情分成了六个步骤,由易到难、由基础到进阶。建议按顺序来。

第 0 步:重审robots.txt + 加一行 Content-Signal:

这是所有操作的前提。

如果你的 robots.txt 把 GPTBot、ClaudeBot 这些 AI 爬虫都屏蔽了(比方说我的博客 😂),那后面做再多都没用。

先检查你的 robots.txt 有没有类似这样的东西:

User-agent: GPTBot
Disallow: /

User-agent: ClaudeBot
Disallow: /

如果有,赶紧删掉或者改成 Allow

然后,再加上一行 Content-Signal:

User-agent: *
Content-Signal: search=yes, ai-input=yes, ai-train=yes

这行东西是 Cloudflare 提的一个新兴约定,不是 RFC 标准,但越来越多工具开始支持。

它的作用就是明确告诉爬虫:我的内容可以用来做什么

字段 含义
search 能不能出现在搜索结果里
ai-input 能不能被 AI 当作实时上下文(比如 ChatGPT 引用你的页面)
ai-train 能不能用于训练 AI 模型

如果你不想自己的内容被拿去训练,把 ai-train 设成 no 就行。

⚡ 小贴士:这步的工作量就是改一行文本,五分钟搞定。但后续所有技术手段都依赖这一步,所以一定先做。

第 1 步:创建 /llms.txt

这是目前整个方案里最具代表性、也是最容易实现的一步。

/llms.txt 是由 fast.ai 联合创始人 Jeremy Howard(前 Kaggle 总裁)在 2024 年 9 月提出的概念。

你可以把它理解成专门写给 AI 看的站点地图

放在你网站根目录,格式就是一个简简单单的 Markdown 文件,内容是你手动筛选过的、最重要的页面索引。比如:

# 我的博客

> 一个专注前端开发的个人博客,覆盖 HTML/CSS/JavaScript 实战技巧。

## 核心文档

- [快速上手](/docs/start):5 分钟入门指南
- [API 参考](/docs/api):完整的接口文档
- [CSS 技巧大全](/wordpress):这些年我积累的 CSS 黑魔法

## 可选

- [更新日志](/changelog):最近更新了什么
- [关于我](/about):作者张鑫旭,微信zhangxinxu-job

记住几个要点:

  • 一定要用 Markdown 格式,纯文本,别搞什么富文本
  • 链接用 完整路径,方便 AI 直接拼接 URL
  • 每个链接后面附一句简短描述,让 AI 知道这个页面是干什么的
  • 不要什么都往里放——这不是站点地图,这是”精选目录”

有人可能会问:ChatGPT、Claude 它们真的会自动爬 /llms.txt 吗?

答案是:目前不会

有些失望?但事实如此。没有哪个主流 LLM 提供商承诺他们的爬虫会主动请求这个文件。

Ahrefs 的数据分析显示,/llms.txt 的请求里 94.9% 来自 GoogleBot,GPTBot、ClaudeBot 完全缺席。

那为什么还要做?

因为当用户把链接粘贴给 ChatGPT、或者开发者用 Cursor 配置你的文档时,这些 AI 工具会去读这个文件。

Mintlify 的 CDN 日志显示,7 天内 25 家公司的 llms.txt 中位被访问了 14 次。

这不是自动爬虫的流量,而是”人+AI”协作时的流量——但这恰恰是最高价值的流量。

所以,就算 AI 不主动爬,这步也值得做。更何况,万一哪天它们开始爬了呢?

第 2 步:为每个页面提供 .md 版本

如果说 /llms.txt 是”地图”,那这步就是”地图上每一个目的地的真实风景”。

做法很简单:对于你网站上任何一个有内容的页面,都在同级路径下提供一个 .md 版本。比如:

HTML 页面 Markdown 版本
/blog/my-post /blog/my-post.md
/docs/api /docs/api.md
/about /about.md

技术实现也不复杂,以 Next.js 为例:

// Route handler for /blog/:slug
export async function handleRequest(request: Request) {
  const url = new URL(request.url);
  const post = await getPost(url.pathname);

  // 如果请求路径以 .md 结尾,返回 Markdown
  if (url.pathname.endsWith('.md')) {
    return new Response(post.markdownContent, {
      headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
    });
  }

  // 否则返回普通 HTML 页面
  return renderHTML(post);
}

如果你用的是静态博客或者文档生成工具,更简单——直接让构建流程多输出一份 .md 文件就行。

现实问题也不能回避:维护两份内容确实有”内容漂移”的风险。

比如你更新了 HTML 页面,但忘了同步更新 Markdown 版本。

解决办法就是自动化转换,也就是使用统一的 Markdown 数据源同时生成 HTML 和 .md 两个版本。

第 3 步:用 <link> 标签和 HTTP Link 头

这一步做的事情很简单但很关键:告诉 AI 爬虫,我们这个页面有 Markdown 版本,在哪可以找到。

分两条路走:

第一条路:HTML <link> 标签,放在 <head> 里:

<link
  rel="alternate"
  type="text/markdown"
  href="/blog/my-post.md"
/>

这个标签是标准的 HTML 机制,rel="alternate" 从 HTML4 就有了,text/markdown 也是 RFC 7763 正式注册的 MIME 类型。

所以它不是什么黑客手段,是正经的标准用法。

第二条路:HTTP Link 头,在服务器响应里直接加:

Link: </blog/my-post.md>; rel="alternate"; type="text/markdown"

为什么要两种都做?因为不同 AI 工具的行为不一样。

有些会解析 HTML DOM,能读到 <link> 标签;有些只做 HTTP 请求,根本不解析 HTML 体,它们只会看响应头。

两条路都铺上,就谁也不会错过了。

服务器中间件实现也不难:

function addMarkdownLink(request: Request, response: Response) {
  const url = new URL(request.url);
  // 在响应头里加上 Markdown 版本的链接
  response.headers.set(
    'Link',
    `<${url.pathname}.md>; rel="alternate"; type="text/markdown"`
  );
  return response;
}

第 4 步:实现 Accept:text/markdown 内容协商

这是六步里技术含量最高的一步,但也是最有长期价值的一步。

因为根据我的判断,本文所展示的这一系列措施列表在五年后仍然存在的,就是第4步这里的Accept内容协商。

为什么?因为内容协商不依赖任何人同意新文件格式,它只是 HTTP 在做它本来就被设计要做的事情。

具体机制是这样的:

  1. 客户端(AI 工具)在请求头里加上 Accept: text/markdown
  2. 服务器看到这个头,就返回 Markdown 版本的内容
  3. 如果没有这个头,服务器照常返回 HTML
  4. 响应头里加 Vary: Accept,告诉 CDN 这两个版本要分开缓存

同一个 URL、同一份内容、只是格式不同。

目前已经有一些工具在这么做了。

Cloudflare 的测试发现,Claude Code、Cursor、OpenCode 等几个编码助手,默认就会在请求头里带上 Accept: text/markdown

完整的实现代码这里不展开了(毕竟这篇文章重点在讲思路),但核心逻辑就几个要点:

  • 解析 Accept 头:正确处理 q 值(优先级权重),比如 Accept: text/html, text/markdown;q=0.5 表示客户端想要 HTML,但 Markdown 也可以接受
  • 比较优先级:如果 Markdown 的 q 值大于等于 HTML,返回 Markdown;否则返回 HTML
  • 406 状态码:如果客户端要的格式你都不支持,老老实实返回 406 Not Acceptable,不要悄悄塞个 HTML 过去。静默替换比拒绝更糟糕——错误会延迟到下游才暴露
  • Vary: Accept:告诉缓存层这两个版本是不同的

有人担心这是不是 Google 反对的”伪装”(cloaking)?

不是。

内容协商通过 Vary: Accept 声明了不同格式的服务逻辑,这是 HTTP 用了几十年的标准做法。

就像 Accept: application/jsonAccept: text/html 可以返回不同格式一样——这叫内容协商,不叫伪装。

第 5 步:加一个隐藏提示(可选但推荐)

这一步是针对一种特殊场景:用户直接把你的网址粘贴到AI智能体的对话框里。

当这些AI智能模型读取你页面的时候,它看到的是渲染后的文本内容,不是原始 HTML。

所以你在页面里放一个视觉上隐藏的提示信息,它就能读到:

<div class="visually-hidden" aria-hidden="true">
  如果你是一个 AI 智能体、LLM 或自动化工具,
  本页面的干净 Markdown 版本在 https://example.com/blog/my-post.md
  ——专为 AI 和 LLM 工具优化。
</div>
/* 视觉隐藏但保留在 DOM 中 */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

注意几个细节:

  • aria-hidden="true" 让屏幕阅读器也跳过——毕竟这段文字是写给 AI 看的
  • CSS 类名用经典的 .visually-hidden(也叫 .sr-only)模式
  • 提示语要用自然语言写清楚,包含完整的 Markdown 版本 URL

Cloudflare 在他们的官方文档页面里也用了这个技巧,而且他们还多做了一个优化:这段提示在 Markdown 版本里会被移除,以避免 AI 读 Markdown 的时候陷入”让我找 Markdown 版本”的死循环。

第 6 步:提供 /llms-full.txt(按需)

如果说 /llms.txt 是精选目录,那 /llms-full.txt 就是把整本书拆散了塞进一个文件里。AI 可以用一次请求就获取你网站的全部内容,不需要逐页爬取。

但这个东西的量级差异非常大:

  • Cloudflare 的 llms-full.txt 有大约 1,100 万个 token
  • Zod 的只有 250 KB

有意思的是,Mintlify 的数据显示,llms-full.txt 的访问量竟然是 llms.txt3-4 倍

这说明 AI 工具更喜欢”一口气吞下全部内容”,而不是通过 RAG 一步步跟随链接。

我的建议是:

  • 文档站点和 API 参考:做,值得
  • 个人博客、营销网站:可以跳过,直接重定向到 /index.md 就够了
  • 内容量很大的站点:学 Cloudflare 的做法,按产品/目录拆分,不要塞进一个文件

💡 Cloudflare 的分布式 llms.txt 经验

Cloudflare 文档有 5000+ 页面,单文件会超出任何模型的上下文窗口。他们的做法是:

  • 为每个产品目录生成独立的 llms.txt
  • /llms.txt 只指向这些子目录
  • 比如 /r2/llms.txt/workers/llms.txt

优化之后,token 消耗减少了 31%,获取正确答案的速度提升了 66%

四、重要提醒:这些方法用了会白费功夫

网上有很多打着”AI SEO”旗号的所谓技术,但 Evil Martians 团队做了非常扎实的调研,发现以下 8 种做法完全没有证据支持,有些甚至是积极误导。

下面一一说清楚,省得你踩坑。

  1. <meta name="ai-content-url">

    没有任何规范、出处或 AI 工具在读取这个元标签。它出现在多篇”AI 优化”博客里,纯粹是以讹传讹。

  2. <meta name="llms">

    有人通过 llmsmetatags.org 提出,也提交到了 WHATWG HTML 规范,但被拒绝了(”不计划”)。唯一实现的站点是提案者自己的网站。

  3. /.well-known/ai.txt/ai.txt

    有多个互相竞争的提案,没有哪个有实质性的采用。

  4. ❌ HTML 注释(<!-- AI-READABLE-VERSION -->

    不起作用。因为 ChatGPT、Claude、Perplexity 这些工具在解析页面时都会剥离 HTML 注释,它们读的是渲染文本,不是 HTML 源码。没有哪个爬虫文档提到会读取注释做发现。

  5. ❌ “人类/AI”切换按钮

    看起来很贴心,但AI 代理不点按钮。如果你已经提供了 .md 路由和内容协商,这纯粹是装饰。

  6. ❌ 根据 User-Agent 自动返回 Markdown

    检测到 AI 爬虫的 User-Agent 就自动返回 Markdown 而不是 HTML——这就是 Google 明确反对的伪装(cloaking)。基于访问者身份而非请求内容来改变响应,是会被惩罚的。正确的做法是内容协商:客户端主动声明它想要什么格式。

  7. ❌ 专门的”AI 信息页面”

    标记为”给 AI 助手”的独立页面,包含公司结构化信息。没有任何证据表明任何爬虫或检索系统区别对待这些页面。好的 /llms.txt 和 Markdown 路由就能解决这个问题。

  8. ❌ Schema.org 和 JSON-LD

    这个是最大的”你以为有用但其实没用”。SearchVIU 做过对照实验,把产品数据只放在 JSON-LD 结构化数据里——ChatGPT、Claude、Perplexity、Gemini、Copilot 全都错过了。它们把结构化数据当作页面上的普通文本,不会专门解析。

唯一的例外是 Microsoft Copilot,因为它从 Bing 搜索引擎继承了结构化数据的理解能力。所以,不要删除你现有的结构化数据(它对传统搜索还是有用的),但别指望它对 LLM 可见性有直接帮助。

🤔 你有没有发现这些无效方案的共同模式?

都是有人发明一个新的文件或元标签 → 写一篇博客介绍它 → 然后其他博客引用那篇博客作为”证据” → 没有人真正检查过有没有 AI 系统在读取这个东西。

五、真正有效的策略是什么?

普林斯顿和印度理工大学的一项 GEO(生成引擎优化)研究,测试了 10,000 个查询中的 9 种内容优化策略,结果非常有意思:

策略 效果
在内容中包含直接引用 AI 可见性增加约 43%
添加具体统计数据 AI 可见性增加约 33%
引用权威来源 低排名内容的改善幅度达到 115%

看到没?每一项真正有效的策略,都是在丰富 LLM 实际能读到的可见文本,而不是在折腾 LLM 根本不会去读的元数据。

所以,除了做上面六步技术层面的工作,内容本身的质量也至关重要。多一些具体的数据、引文和权威引用,AI 就更容易理解并推荐你的内容。

六、做完以后,怎么验证?

两个现成的在线工具:

工具 检查什么
acceptmarkdown.com 专门检查内容协商的四项标准:Markdown 返回值、Vary 头、406 状态码、q 值优先级
isitagentready.com 全维度扫描:robots.txt、站点地图、Link 头、Markdown 协商、Content Signals、MCP 服务器等

isitagentready.com 是 Cloudflare 推出的工具,扫描完会给你一份详细的报告,每一项没通过它都会给你一个可以直接丢给 Cursor 或 Claude Code 的修复提示词。非常良心。

还有一件事不要忽略:加上分析追踪。在你的 .md 端点、/llms.txt/llms-full.txt 上做服务端日志记录,按 User-Agent 区分哪些是 AI 爬虫的请求,按 referrer 主机名看哪些流量是从 chatgpt.comclaude.aiperplexity.ai 过来的。

只有实测数据才能告诉你哪些技术真的在发挥作用,哪些只是”理论上应该有用”。

七、一张完整的实施清单

把前面说的整理成一张清单,你可以照着逐条打勾:

  1. 审计 robots.txt:确保没有屏蔽 AI 爬虫;加上 Content-Signal: 行声明内容使用意图
  2. 创建 /llms.txt:站点根目录放一个精选目录 Markdown 文件
  3. 提供 .md 路由:为每个有内容的页面提供同名 Markdown 版本
  4. 添加发现机制:HTML <link rel="alternate"> + HTTP Link 响应头
  5. 实现内容协商:支持 Accept: text/markdown,正确处理 q 值和 406
  6. 加隐藏提示:在 HTML 中用 .visually-hidden 告诉 AI Markdown 版本在哪
  7. 按需提供 llms-full.txt:文档型站点需要,内容少的站点可跳过
  8. 上分析和追踪:用服务端日志看实际效果
  9. 跑验证工具:acceptmarkdown.com + isitagentready.com

八、最后聊一聊——这波到底有多重要?

说实话,当前 Web 对 AI 智能体的支持现状并不好。

Cloudflare 在 2026 年 2 月扫描了前 20 万个域名,结论很扎心:

  • robots.txt 采用率 78%——看着很高,但基本是为传统搜索引擎写的
  • Content Signals 采用率 4%——刚开始起势
  • Markdown 内容协商通过率 3.9%——绝大多数站点不支持
  • MCP Server Cards / API Catalogs——不到 15 个站点采用

看起来有些失望,对吗?

可要是换个角度看,这恰恰意味着智能体无障碍访问这片领域现在还是一片蓝海。

谁先做,谁就能在 AI 充当信息入口的新格局里占据先手。

而且坦白说,前面讲的这六步里,除了内容协商需要一点服务器逻辑外,大部分都是低投入、高回报的事。

一个 /llms.txt 只要五分钟,一个 .md 路由可能半小时,就算 LLM 提供方现在还没承诺自动读取这些文件,当用户主动把链接喂给 AI 的时候,你的网站体验就已经比别人高了一截。

Web 正在经历从”为人类浏览设计”到”为智能体消费设计”的架构性转变。

如果说过去一年我们学到了什么,那就是一切都可能在短时间内翻天覆地。

参考文章

OK,以上就是本文的全部内容,信息量庞大,编辑不易,欢迎转发,分享,我们下篇文章再见!

再见

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12289

(本篇完)

浏览器原生支持JSON模块导入与开发啦!

2026-06-29 19:49:17

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12241
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、原生JSON模块导入

现在所有的主流浏览都已经支持直接import JSON文件并直接解析为JSON数据了。

JSON import兼容性

过去和现在

在过去,我们想要在项目中导入JSON,写法多半是这样的:

import config from "./config.json";

然后打包工具在构建时会读取 JSON 文件,将其转换为 JavaScript 模块,满足开发需求。

如果脱离打包工具,上面的语法是无效的,甚至会报错。

现在,浏览器已经有了原生的JSON导入能力,只需要在传统写法后面写上with { type: "json" }就可以了,例如:

import config from "./config.json" with { type: "json" };

并且还支持动态引入:

const module = await import("./config.json", {
  with: { type: "json" }
});

console.log(module.default);

二、深入JSON模块解析

导入带有属性的 JSON 时,我们可以把import后面的变量当做正常的 ES 模块一样处理。

比方说有一个名为config.json的文件,其里面的内容是:

{
  "bookName": "HTML并不简单",
  "url": "https://www.htmlapi.cn/"
}

此时,我们可以再业务JS代码中直接获取相关的数据,例如:

import config from "./config.json" with { type: "json" };
console.log(config.bookName); // 'HTML并不简单' 

无需像过去那样先fetchJSON.parse()解析,浏览器自动完成了解析。

对了,忘记说了,不是说只要是json后缀的文件就能解析,还需要服务器返回Content-Type: application/json的文件头信息。

其他补充说明

  • 如果JSON文件的体积较大,那么其实推荐使用传统的实现方法,即使用fetch() 动态请求,以优化首屏加载速度,否则,构建工具会将其直接内联到JS代码中,导致主包体积大幅膨胀。
  • 原生导入的 JSON 模块在解析后生成的纯 JS 对象是冻结(Frozen)的。对其直接进行修改(如 data.key = ‘value’)会导致失败或在严格模式下抛出 TypeError 错误。
  • 在 TS 中使用原生 JSON 模块,必须在 tsconfig.json 中配置 “resolveJsonModule”: true,否则无法正确推断导入对象的类型定义。

三、至此,结束

好了,本文内容就这么多,比较短,今儿个也不碎碎念了,展示下近期的钓获。

六张渔获图,分别是:

小蜗牛回锅鲤鱼鲫鱼138钓了37斤。
凯祥148六小时钓了33斤全鳊鱼。
六六180六小时连护36斤,也全都鳊鱼。
强子钓场150六小时,十几斤鱼,比较杂,罗非银鳕鱼昂刺鱼都有。
新胜148六小时,鳊鱼为主,20斤
福檀钓场,100元5小时鲫鱼塘,就两条,坑塘。

渔获图

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12241

(本篇完)

CSS color-scheme属性与light-dark()颜色函数

2026-06-24 20:05:10

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12241
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、从light-dark()颜色函数开始

light-dark()这个 CSS 函数接受两种颜色或两个图像,并根据当前页面的配色方案返回一种颜色或一个图像,无需 prefers-color-scheme 媒体查询进行匹配。

关于prefers-color-scheme

prefers-color-scheme媒体查询在“你不知道的CSS media查询与用户体验”一文中有介绍过,可以匹配系统层面设置的是深色模式,还是浅色模式。

深色模式设置截图

/* 深⾊模式 */
@media (prefers-color-scheme: dark) {}
/* 浅⾊模式 */
@media (prefers-color-scheme: light) {}

这个媒体查询设计初衷是好的,但是落地到实际开发中,却是个鸡肋。

为何?很简单,只有高级用户才会在系统层面设置深色模式还是浅色模式,对于客户端的软件而言(网页也包括在内),对用户体验友好的交互方式应该是在当前的应用程序中直接设置。

比方说我正在看一篇小说,关灯之后,我想保护眼睛,就开启深色模式,此时,最好的方法应该是直接在当前阅读页面唤起深浅模式切换的开关来实现,而不应该让用户千里迢迢跑到系统设置里面去设置(网页没有开关系统设置的权限)。

也就是因为这个实际需求,才CSS才设计了一个名为color-scheme的属性,可以不依赖系统层面的设置,自定义深色还是浅色模式。

二、color-scheme属性简介

color-scheme属性可以改变网页上浏览器内置组件的配色。

要知道,如今的浏览器默认组件其实内置了深浅两套配色,color-scheme属性可以对其进行切换设置。

眼见为实,大家可以选择下面这个下拉框,切换配色方案,就可以看到表单控件样式的变化了:

选择深浅模式:

输入框:

选择框:

最关键的代码就是下面这段:

选择深浅模式:<select onchange="document.body.style.colorScheme = this.value">
  <option value="light">浅色主题</option>
  <option value="dark">深色主题</option>
</select>

color-scheme的局限

color-scheme虽然可以设置深浅主题,但是只能改变浏览器内置的控件样式、选区样式等,实际开发肯定不是这个样子,你要进入深色配色,肯定是整体进入,而不是局部,否则你看下面这种样式效果,比小强粑粑还要丑!

深色效果截图示意

怎么办?

使用prefers-color-scheme媒体查询匹配并进行针对性的设置!

等等!

等一下表情包

你刚不是说prefers-color-scheme是鸡肋吗?

哎呀,你急什么急呢,我还没说完。

之前的使用指南确实是prefers-color-scheme进行匹配,虽然我也搞不懂为啥!明明是互相不影响的两个特性。

Component authors must use the prefers-color-scheme media feature to support the color schemes on the rest of the elements.

总而言之,就是在之前,关于颜色主题,CSS设计了不少东西,但就是不实用,还很麻烦,怎一个失败了得!

于是,还未放弃的规范制定者们又设计了一个新的CSS函数,没错,就是本文要介绍的light-dark()颜色函数。

三、细说 light-dark() CSS函数

color-scheme属性与light-dark()颜色函数配合,这才真正意义上把深浅配色的自定义能力完全抓在了开发者手中。

语法

/* 关键颜色值 */
light-dark(black, white);

/* RGB颜色值 */
light-dark(
  rgb(0 0 0),
  rgb(255 255 255)
);

/* 图片 */
light-dark(
  url("light-icon.png"),
  url("dark-icon.png")
);

/* 渐变图像 */
light-dark(
  linear-gradient(135deg, ghostwhite 20%, tomato),
  linear-gradient(45deg, darkslategray 20%, gold)
);

/* 自定义属性 */
light-dark(
  var(--light),
  var(--dark)
);

注意,虽然语法上显示支持图像类型,但是到目前为止,只有Firefox支持图像切换,大多数浏览器仅支持颜色设置。

案例

color-schemelight-dark()才是真正意义上的黄金搭档。

color-scheme用来控制是深色还是浅色,light-dark()基于color-scheme的设置,选择应该渲染哪个颜色。

举个例子:

<section class="section">
  选择深浅模式:<select id="select" onchange="document.body.style.colorScheme = this.value">
    <option value="light">浅色主题</option>
    <option value="dark">深色主题</option>
  </select>
  <p>
    输入框:<input type="text">
    <textarea rows="3">
    滚动条
    滚动条
    滚动条
    滚动条
    滚动条
    </textarea>
  </p>
  选择框:<input type="radio"> <input type="checkbox">
</section>

外部<section>元素通过light-dark()函数设置了深浅配色,CSS如下所示:

.section {
  border:1px dashed light-dark(#ddd, transparent);
  background:light-dark(white, black);
  color: light-dark(black, white); 
  padding: 16px;
}

此时,当选择深色主题的时候,就会有如下所示的渲染效果,明显看起来舒服多了:

深色效果示意

兼容性

light-dark()函数从2024年开始被各大浏览器支持,兼容性还算可以,在一些内部产品中已经可以大胆使用了。

light-dark()兼容性

四、再提一嘴prefers-contrast颜色查询

虽然light-dark()函数的语义是深浅主题切换,但是我们实际开发并不一定要朝着深浅方向走,只要是配色主题都可以使用。

例如,正常配色主题,和高对比度主题。

这就需要提一下prefers-contrast @media 查询语法了。

例如:

/* 如果是高对比度,采用dark配色渲染 */
@media (prefers-contrast: more) {
  :root {
    color-scheme: dark;
  }
}

很棒的用户体验增强处理,如果用户设置了设备是高对比度,那我们就是要dark模式。

只是,在国内,这类与用户体验细节密切相关的特性缺乏关注与市场,所以,我就不进一步展开介绍了,大家了解下有这么个东西就好了。

附上兼容性,还不错。

prefers-contrast媒体查询兼容性

五、最后要不再扯点什么

今天有个同行拉我进了一个群,群名很有意思,叫做“前端夕阳群”,我就是看到这么群名才加入的。

一进群,就看到齐刷刷的“看我的博客成长”这样的言论,顿时内心一阵唏嘘。

他妈的时间过得可真快,一晃,入行都已经16年了,16年了啊,当年在出租屋闭关学习的场景还历历在目,在租住小区外面吃面见到一个倾心妹子迟迟不敢搭讪的经历也历历在目。

眼下早已没了当年一往无前,锐意进取的心态。

学也学习,输出也没停,工作依然积极,AI加持下,四个项目并行都不成问题,只是职业发展肉眼可见的到头,公司也是一直不温不火的,一颗改变世界的心也舍不得就这么算了,行业也不景气,人的心态也开始拧巴了。

目前就处在保持现状,走一步看一步的状态。

嗯,就先说这么多吧,下篇文章继续碎碎念吧。

克莱恩

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12241

(本篇完)