最近公司的一个新项目上马,我被安排来做项目初始化,前端初始化的时候使用了 Vue3 + Tailwind CSS + NaiveUI,在搞基础布局的主题变化时出现了本次插曲。
按照设计,Web PC 端的左侧边栏有一排导航按钮,这些导航按钮在 light 模式下应该是白底,在 dark 模式下应该是 Naive UI 的默认底色。按照需求描述,只需要使用 Tailwind CSS 来实现「仅在 light 模式下修改指定按钮背景色为白色」就行了。
可坑爹的是 Tailwind CSS 只提供了 dark:bg-white
的写法而没有类似 light:bg-white
的写法,按照正常写法可以使用 class="bg-white dark:bg-[#需要的颜色]"
来处理。但是我不希望这样写,因为 dark 模式下 UI 按钮组件默认的底色就是我所需的,所以我没理由再去强调 dark 模式下的底色是什么,我只需要使用类似 light:!bg-white
的写法来强制覆盖 light 模式下 UI 按钮组件的底色就行。
于是我开始了无休止的 AI 辅助编程:
注:这一步我的诉求是让 AI 帮忙直接生成解决方案。
我最开始的思路是直接将问题描述清楚,让 AI 帮忙生成解决方案,于是:
ryomahan [5:26 PM]
Tailwind CSS 有没有类似 light:bg-white 的写法
ClaudeAPP [5:26 PM]
在 Tailwind CSS 中,确实有类似 light:bg-white 这样的写法。这叫做 variant。Tailwind CSS 支持几种 variant:- responsive:用于响应式设计,如 sm:bg-white 表示在小屏幕上使用 bg-white 类
- hover/focus/active:用于鼠标交互状态,如 hover:bg-white 表示鼠标悬停时使用 bg-white 类
- dark:用于暗色模式,如 dark:bg-white 表示在暗色模式下使用 bg-white 类
- light:用于亮色模式,如 light:bg-white 表示在亮色模式下使用 bg-white 类所以,如果你想在亮色模式下应用 bg-white 类,可以写成:
html
<div class="light:bg-white bg-gray-800">...</div>
这个 div 在亮色模式下会有 bg-white 类,在暗色模式下会有 bg-gray-800 类。除此之外,Tailwind CSS 还有很多其他的 variant,可以在官方文档中找到:[https://tailwindcss.com/docs/variants](https://tailwindcss.com/docs/variants)希望这个解释可以帮助你!有任何其他问题 feel free 问我。
马后炮:其实这里已经将问题的核心点出来了,即 Tailwind CSS 实现类似 dark:
的写法是通过一种叫做 variant
的概念来实现的,Tailwind CSS 本身提供了一些基础写法,并且给出了文档。
我当时着急直接得到解决方案,便以为 <div class="light:bg-white bg-gray-800">...</div>
就是我想要的答案,但是拿到项目代码中测试发现不管用。于是便开始了下一次问答,而无视了这条答案中最重要的那句话:在 Tailwind CSS 中,确实有类似 light:bg-white 这样的写法。这叫做 variant。
在直接提问这个思路下我又尝试问过如下几个问题:
得到的答案都不是我想要的东西,于是我开始转换思路。
注:这一步我的诉求是让 AI 帮忙直接生成解决方案。
经过直接提问发现无法得到我想要的答案,于是我开始尝试从实现原理层面进行引导,让 AI 帮忙生成解决方案,我尝试过如下提问:
经过这几次提问之后我发现其中多个答案都在配置文件中提到了 variants 这项配置,于是:
ryomahan [7:09 PM]
variants 是什么
ClaudeAPP [7:09 PM]
variants 是 Tailwind CSS 一个很强大的功能。它允许你为某些类定义不同的变体(variants),然后在使用这些类时,可以通过如 dark: 前缀来选择使用哪个变体。比如说,对于背景颜色 bg-blue 这个类,你可以定义两个变体:
js
variants: {
backgroundColor: ['responsive', 'dark']
}
这定义了 responsive 和 dark 两个变体。然后在使用时,可以这样写:
- bg-blue - 选择默认的变体
- dark:bg-blue - 选择 dark 变体
- responsive:bg-blue - 选择 responsive 变体所以通过 variants,一个类可以有多种样式,你可以根据需要选择使用哪个变体。variants 常见的使用场景有:- 定义 light/dark 模式下的样式
- 定义不同 breakpoint 下的响应式样式
- 定义不同互动状态(hover/focus/active)下的样式
- ...可以说,variants 是 Tailwind CSS 实现高度可定制和条件化样式的基石功能之一。掌握好了 variants,可以极大提高在 Tailwind 中写 CSS 的能力。总之,variants 是一个非常强大而又重要的 Tailwind CSS 功能,值得深入学习和使用。
马后炮:这个回答其实已经讲了很多有用的信息了。
我还是执着于让 AI 直接给我生成方案,所以我粗略看了一下之后又进行了如下提问:
<div class="bg-white">
等同于 <div class="light:bg-white">
.light
class 下 dark:bg-white 没有生效(这里我把我使用的配置文件贴上去了,因为内容太长就不复制过来了)bg-white
可以仅在 light mode 生效在 dark mode 不生效(又开了一个新的会话,这已经是第二天了,昨天晚上感觉这是个坑所以就去忙别的了)其实这一步的所有提问基本都是在做无用功,因为我太执着于直接获取解决方案而忽略了解决问题的正确流程,我把 AI 想象的过于强大了。
注:这一步我的诉求是让 AI 帮忙解释这一类功能的实现原理,在从原理引导 AI 帮忙生成解决方案。
经过了漫长的无效提问后我开始反思,我在处理问题的过程中太执着于让 AI 直接帮忙生成解决方案了,于是我又转换思路:先让 AI 帮忙阐述实现原理,再从原理入手生成解决方案。
ryomahan [9:40 AM]
Tailwind CSS dark mode 实现原理
ClaudeAPP [9:40 AM]
Tailwind CSS 的 dark mode 实现原理主要有以下几点:1. 定义 dark 模式的颜色变量。Tailwind 在 colors.js 文件中定义了 dark 模式下的颜色变量,例如:
js
colors: {
// ...
gray: {
900: '#202225',
800: '#2f303d',
700: '#353646',
// ...
}
}
这些颜色变量会在 build 过程中生成对应的 utility。2. 定义 prefers-color-scheme 相关的 variants。在 variants.js 中定义了:
js
variants: {
// ...
prefersColorScheme: ['dark']
}
这表示 dark mode 下会生成 .dark 等 class。3. 在 build 过程中生成 dark mode 的样式。在 build 过程的 variants 阶段,会根据 prefers-color-scheme 的 variants 生成 .dark 等 class,并使用 dark mode 的颜色变量填充这些 class。4. 在 HTML 中使用 prefers-color-scheme media query 加载相应的样式。例如:
html
<link rel="stylesheet" href="tailwind.css">
<link rel="stylesheet" media="prefers-color-scheme: dark" href="tailwind.dark.css">
tailwind.dark.css 是 dark mode 的样式,会在 prefers-color-scheme: dark 条件下被加载。5. 可选:使用 JavaScript 切换模式。可以使用 matchMedia 检测 prefers-color-scheme 的变化,并动态加载样式来实现模式切换。所以总体来说,Tailwind 通过定义 variants、颜色变量和生成不同模式的样式文件来实现 dark mode。hope this helps!
马后炮:回过头来看,这个回答完全没有参考价值,甚至不如前面提到 variants 的那几个回答有价值,所以说 AI 的生成式回答的随机性还是比较坑的。
然后我问了:
这里我是想:在当前项目中所谓的主题模式是通过控制 Layout 根 DOM 的 class name 来实现的,当 class 是 dark 时为 dark mode,是 light 时为 light mode。所以只需匹配父类就能够实现我想要的东西。事实证明这个思路确实是没问题的,但是并不是通过简单的 class 实现的。
最终在 Tailwind CSS 的官方文档的引导下我发出了如下提问:
class="light:bg-white"
能够转换为如下 CSS .light .light:bg-white { background: #fff }
经过这四连问之后,我以为找到了解决这个问题的正确途径,因为这几个问题最终得到的结果虽然不能生效但是看上去像是那么回事,于是我整理了之前的问题继续发问:
ryomahan [9:58 AM]
在 Tailwind CSS 中使用 addVariant 实现如下效果使得 `class="light:*"` 能够与 `class="dark:*"` 类似在有 .light 父 css class 时生效
ClaudeAPP [9:58 AM]
你可以这样使用 Tailwind 的 addVariant 功能来实现这个效果:
js
addVariant('light', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.light ${className}`
})
})
这段代码会添加一个 light 变体,使得任何使用 light: 作为前缀的类都会在 .light 父元素下生效。例如:
html
<div class="light">
<p class="light:bg-blue-500">...</p>
</div>
这个 p 元素会具有 bg-blue-500 这个类,因为 light:bg-blue-500 在 .light 元素下生效了。如果没有 .light 父元素,light: 类就不会生效,和 Tailwind 的 dark: 变体一致。所以这给你实现了和 dark: 类似的机制,来根据父元素开启某些样式。Hope this helps! Let me know if you have any other questions. (edited)
这是 AI 给我的方案中距离正确答案最近的一次 ,但是仍旧无效,我又在这个问题的基础上尝试了十几轮的询问,得到的答案都是无效的。
在经历昨天下午两个小时外加今天上午两个小时的 AI 问询无果后,我悟了:就当从来没有 AI,早些时候我怎么解决问题现在还怎么解决,于是:
整个流程大概持续了十分钟左右的时间,我解决了这个问题,其实如果不去群里发求助信息一上来就看源码的话应该三两分钟就能解决问题。
完整搜索路径:先在 Tailwind CSS 中全局搜索 dark,看了一些结果发现都不是,好多都是测试用例或者其他无关文件中的。于是将搜索范围调整到 src 目录,大概视察了一下后定位在了一个叫 darkVariants 的变量上,跳转到指定文件后果然就是 dark mode 的定义原文,原文如下:
darkVariants: ({ config, addVariant }) => {
let [mode, className = '.dark'] = [].concat(config('darkMode', 'media'))
if (mode === false) {
mode = 'media'
log.warn('darkmode-false', [
'The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.',
'Change `darkMode` to `media` or remove it entirely.',
'https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration',
])
}
if (mode === 'class') {
addVariant('dark', `:is(${className} &)`)
} else if (mode === 'media') {
addVariant('dark', '@media (prefers-color-scheme: dark)')
}
}
它被放在一个 variantPlugins 变量中,然后我通过引用查看发现使用该变量的地方是项目初始化内部 plugin,然后我参考这个思路定义了一个自定义插件:
/** @type {import('tailwindcss').Config} */
import plugin from "tailwindcss/plugin"
module.exports = {
darkMode: "class",
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {},
plugins: [
plugin(function ({ addVariant }) {
addVariant("light", ":is(.light &)")
})
]
}
问题解决。
经过这一次折磨人的使用经历后我觉得我需要重新审视一下 AI 在我日常编程时的定位,或者说我在编程时应该如何使用 AI。一下是基于本次经历我的几点感悟: