tailwind的基础概念

TailwindCSS 草稿

我个人认为tailwind的基础概念有三部分:

  • 响应式断点
  • 组件化
  • 灵活的前缀

我的分类并不科学,这三部分其实有些是互相重叠的,这篇文章就过一下这三部分内容。。当然不会事无巨细的过,更多详情还是要参考官方文档。

响应式断点

五个关键字,sm, md, lg, xl, 2xl。分别对应640, 768, 1024, 1280, 1536三个横向尺寸。这五个关键字可以前缀冒号在所有的tailwind类名上。

这些关键字的意思是:屏幕宽度至少到达对应的标准,后续的样式才会被应用上。

比如md:w-32的意思是:在屏幕尺寸超过768px时,才应用w-32这个样式。

而没有带断点前缀的类,则会生效在任何宽度的屏幕上。比如我直接写个w-32,它会在任何尺寸的屏幕上都生效,即便屏幕宽度只有40px。

所以在设计的时候,我觉得一个比较好的方式是先确定你的应用要使用几个断点。比如我们开发的应用准备支持三种形态:宽度小于640的极小屏幕,宽度在640~1024之间的中型屏幕,然后宽度大于1024的统一认为是大屏幕。那么实际上我们用到的断点只有两个:smlg。那么我们实际上要为页面写三套样式,分别是:

样式前缀 应用的屏幕尺寸
无前缀 0~640px
sm:前缀 640px~1024px
lg:前缀 1024px~

这稍微有一点不直观,反直觉。tailwind还支持另一种响应式声明:我们上面说明白了,sm/md/lg/xl/2xl这样的断点关键字指的是屏幕尺寸至少达到断点标准。还有max-sm/max-md/max-lg/max-xl/max-2xl这样的断点关键字指的是屏幕尺寸至多达到断点标准。

所以如果你不嫌麻烦,我们也可以如下表达上面的例子:

样式前缀 应用的屏幕尺寸
max-sm: 0~640px
sm:max-lg: 640px~1024px
lg: 1024px~

如果你还是觉得别扭,tailwind支持你自定义断点定义

  • 可以更改断点的尺寸定义
  • 可以自定义断点的名字
  • 可以更改断点关键字的语义:从至少,改成至多,甚至是在某个区间,都可以

下面就是一个在tailwind.config.js中自定义断点来实现上例需求的例子:

module.exports = {
    theme: {
        screens: {
            "phone": {
                "max": "639px"
            },
            "tablet": {
                "min": "640px",
                "max": "1023px"
            },
            "pc": {
                "min": "1024px"
            }
        }
    }
}

如果你采用了如上的配置,那么使用起来就比较符合直觉了:

样式前缀 应用的屏幕尺寸
phone: 0~640px
tablet: 640px~1024px
pc: 1024px~

另外虽然说响应式设计主要针对的是屏幕宽度进行,但你也可以在断点定义中自定义其它的屏幕属性,比如高度什么的。。不是很常用,这里就不提了。

夜间模式

CSS有个media query叫prefers-color-scheme,它一般用来查询用户的agent或系统设置里是否开启了夜间模式。开启夜间模式的话,值是dark,否则是light

所以一般支持夜间模式的App会如下这样写它的css

<style>
.box {
    width:300px;
    height:300px;
}

.theme-a {
  background: #dca;
  color: #731;
}

@media (prefers-color-scheme: dark) {
  .theme-a.adaptive {
    background: #753;
    color: #dcb;
    outline: 5px dashed #000;
  }
}
</style>
<div class="box theme-a adaptive">Theme A (changed if dark preferred)</div>

简单来说就是先写一个基础样式,然后再使用@media(prefers-color-scheme:dark)把夜间模式的样式写上。这样如果系统开启了夜间模式,.theme-a.adaptive里的样式就会把.theme-a里的样式覆盖掉。原理就是这么个原理。

上面这段代码在非夜间模式下的渲染结果如下:

css-example-light

在夜间模式开启的情况下的渲染结果如下:

css-example-dark

在夜间模式下,浏览器是如下采用样式的:

css-example-dark-styles

tailwind里要做这个事情就简单了,给样式加dark:前缀就行,比如在tailwind里,上面的例子就可以如下写:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            sapling: {
              300: '#dca'
            },
            pueblo: {
              900: '#731'
            },
            copper: {
              700: '#753'
            },
            bone: {
              200: '#dcb'
            }
          }
        }
      }
    }
  </script>
  <title>Document</title>
</head>

<body>
  <div class="w-[300px] h-[300px] bg-sapling-300 text-pueblo-900 dark:bg-copper-700 dark:text-bone-200 dark:outline-dashed dark:outline-black dark:outline-5">Theme A (changed if dark preferred)</div>

</body>

</html>

说到这里,推荐一个颜色分级生成工具:Color Generator

tailwind指令,样式复用与组件化

说这个东西之前,就需要先介绍下tailwind中的指令,有四个,分别是@tailwind, @layer, @apply@config。其中@config是个很无聊的指令,它是用来显式指定tailwind配置文件的路径的,这里就不提了,主要说另外三个。

@tailwind这个指令是用来声明“你要用tailwind的哪部分功能的”,官方教程里的初始CSS文件里(即传递给tailwindcss命令行的输入文件),让你加以下三行:

@tailwind base;
@tailwind components;
@tailwind utilities;

它们的意思如下:

指令 语义
@tailwind base; 生成基础样式类。基础样式是一些覆盖Agent默认行为的样式集合,比如取消默认marin,unstyle所有heading,list,将图片设定为block-level之类的基础样式
@tailwind components; 生成组件样式类。目前我所知道唯一的组件样式类,就是container。不过用户可以在这里面扩充。所谓的组件样式类,是对工具类的聚合和打包
@tailwind utilities; 99%的tailwind知识点都在这里,几乎所有的自带的样式类都属于工具类。比如bg-xxxw-xx之类的

换句话说,tailwind引擎为你生成的每一个类名,都归属于basecomponentsutilities之一。这么说或许不准确,准确的说是:

  1. 那些默认的,不需要你手动添加就自动套用上的基础类的定义,属于base
  2. container以及用户自行扩充的自定义类名,属于components
  3. 绝大部分tailwind里的类,都属于utilities

那这三个兄弟,base, components, utilities是个什么东西呢?它们叫“layer”。

你知道了“layer”的概念,下面我们就能比较合理的介绍@layer这个指令:它就是用来扩充对应的“layer”的定义的

比如,你想自定义基础样式类。因为tailwind自带生成的基础样式类中,所有的<hx>标签都是去除所有样式的,你不满意,你觉得还是要把<h1><h2>的字体调大一点,你就可以在你的tailwind样式文件中添加以下内容进行自定义:

@layer base {
    h1 {
    font-size: xx-large;
    }
    h2 {
    font-size: x-large;
    }
}

你正常写一个蓝色的按钮,需要如下写:

  <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Press Me</button>

你觉得太麻烦了,想把它的样式“封装”一下,你就可以在components layer添加一个你自定义的组件样式集合,如下:

@layer components {
    .btn-blue {
        @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
    }
}

诶,出现新知识点了啊!这个@apply又是什么意思呢?在上面我们扩充base layer的时候,使用的是原生的CSS来扩充的。需要我们一个一个写样式。使用@apply的意思就是:把标准utilities layer里的工具类的样式,应用到这里来。

当然,同样的照猫画虎,你可以扩充utilities层的类定义:更改现有工具类的定义,或者新增你自己的工具类,都行。

不过有两个有关@apply的小坑需要注意一下:

第一个小坑

使用@apply生成的样式,默认是不带!important声明的。如果需要,你要自己在尾巴后面把!important写上,如下:

.btn {
  @apply font-bold py-2 px-4 rounded !important;
}

第二个小坑

像Vue或React这样的前端框架,都支持组件样式隔离:即每个组件都可以有自己独立的CSS代码。咱们这里不讨论具体框架,只说这么个意思:比如你的项目,有一个全局样式文件global.css,然后有两个组件C1C2

这时,比如你在global.css中扩充components layer,自定义了一个组件类,如下:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .card {
    background-color: theme(colors.white);
    border-radius: theme(borderRadius.lg);
    padding: theme(spacing.6);
    box-shadow: theme(boxShadow.xl);
  }
}

然后你想在你的组件中使用这个组件类,但并不是以内联的形式使用它的,而是试图以如下的方式使用它(以下是伪码,既不是React也不是Vue,就说那么个意思):

<style scoped>
    div {
        @apply card;
    }
</style>

<template>
    <div>...</div>
</template>

你工具链是会报错的。这里报错的原因在于:多数前端框架在做打包整合时,处理组件的隔离样式,是独立且并行进行的。也就是说,C1C2的编译,以及global.css的编译是独立进行的,在编译过程中,tailwindcss引擎是不知道card这个类的定义到底在哪的,它就没法把这个@apply翻译成正确合法的CSS代码。

如果你用内联的方式,把这个card直接写在<div class="card">...</div>里,倒还没有问题,因为这样写的话,编译打包工具链和tailwindcss引擎都不需要处理这个class,也就不需要在编译打包的时候知道card的具体定义到底在哪。

但你使用@apply的话,就不行了:tailwindcss必须知道card的定义到底在哪里。

解决办法有两个:一个上面已经说了,内联的去使用自定义的类名。

另一个,就是通过tailwind插件的方式去扩展Layer定义,在tailwind配置文件里进行扩充,如下:

const plugin = require('tailwindcss/plugin')

module.exports = {
  // ...
  plugins: [
    plugin(function ({ addComponents, theme }) {
      addComponents({
        '.card': {
          backgroundColor: theme('colors.white'),
          borderRadius: theme('borderRadius.lg'),
          padding: theme('spacing.6'),
          boxShadow: theme('boxShadow.xl'),
        }
      })
    })
  ]
}

灵活的前缀

除了断点前缀sm/md/lg/xl/2xl和夜间模式前缀dark外,tailwind中还有巨多的前缀。巨灵活无比。这里简单的过一下这些前缀

对应伪类选择器的前缀

这类前缀一般用来处理交互响应,典型的就是hoverfocusactive这三个前缀。它们基本都能与CSS原生的某个伪类选择器对上。这里就不一一列举了,简单的把它们分为三类:

分类 常用前缀
处理交互 hover, focus, active, visited, focus-within, focus-visible
在列表或表格中选择元素,常用于<li><tr> first, last, odd, even
表单相关 required, invalid, disabled, read-only, indeterminate, checked

伪类前缀还有两个特殊用法:一个是与父元素联动,一个是与兄弟元素联动

与父元素联动

在父元素上应用group类,然后在子元素上使用group[xxx]来查询父元素的状态,从而有条件的在子元素上应用样式,如下示例:

<a href="#" class="group block max-w-xs mx-auto rounded-lg p-6 bg-white ring-1 ring-slate-900/5 shadow-lg space-y-3 hover:bg-sky-500 hover:ring-sky-500">
  <div class="flex items-center space-x-3">
    <svg class="h-6 w-6 stroke-sky-500 group-hover:stroke-white" fill="none" viewBox="0 0 24 24"><!-- ... --></svg>
    <h3 class="text-slate-900 group-hover:text-white text-sm font-semibold">New project</h3>
  </div>
  <p class="text-slate-500 group-hover:text-white text-sm">Create a new project from a variety of starting templates.</p>
</a>

最外层的<a>上应用了group,里面两个孙子一个儿子都使用group-hover:stroke-whitegroup-hover:text-white来在父元素被hover时更改颜色,这个效果如下所示:

group-modifier