新年福利!TailWindCSS+Vite+Vue3打造个性化在线简历项目,欢迎Fork

前言

祝大家在新的一年:大吉大利,身体健康,迎大运,发大财! 吐槽随便侃侃,无感请直接跳过到正文。

注册了快 7 年的账号,终于发了第一篇文 你敢信 ~~ 哈哈,今天开张了,撒花。至于契机嘛... 司倒,失业之 T_T。 最近刷看到类似的帖子也挺多的,ORZ 放心,真不是来输出负面情绪的 ~ 主菜马上就到。

y 情后各行各业都不容易啊,待了六年的公司也熬不住去年末倒下了(还有 3 个月工资拖欠着,希望仲裁给力吧)。个人也长时间呆在舒适区,大环境每况愈下,经历了这波社会毒打后,时常感到焦虑。经过了几天的 emo 期(并没有,疯狂打球笑嘻嘻 ... 但心里苦)哥们已经调整好了,等你回来 生活终归是要继续的

给自己放放假,调整心态,查缺补漏,为了找到新工作,自己制定了如下小目标:

  • ✅ 1. 重启技术博客 blog.fridolph.top,坚持输出
  • ✅ 2. 做几个实战项目(于是这篇文就诞生了)
  • ⏰ 3. 整理前端知识大纲,形成思维导图
  • ⏰ 4. 逛掘金,刷面经 + 整理(有些想法,于是开了新坑,再过几天就 OK)
  • 💊 5. 找到工作转正前 封印Steam - "你算什么男人算什么男人,眼睁睁看帕鲁走却不闻不问" T_T

我原本是想重构一下我之前写的在线简历,采用 Vue3 + TS + Pinia 来实现。几下敲完代码,我自己都觉得没啥东西。简历上个人作品,开源那两栏空空如也 = = 之前老刷到tailwind的文章,遂决定学习换个心态尝试学习,加到项目中,现切现做,这瓜绝对保熟

之前刷到 前端发展趋势的预测 之类文,很赞同一些观点。学历,简历只是敲门砖,在这个越来越卷的环境,竞争力不足,以后还是要多关注学习下 Node、Next.js,伪全栈万金油是个可选方向。

通过这篇文章你可以

  • 📖 学会在项目中灵活使用 TailWindCSS
  • 📮 了解 TailWindCSS 的核心概念
  • 📝 获得我整理的一份 Tss 速记指南
  • ⬆️ 随我一起学习 CSS "新" 特性
  • 🚀 本项目 Vite + Vue3 实践及相关优化
  • 🌹 Fork + Star 即得一份在线简历模板,现改现用

注:本项目使用 pnpm 作为包管理工具,请升级 Node 版本到 16.22.2 以上

下面是我学习 TailWindCSS 的过程,本文不会先上来讲大段概念,而是通过实际项目入手,获得正反馈后再反思、理解,并思考总结写出该篇文章。

为什么想学 TailWindCSS

之前也看到过类似的文章推送,我大致的印象就是这不就是 BootStrap 嘛。毕竟,人的精力是有限的,现有的 BEM 定义 class 一把梭就挺快的,为什么还要用这种过于原子化的 CSS mixin,import 肯定又是 几百 k。 不知道屏幕前的你有没这些疑问。

每一个能够流行起来的高 Star 项目内涵都不会简单,反正练手,正好了解一番,毕竟实践过后,才有发言权不是嘛

而巧合的是,我有一个现成的案例。以前为了找工作,我也做过在线简历项目。做完新版简历后,不论是代码的提炼还是规范性方面都有了很大的提升。这也算是对过去几年的努力有个交代吧,各个方面有了一个明显的对比。

本文中我简称 TailWindCSS 为 Tss可能会有误导,因此在其他地方请不要使用这个名词

在比较的同时,我会插入一些 TailWindCSS 文档中的概念,这样更容易让我们理解和体会到 Tss 的特性。

最重要的是切入点,本文通过实际案例让你快速了解 Tss,对感兴趣的部分可以对照文档自行学习,可谓事半功倍。废话不多说,show you the code!

新旧项目对比

下面开始差生示范,只对比 HTML 结构和 CSS,JS 代码就暂省略了

App.vue

老项目的 app.vue style 没有加 scoped,我们编写的 css,比如 .page-resume p { margin: 10px 0; } 这类代码,一不小心就会应用到其他模块中,而很难发现。命名是门学问,诸如 BEM 之类的规范有很多,这也是业界主流吧,但随着项目内容增多,样式会越来越多臃肿。

我正好遇到过项目中三人同时写一个路由下的三个子页面,都有查询和表格,另个页面是以图文为主,很多链接,提示说明。 有个同事下了如下代码:

html 复制代码
<style>
  .xxx-page a {
    color: var(--text-link) !important;
  }
  .xxx-page .title {
    font-size: 12px !important;
  }
</style>
<style scoped>
  /* 页面相关样式 */
</style>

template 都是复制粘贴,page 名还没改,一旦切到这页面再切到其他页面就会覆盖某些样式 ...... 产生极其强大的魔法

虽然标签里的 class 会变得很臃肿。但单看 DOM 结构,这是良好符合 WAI-ARIA 规范的。有以下好处:

  • 不用纠结命名,从此告别 xxx-wrapperxxx-titlexxx-listxxx-item

  • 避免潜在的样式冲突。因为框架都给你定义好了

你可能会吐槽,这 html 一大堆很难维护,css 是好了可 html 又病了

关于这点,后续章节 @apply 或者 tailwind.config.js 可能解决,但注定要在灵活性上做一定取舍

Hobby > movie 过渡

如果上面的不能说服你,那这个例子可以补充一些论据。

Hobby 模块里都是一些简单的动画实现,其中 movie 在旧模块中是这么实现的:

这只是其中一个组件,我们给了不同命名来加以区分。最开始写这块的时候没想太多,也没拆分组件,一个文件几百行 css 代码出现了(屎山就是这么炼成的)

项目中 = = 动辄几百行的还算好,经常 template、js 代码加起来几百行,若是再来上千行的 css,加上项目赶工期,就更头大。项目完给时间大家自觉优化还好,但现实情况,经常继续改 bug,同时又要做新功能 ... 看着那座"山",不由得一声叹息

html 复制代码
<!-- 神奇,我真的没写css代码,也不用去想 wrapper item box 之类的命名了 -->
<div class="flex flex-wrap justify-content">
  <div class="group overflow-hidden relative ring-1 ring-slate-200 rounded flex items-center justify-center size-12 mr-3 mb-3 origin-center align-center">
    <Iconfont class="size-12 absolute left-0 top-0 translate-x-[10px] translate-y-1 transition-transform ease-linear duration-500 group-hover:-translate-y-12" />
    <Iconfont class="size-12 absolute left-0 top-0 translate-x-[10px] translate-y-1 transition-transform ease-linear duration-500 group-hover:-translate-y-12" />
  </div>
</div>

上手 Tss 后,最大的一个变化和惊喜就是,只要你知道你想实现什么,都可以直接用 class 实现最终效果。

可能会吐槽,class已经一坨完全不能看,放心,这只是开始,后面有解决方案

没有写一行样式,完美实现了相同的效果,而且复用性强。当你把这串 template 丢给同事,只要有 tailwind 不需要引入 css 文件,不用担心 scoped 和样式覆盖就能得到一样的效果。

并不是说 BEM 不好,我们甚至可以两者结合,如果 Tss 提供的类名不足以实现你想要的效果,可把大致布局文字颜色等先写好,再另起一个 class 继续写 CSS 即可。

对 Tss 动画感兴趣的小伙伴可参考文档,玩出更多花样 www.tailwindcss.cn/docs/transi...

Hobby > shake 动画

上面的例子可实现过渡效果了,那动画了? 在 class 里写@keyframe 嘛?

这里就延伸出 Tss 可配置文件,tailwind.config.js。不仅仅是 动画,其他类似断点,和一些自定义类名,mixin 等,我们都可根据实际情况自行添加。

原则上,能通过 base core 完成的功能就尽量别放到配置中,因为打包会生成额外的代码,除非你确定这部分会多处使用和利于抽象。

js 复制代码
/** @type {import('tailwindcss').Config} */
export default {
  theme: {
    extend: {
      animation: {
        singerShake: 'singerShake linear infinite',
      },
      keyframes: {
        singerShake: {
          '0%, 100%': { transform: 'translateX(0)' },
          '10%, 30%, 50%, 70%, 90%': { transform: `translateX(-2px) scale(1.05)` },
          '20%, 40%, 60%, 80%': { transform: `translateY(2px) scale(1)` },
        },
      },
    },
  },
}

在代码中直接 animate-[singerShake_3s_ease-in-out_infinite] 即可使用。至于细节和命名等看文档就好,后面的 Tss 实用指南会提到。

html 复制代码
<div class="transition-all duration-300 ease-linear origin-center hover:bg-slate-500 hover:rounded-full hover:text-white">
  <Iconfont 
    class="size-12 absolute left-0 top-1 rotate-0 text-center transition-all origin-center group-hover:animate-[singerShake_3s_ease-in-out_infinite]"
    :size="28"
    name="changge"
  />
</div>

CSS 更可控

为了最快实现一个公共列表的样式,写出这么一串样式。如果要优化,在前期,可以通过定义 class,把公共样式抽离出来。

本简历的标题 - 内容(行) - 左 label 右 text,结构也大致如此。上述样式可实现该结构,但随着项目内容变多,需求变得繁杂:

  1. label 应自适应,可以给一个最小宽度,但在移动端如果太长能换行
  2. 行结构只有内容,没有 label。或 行结构只展示标题(粗体)没有 text,支持 icon
  3. 作为子组件时,应该统一对齐 padding

这不,html 越来越多,很多样式感觉能公共,但又不好复用。(实现开发经常遇到这类情况)与其花时间做优化,不如直接新撸来得更快

而用 Tss 这些布局-排版-文字 相关样式类名可直接写到 class 中,复用性大大增强。

html 复制代码
<div class="magic-wrapper gird-cols-3 bg-white gap-3 font-bold text-sm leading-5"></div>
<style scoped>
  .magic-wrapper {
    /* ... */
  }
</style>

这样的好处是我们通过 TW 抽离了很多公共样式,而真正额外的样式则是 scoped 里的,不会污染其他作用域,这部分样式代码会比一开始少很多。若其他人接手,看到这部分样式能更快知道改动效果。

到这里相信很多朋友会发问:

  • 这和 Bootstrap 有什么区别?

  • css 是好维护了,但 html 结构混乱得不堪入目,10 行简单结构,给你能搞出 20 行?

别急,优化的事一步步来,下个章节会结合项目实践,慢慢解释

从布局开始,更高效的响应式支持

如果一个结构细节多,需要针对更多断点情况,媒体查询的代码很更让人头疼了

Tss 实现起来非常简单快速,手敲示例代码:

wrapper 这类命名不需要,这里只为了大家能看清楚结构和上述 css 进行对比 非常建议手敲代码,能更快上手。这里实现下图片的 CSS,放心,绝对没有添加剂!

html 复制代码
<div class="page-resume lg:py-[5vh] lg:px-[5%]">
  <div class="group side-wrapper md:h-screen lg:h-[90vh] p-5 pr-[21px] hover:pr-[15px]">
    <div
      class="list-wrapper md:h-[calc(100%-262px)] overflow-hidden group-hover:oerflow-y-auto"
    >
      <div class="hero-box pr-[6px]"></div>
    </div>
  </div>
  <div
    class="main-wrapper lg:h-[90vh] overflow-hidden pr-[26px] hover:overflow-y-auto hover:pr-5"
  ></div>
</div>

vscode安装插件后上手更佳,智能提示放心写


你可以试着安装 VScode 插件 Tailwind CSS IntelliSense

看到这里如果你对 mdlg 这些名称感到似曾相识,那么文档是最终的归途

www.tailwindcss.cn/docs/respon...

TailWindCSS 核心概念

简历很简单,但这并不就是 Tss 最佳实践,还很多东西等待着我们挖掘 有了这样一个开局,如果你已经对 Tss 产生了兴趣,那么接下来的官网概念相信会容易接受得多

  • 效用第一 Utility-First Fundamentals
  • 处理状态 Handling Hover, Focus, and Other States
  • 响应式设计 Responsive Design
  • 深色模式 Dark Mode
  • 重用样式 Reusing Styles
  • 添加自定义样式 Adding Custom Styles
  • 功能和指令 Functions & Directives

效用第一

引用下文档内容:

  • 您不会浪费精力发明类名。不再需要添加像 sidebar-inner-wrapper 这样的愚蠢的类名只是为了能够设计某些东西,也不再为实际上只是一个 Flex 容器的东西的完美抽象名称而烦恼。
  • 你的 CSS 停止增长。使用传统方法,每次添加新功能时,您的 CSS 文件都会变得更大。有了实用程序,一切都可以重用,因此您很少需要编写新的 CSS。
  • 做出改变感觉更安全。 CSS 是全局性的,当你做出改变时你永远不知道你会破坏什么。 HTML 中的类是本地的,因此您可以更改它们,而不必担心其他内容会被破坏。

处理状态

Tss 包含几乎所有的修饰器,包括:

  • 伪类,例如 :hover:focus:first-child:required
  • 伪元素,例如 ::before::after::placeholder::selection
  • 媒体和功能查询,例如响应式断点、深色模式和偏好减少模式
  • 属性选择器,例如 [dir="rtl"][open]

我们平常写样式的思路是: 1. 定义 class; 2. 添加状态; 3. 添加对应样式代码

html 复制代码
<div class="container"></div>
<style>
  .container {
    background-color: black;
  }
  .container:hover {
    background-color: white;
  }
</style>

而Tss 则是编译成

css 复制代码
.hover\:bg-white:hover { 
    --tw-bg-opacity: 1; 
    background-color: rgb(255 255 255 / var(--tw-bg-opacity)); 
}

在 Tailwind 中,您不是将 hover 状态的样式添加到现有类,而是向仅在 hover 时执行某些操作的元素添加另一个类。

www.tailwindcss.cn/docs/hover-...

更多可参考文档,需要时再看就好,学一点用一点会一点。

响应式设计

上一快状态里也有响应式标记,这里就不多提,记住一点就好

  • 移动优先

默认情况下,Tss 使用移动优先断点系统,类似于您在 Bootstrap 等其他框架中可能使用的系统。 这意味着无前缀的实用程序(如 uppercase)对所有屏幕尺寸都有效,而带前缀的实用程序(如 md:uppercase)仅在指定的断点及以上位置生效。

深色模式

暂略过,核心就是通过改变状态标记,这里挖个坑吧,后续会为简历添加深色模式。敬请期待

(新增内容)快速实现主题切换

不用后续,克服困难,现在开码。

提前定义好变量,便于维护

说干就干。为了更好地使用主题模式,我建议提前定义好主题相关的变量,并为这些变量添加一个 dark 的前缀(根据实际情况自行调整)。

其实官方文档就是最好的示例。当我们要实现深色模式或者多主题时,需要考虑需要改变哪些内容?我简单总结如下:

  • 页面的大背景色(如果有的话)
  • 布局模块(如:header、main、aside、section、footer 等)的背景色
  • 模块的常态样式,如:边框(内边框、外边框还是 background-image 等)圆角等
  • 文字的颜色
  • 阴影效果
  • 其他等等...

当我们考虑到以上内容之后,再来思考状态改变时的颜色,例如 hover、focus、active 等。这只是一个思路,具体设计主题系统还需要 UI 设计同学的参与,还有许多细节值得我们深入研究。这里只是一个简单的参考。

css 复制代码
/* src/styles/var.css */
:root {
  /* theme-light */
  --page-bg: #c5dae2;
  --bg-hover: #f5f5f5;
  --text: #607d8b;
  --text-hover: #00bdea;
  --main-bg: white;
  --aside-bg: #f5f5f5;
  --scroll-piece: rgba(218, 230, 238, 0.9);
  --scroll-thumb: rgba(188, 198, 223, 0.8);
  --border: #e5e7eb;
  --focus-border: #bae6fd;
  --card-bg: #f0f9ff;

  /* dark-mode */
  --dark-page-bg: #1f2635;
  --dark-bg-hover: #151e31;
  --dark-text: rgb(145, 158, 175);
  --dark-text-hover: rgb(185, 196, 212);
  --dark-main-bg: #151e29;
  --dark-aside-bg: #131822;
  --dark-scroll-piece: rgba(18, 40, 54, 0.9);
  --dark-scroll-thumb: rgba(82, 86, 95, 0.8);
  --dark-border: rgb(13, 39, 53);
  --dark-focus-border: rgb(21, 53, 70);
  --dark-card-bg: #0a2c42;
}

我把使用到的色彩变量,都加了对应的 dark 副本,在用到这些的地方提醒自己写一个 dark:xxx 即可

你可以点开简历,查看下效果。下面以最外层wrapper为例子:

html 复制代码
<div class="w-full min-h-screen md:flex md:justify-center md:align-middle p-0 bg-[var(--page-bg)] md:py-[5vh] md:px-[5vw] dark:bg-[var(--dark-page-bg)] dark-transition">
    <section class="relative sm:w-full md:w-full max-w-[1600px] flex flex-col md:flex-row justify-center align-middle text-color shadow-inner sm:shadow-2xl dark:shadow-neutral-700">
        <!-- 内容省略 -->
    </section>
</div>

当主题为 dark 时,容器的背景色会变成 var(--dark-page-bg);section 默认是内阴影,dark主题后,会应用设定的 box-shadow shadow-neutral-700

tailwind 主题配置

根据文档,我们需要先配置 tailwind.config.js

js 复制代码
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'class',
  // ...
}
// 名称可自行改动,具体参考文档,这里不多赘述

配置好后,只要我们在 html 标签上添加 class="dark" 并为要改变的元素上按下面的方式增加对应类名即可

html 复制代码
<!-- Dark mode enabled -->
<html class="dark">
  <body>
    <!-- Will be black -->
    <div class="bg-white dark:bg-black">
      <!-- ... -->
    </div>
  </body>
</html>

注意:我们的页面是 create(App).mount('#app') 为了实现上述对 html class 的添加,我们还需要进行额外的 DOM 操作

我添加了一个 PageTool / ThemeChange (小项目其实用不上状态管理,这里只为展示Pinia用法请勿吐槽 )

js 复制代码
import { defineStore } from 'pinia'
export const useCommonStore = defineStore('common', {
  state: () => ({
    theme: 'light',
  }),
  getters: {
    isDark: (state) => state.theme === 'dark',
  },
  actions: {
    switchTheme() {
      if (!this.isDark) {
        this.theme = 'dark'
        document.documentElement.classList.add('dark')
      } else {
        this.theme = 'light'
        document.documentElement.classList.remove('dark')
      }
    },
  },
})

现在我们绑定好方法就可以快速切换主题了

html 复制代码
<div class="switch-wrap" @click="handleSwitch">
  <Iconfont v-show="isDark" name="night-mode-fill" />
  <Iconfont v-show="!isDark" name="light-bulb" />
</div>
<script setup lang="ts">
  import { toRefs } from 'vue'
  import { useCommonStore } from '../../../store/common'
  import Iconfont from '../../Iconfont/Iconfont.vue'
  const store = useCommonStore()
  const { isDark } = toRefs(store)
  const handleSwitch = () => {
    store.switchTheme()
  }
</script>

综上所述,这个简历项目,在 App.vue 的结构清晰明了,只需要在 wrapper、main、aside 等地方填入相应的 dark:xxxx 即可,比如 <main class="... dark:bg-[var(--dark-main-bg)] ">

优化 - 添加平滑过渡

这部分代码可能会变得很冗长,慢慢地,tailwind 的这一缺点就凸显出来了。不过这并非大问题,通过使用 @layer,我们可以将这些公共样式抽离出来,这是我在项目开发过程中一点点进行优化的成果。

感兴趣可点击切换,试下效果。会了黑白主题,再添加其他自定义主题不在话下。

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
  .dark-transition {
    @apply transition-colors duration-1000 ease-in-out;
  }
}

这样再切换,我们的主题就拥有良好的过渡效果了。

重用样式

上文提到实现 Hobby 模块的 movie 效果时,对 item 写了一堆样式。

html 复制代码
<div class="flex flex-wrap justify-content">
  <div class="group overflow-hidden relative ring-1 ring-slate-200 rounded flex items-center justify-center size-12 mr-3 mb-3 origin-center align-center">
    <!-- ... -->
  </div>
  <div class="group overflow-hidden relative ring-1 ring-slate-200 rounded flex items-center justify-center size-12 mr-3 mb-3 origin-center align-center">
    <!-- ... -->
  </div>
  <div class="group overflow-hidden relative ring-1 ring-slate-200 rounded flex items-center justify-center size-12 mr-3 mb-3 origin-center align-center">
    <!-- ... -->
  </div>
  <!-- 很多重复结构 -->
</div>

在项目中有 8 个 item,需要将相同的东西复制八遍吗?起初可能是这样,尽管看起来有些辣眼睛,但至少功能已经实现了。无意中翻看文档时,我发现了以下的信息:

很多时候,类似这样的重复甚至不是真正的问题,因为它们通常都可以集中在一个地方,甚至实际上根本不存在,因为您正在迭代一组项目并且只编写一次标记。如果您只需要在单个文件中重用需要重用的样式,那么多光标编辑和循环是管理任何重复内容的最简单方法。 ------ 官方文档

好的,给我我笑了 ... 于是继续编码。 额,额 ...... 当然不止是这样,我们有几种方式来优化

*: 选择器

html 复制代码
<div class="parent-comp flex flex-wrap justify-start *:overflow-hidden *:relative *:ring-1 *:ring-slate-200 *:rounded *:flex *:items-center *:justify-center *:size-12 *:mr-3 *:mb-3">
  <div class="child-comp"></div>
  <div class="child-comp"></div>
  <div class="child-comp"></div>
  <div class="child-comp"></div>
  <div class="child-comp"></div>
</div>

使用@apply 提取类

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
  .hobby-item {
    @apply overflow-hidden relative ring-1 ring-slate-200 rounded flex items-center justify-center size-12 mr-3 mb-3 origin-center align-cente;
  }
}

笔者也只是初窥门径,还有更多用法学习,尽在官方文档(再次强调)

添加自定义样式 Adding Custom Styles

如果您想更改调色板、间距比例、排版比例或断点等内容,请将自定义添加到 tailwind.config.js 文件的主题部分:

js 复制代码
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{ts,tsx,vue}'],
  theme: {
    container: {
      padding: '1rem',
    },
    screens: {
      sm: '640px',
      md: '1024px',
      lg: '1376px',
      xl: '1920px',
    },
  },
}

项目中如何优化 Tss 代码

我新建了一个 styles/tailwind.css 专门存放 Tss 相关代码

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
  .hobby-fromBottom {
    @apply translate-y-full transition-transform ease-in-out duration-500 group-hover:translate-y-0;
  }
  .hobby-fromTop {
    @apply -translate-y-full transition-transform ease-in-out duration-500 group-hover:translate-y-0;
  }
  .hobby-fromLeft {
    @apply -translate-x-full transition-transform ease-in-out duration-500 group-hover:translate-x-0;
  }
  .hobby-fromRight {
    @apply translate-x-full transition-transform ease-in-out duration-500 group-hover:translate-x-0;
  }
  .hobby-fadeIn {
    @apply transition-opacity ease-in-out duration-500 opacity-0 group-hover:opacity-100;
  }
}

结构简洁多了,把布局颜色等样式代码单独写 css,过渡相关样式写到了单独的文件中便于维护。

需要注意的是:hobby-fromBottom 这种用法在打包后会有编译开销,实际我们只用到一次的话没必要这样使用。这里只是方便展示

html 复制代码
<div
  class="absolute w-full h-full p-2 z-10 left-0 right-0 top-0 bottom-0 rounded bg-sky-50"
  :class="{
    'hobby-fromBottom': transitionType === 'fromBottom',
    'hobby-fromTop': transitionType === 'fromTop',
    'hobby-fromLeft': transitionType === 'fromLeft',
    'hobby-fromRight': transitionType === 'fromRight',
    'hobby-fadeIn': transitionType === 'fadeIn',
  }"
>
  <slot></slot>
</div>

至此已经可以回答之前的疑问了,和 Bootstrap 像吗,是的。但不仅仅是这些,Tss 不仅可定制样式,而且更加灵活,而库体积却更小。

功能和指令 Functions & Directives

Directives

指令是自定义 Tss 特定的规则,您可以在 CSS 中使用,为项目提供特殊功能。

css 复制代码
/**
 * 这会注入 Tailwind 的基本样式以及插件
 */
@tailwind base;
/**
 * 这会注入 Tailwind 的组件类和任何组件类通过插件注册
 */
@tailwind components;
/**
 * 这会注入 Tailwind 的实用程序类和注册的任何实用程序类通过插件
 */
@tailwind utilities;
/**
 * 使用此指令来控制 Tailwind 注入悬停、焦点、响应式、深色模式以及其他变体
 * 如果省略,Tailwind 会将这些类附加到默认情况下您的样式表
 */
@tailwind variants;

Tss 速记指南

一个新的东西需要学习成本。看到这的你,我相信已接受 Tss,并想投入使用了。这里结合笔者的经验,整理了一些速成技巧

编写 Tss 建议遵循着以下思路和顺序来:

  • 布局
  • 间隔 margin、padding
  • 背景
  • 边框
  • 尺寸 width、height
  • 排版(文字相关)
  • 过渡、动画
  • 其他交互
    • cursor
    • hover

这里以组件 TitleDesc.vue 为例。简历项目中在 ProjectWrap 用到来展示标题 + 内容,结构如下:

html 复制代码
<template>
  <div class="flex flex-between flex-nowrap border-b-2 my-3 py-2 gap-x-5 text-lg">
    <div class="text-justify">
      <Iconfont v-if="showIcon" class="align-middle"></Iconfont>
      <span class="ml-1 font-bold align-middle text-[#333]">{{ title }}</span>
    </div>
    <div>
      <slot name="extra"></slot>
    </div>
  </div>
  <div class="bg-gray pl-3 text-base text-[#777]">{{ description }}</div>
</template>

思路有了一切都很简单,Tss 很多 Class 和原生缩写一样,很容易记,看一遍基本就会了

这里整理一些常用 和 容易错误的:

布局相关

display

Class Properties
block display: block;
hidden display: none;
inline-block display: inline-block;
inline display: inline;
flex display: flex;
grid display: grid;

container 在当前断点下都处于 width: 100%

gap 是真的好用,前提是在 flex 或 grid 下生效

overflow

Class Properties
overflow-auto overflow: auto;
overflow-hidden overflow: hidden;
overflow-clip overflow: clip;
overflow-visible overflow: visible;
overflow-scroll overflow: scroll;

position

Class Properties
static position: static;
fixed position: fixed;
absolute position: absolute;
relative position: relative;
sticky position: sticky;

size 尺寸这里单独说一下

Tss 里的基本单位 1rem = 16px 这点非常重要,请记住。对于尺寸,1 TSS 标准单位大小 = 4px (0.25rem)

后面跟的 left-1、size-10 看到这种默默乘 4 就可以换算出 left-1(left:4px)、w-8(width: 32px)、mb-4(margin-bottom: 16px)

Class Properties
m-0 margin: 0;
w-px width: 1px;
mx-auto margin-left: auto;margin-right:auto;
py-auto padding-top: auto;padding-bottom:auto
h-full height: 100%;
left-1/2 left: 50%;

排版

  • 0 就是 0px
  • px 一般都是 1px
  • full 表示 100%
  • x 横坐标,如mx就是 margin-left、margin-right
  • y 纵坐标,同上 py 就是 padding-top、padding-bottom
  • 加入 1/3 这样的,表示百分比,具体看文档
  • 若想精确的,可以自定义 -[x],如 w-[66px]h-[calc(100vh-50px)

背景

Tss 的写这类复合样式,都是要拆开写的,虽麻烦,但本质上也算是解耦嘛

后续 flex、border、transition 这类属性也类似,我们只要知道大致 value 即可,这么一想也挺好,对 CSS 越熟悉就越容易上手 Tss。

background 本身是 background-color、background-image、background-position、background-repeat、background-size 多个属性的缩写。

Class Properties Desc
bg-none background-image: none; -
bg-white background-color: white; 颜色
bg-center background-position: center; 位置
bg-cover background-size: cover; 尺寸
bg-contain background-size: contain; 尺寸
bg-no-repeat background-repeat: no-repeat; 重复

用这类属性,需要根据 bg-[效果] 带来的实际效果提炼关键点。这么想就容易上手了

Transform

常用的都在这里,知道这几个即可:

  • translate 同 left right ,直接写
  • scale 同 opacity,需注意 Tss 里的像百分比的值都乘了 100
  • rotate 官方只制定了几个常用的,这类自己写就好,如rotate-[46deg]
  • skew 同上
  • origin origin-center为 transform-origin: center;

transform-gpu 就是面试中常考的的开启 gpu 加速

但没看到对 translate-z 的支持,perspective只能手动添加了 若有错误麻烦指正下,谢谢

来记点不一样的

上面说了关键点在于应用 CSS 带来的实际效果。再来看下面这些,你就会容易理解得多:

Class Properties Desc
bg-white background-color: white; 颜色
text-base font-size: 1rem; line-height: 1.5rem; 字体和行高
truncate overflow、text-ellipsis、white-space 三剑客文字省略你懂的
italic font-style: italic; 斜体
font-bold font-weight: 700; 加粗
leading-5 line-height: 20px; 行高
text-center text-align: center; 文字居中
align-middle vertical-align: middle; 内联垂直对齐
rounded border-radius: 4px; -
divide-x > + border-right-width: 0px; border-left-width: 1px; 不用单独写边框了
divide-y-2 > + border-top-width: 2px; border-bottom-width: 0px; 不用单独写边框了
outline-1 outline-width: 1px; 外边框,这几种根据实际需要选择即可
ring-1 太多省略 本质为 box-shadow 模拟的边框
opacity-0 opacity: 0; 透明
opacity-100 opacity: 1; 不透明
visible visibility: visible; 典,虽隐藏了但保留 DOM 位置,影响回流
invisible visibility: hidden; 本质为 box-shadow 模拟的边框
cursor-pointer cursor: pointer; 聚焦
cursor-not-allowed cursor: not-allowed; 禁止
pointer-events-none pointer-events: none; 阻止浏览器事件
select-none user-select: none; 禁止选中

注:用 text 千万要注意line-height,虽然很想吐槽 明明是 font-size ,但整个 text 很异类. 如果只想使用 font-size 的话,直接 text-[14px] 这样就好了

上述这些浏览一遍就可快速上手,遇到不会的再查文档就是。

学到的 CSS 新特性

1. display: flow-root;

flow-root 该元素生成一个块级元素盒,其会建立一个新的块级格式化上下文,定义格式化上下文的根元素。

IE8 启动 ...... 额,不是 还在用 clear 清楚浮动嘛? float 毕竟是针对内联图文的,

如果想让之后的元素不受到浮动影响,可以直接新建一个元素,并使用该属性

2. grid 布局快速上手

四列布局是吧? 来了,瀑布流?不在话下

html 复制代码
<div class="grid grid-cols-4 gap-4">
  <div>01</div>
  <!-- ... -->
  <div>09</div>
</div>

更多参考:www.tailwindcss.cn/docs/grid-t...

3. scroll-behavior: smooth;

论回到顶部的四种写法:

  • jQuery.animate({scrollTop:0})
  • window.scrollTo(0, 0)
  • document.body.scrollTop = 0

如果想丝滑滚动还要写一堆 js 代码,防抖啊,requestAnimationFrame 又给整出来了,现在不要 998,只需一句 css scroll-behavior: smooth,结合 <a>锚点即可实现,浏览器更懂浏览器。

此时一位路过的朋友提出兼容 IE8(大哥现在 2024 React 都 18 了,vue 都 3 了,全民 AI 时代您还搁这 IE。抗击旧世界残党我辈义不容辞)掘金内外又瞬间充满了快活的空气

4. caret-color

用于控制文本输入光标颜色 caret-color: orange;

html 复制代码
<input class="caret-orange-200 ..." />

5. background-clip

设置元素的背景(背景图片或颜色)是否延伸到边框、内边距盒子、内容盒子下面,在这里,头像下的文字就是 background-clip: textlinear-gradient 实现的。

更多可看文档 developer.mozilla.org/zh-CN/docs/...

6. will-change

用于优化即将发生变化的元素的动画。官方建议慎用,在明确 transition perproties 的情况下可以提升性能

7. postcss

我发现两者结合已能满足大部分功能和场景,什么 sass(安装就像买彩票) scss、stylus 我都不需要了

8. 更多选择器和修饰符

再帖一遍文档,真的很好用

www.tailwindcss.cn/docs/hover-...

一个好的优化原则就是,能用 css 的尽量用 css 实现。 合理选择器和好处在于 它的权重和 class 都是相同的,知道这点,就可以让我们可以少写很多没必要的高权重选择器

9. 了解到 WAI-ARIA

感兴趣可看我之前的一篇文,如果需要,可以向标签添加 data 和 aria 属性,结合 role,提供更好的用户可访问性。

blog.fridolph.top/2024/01/26/...

还有更多等待发掘

HTML、CSS,简单和立即反馈,通过这次学习感觉找回了初学前端时的快乐,这算是一个意外之喜吧。 在写博客时,也发现一些可尝试的新东西,预计就随便写写 2000 字不能再多,没想到码了这么多字

这篇文章真是从 2000 字 -> 想着扩展点吧写到 5000 字 -> 好人做到底完善到 1W 字 ~ 骄傲,原来我不是文废,语文老师可以放心了!

总结

考虑到这一章节,这实质上也是对过往经验的一种总结。在我早期的项目开发中,往往采用一种即兴的方式编写 css 和模板,导致组件粒度不够细,复用性不高,同时也存在一定程度的未提取冗余样式的问题。随着项目复杂度的增加,势必出现一些无法解决的样式问题,不得不通过全局样式甚至使用"important"来进行覆盖。

随着对 Tss 的学习,加上最近也是在复习,想到就会尝试添加新的特性,不知不觉 2 天搞定的项目断断续续码了十多天,但这一过程非常享受,我重新找回了对编码的好奇心和热情。

关于技术选型

  • 本文不是说服你去用Tss,而是多一个了解,多一个待选方案

  • 项目中添加 TSS 有一定的学习成本

刷到些评论,感觉更多的困难是能否让同事接受 TSS 的核心概念。刷文档做 demo 实际 1 天即可上手。还有另一好处是让平时不太关注 CSS 的小伙伴,(不考虑兼容性情况下)能用到一些新特性。

  • 对于灵活性的取舍

我理解熟悉 TSS 带来的高效开发和其带来的 html 臃肿问题,@apply(tailwind.config.js) 如何使用决定了我们需要牺牲怎样的灵活性以换来更高的可维护性

  • 代码体积小,灵活度高,推荐一试

从下面可看到打包后 css 代码部分很小(只编译打包了我们用到的部分)这也是与其他写好 .xs .md .m-1 .m-2 .p-b-1 .p-t-2 样式的 UI 的不同之处。

个人总结的一些开发实践

  1. 对于入口文件 app.vue,应确保其作为容器组件,尽量减少业务逻辑的耦合。

  2. 在划分组件和视图时,要遵循componentsviews 的原则,以减小粒度并分离业务逻辑。

  3. 善用 hooks,尤其在项目中逻辑较为简单时,例:我抽了个 useLocale hook,供参考。

  4. 在划分视图时,要保持与入口的清晰一致, 如App.vue 它会为你带来意想不到的好处。

  5. 在组件开发时不要刻意进行优化。我的建议是,待一个大的 page/view 开发结束后再考虑优化的事情

过早的优化属于浪费精力。但如果优化过晚,特别是在多人合作时,一些未提取的模板可能会被他人复制,然后进行修改,一旦变得不可控,进行优化就会需要更多的精力。

  1. 布局、文字等细节不应等到 UI 设计确定后再考虑,而应从决定支持响应式、i18n 时开始考虑。

我前公司的项目中,基本上都需要支持国际化。对于布局,务必要考虑文本所带来的影响,例如文字不固定长度,中文切换到英文长度变化带来的影响等,这些细节可以尽早地写入到 common.css 中作为公共样式。

  1. 过渡和动画可以起到画龙点睛的作用,但如果使用,尽量多尝试不同的 cubic-bezier。

在可控的情况下,要减少 transition-all 的使用,而以 transition-opacity、transition-transform 为例。否则,这与 TypeScript 中使用 any 有何区别呢?

我们的项目很简单,在实际开发中远不止这些,推荐参考 dev.to 、MDN 等

终于上线啦!别高兴太早

网站既然都放到了生产环境,F12 是常有的事 ... 那有必要 print 秀一波 console.log 了,啊,不是!

小红红:停停停!兄弟,你是来捣乱的嘛?

性能才是我们要关注的重点好吧。先来一波分析 performance. 这是我自己网站的加载情况

实际可自行 performance API 或运用 lighthouse 等工具。这里就简单示范下

有点搞笑?!就这么少的文件,只有一个单页,怎么会加载得如此缓慢呢?代码写出来了,也在本地运行了,但放到生产环境后才发现问题如此突出。

没错,你也许以为这个项目已经完成了,但实际上,这只是一个开始。还有很长的路要走,兄弟们,我们的任务尚且艰巨。

构建优化

也是为了面试,正好实践一下 = = 以前也没配过这些,vue-cli 直接搞定,反正需要啥用啥,直接看文档即可 cn.vitejs.dev/.

大方向嘛无外乎两点:减少构建和打包耗时,减小打包资源体积

  1. vite-plugin-compression

这点 vite 默认配置就有了,不用担心。这里我加了一个插件 compression,它的作用是打包压缩且生成 gzip,感兴趣的小伙伴自行文档

  1. vite-plugin-chunk-split

Vite 代码拆包插件。支持多种拆包策略,可避免手动操作 manualChunks 潜在的循环依赖问题。

js 复制代码
export default defineConfig({
  plugins: [
    vue(),
    compression(),
    chunkSplitPlugin({
      strategy: 'default',
      customSplitting: {
        // vue相关会被打包到一个名为`render-vendor`的 chunk 里面(包括它们的一些依赖,如 object-assign)
        'vue-vendor': [/vue/, /pinia/],
        'tailwind-vendor': [/tailwindcss/],
        'fri-vendor': [/fri-element-plus/],
        // 源码中目录的代码都会打包进 [name] 这个 chunk 中
        styles: [/src\/styles/],
        locales: [/src\/locales/],
      },
    }),
  ],
})

原来是之前的练手项目导致了这些问题。动态导入却没有实现只导入所需的组件和样式,导致页面大小异常庞大。(若有时间,再优化这项目吧,这里直接去掉了) 实现代码拆分(split-chunks),来减少页面加载体积。

对于使用开源 UI 库的情况,大多数都会支持这样的特性,比如 ElementPlusResolver 等,以此来解决该问题

  1. vite-plugin-importus

这里假装已经优化了:囧 (如果 UI 库支持,可实现按需加载)

js 复制代码
export default defineConfig({
  plugins: [
    // ...
    vitePluginImportus([
      {
        libraryName: 'ElementPlus',
        libraryDirectory: 'es',
        style: 'css',
      },
    ]),
  ],
})

其实你还可以考虑,在你的项目中并不一定需要那么大而全的 UI 或工具库。既然已经开始着手进行优化,那就一丝不苟地将这些练手用的 UI 库移除吧。 同时,在此时可以思考一下,这些库、UI 组件等是否真的是你项目中所必需的,是否存在其他更优的替代方案,是否支持按需加载等功能。 我又开始进行一番调整,将这些添加的"玩具"组件库一一移除了。

  1. Vite-plugin-bundle-prefetch
  • 获取所有包含 assetDir 的包
  • 过滤目标文件(rm .map 文件或忽略旧版本)
  • 获取最终输出的 html 文件内容
  • 在头部附加 <link rel="prefetch" href="xxxx">

通过 prefetch 也能尽早渲染页面,从而达到优化的效果

  1. 路由按需加载 (这里没用到就略过了)

构建上还有很多,主要是根据自身实际需求来就好。适合的就是最好的

优化后快多了不是,但还没完,别急,马上就好了,虽然我们打包了 gz 后缀,但其实还没生效呢。

请求优化

由于近期正在找工作,我发现简历内容可能会需要频繁更新。为了确保浏览器不会缓存旧的信息,我采用以下缓存策略:(仅供参考,可根据你的情况调整)

  • index.html 不使用任何缓存策略,因为文件体积本身很小
  • js、css、pdf、md 等可能频繁变动的文件,缓存 1 天
  • svg、jpg、pdf 和字体文件等不会经常更改的文件,缓存 7 天
  • 同时,我也开启了 gzip 压缩功能。

下面是我的 nginx 配置,供大家参考:

bash 复制代码
server {
  server_name resume.fridolph.top;
  # ssl和证书相关配置省略了

  location / {
    index index.html;
    add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
  }

  location ~* ^.+\.(jpg|png|svg|ico|woff|woff2)$ {
    log_not_found off; # 关闭日志
    expires 7d; # 缓存时间7天
    add_header Cache-Control  max-age=604800;
  }

  location ~* ^.+\.(css|js|md|pdf|)$ {
    expires 1d; # 可能会频繁变动的资源只缓存1天
    add_header Cache-Control  max-age=86400;
  }

  # 开启gzip
  gzip  on;
  # 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
  gzip_min_length 1k;
  # gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间。一般设置1和2
  gzip_comp_level 2;
  # 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
  gzip_types text/plain application/javascript application/x-javascript text/css application/xml image/jpeg image/png;
  # 是否在http header中添加Vary: Accept-Encoding,建议开启
  gzip_vary on;
}

优化暂时搞定,秒开!

总体来看,作为一个"简单"的项目,我相信它应该达标了吧!呜呜呜,简历兄你完成了自己的使命,一路走好。

当然,优化远不止这么简单,这里只例举了一些常见方式,可根据需要将这些优化技巧运用到你的项目中。

再强调一下 Tss 四大核心概念:

  • 效用第一
  • 处理状态
  • 响应式设计
  • 重用样式

这才是我们在项目中使用的目的,tss 只是其中一个手段。感谢您看到这里。文章内容基本结束,本文只是引子,Tss 本身不仅仅这些内容。若通过本文能引起你的兴趣并学习这也是我的荣幸。

写在最后

实践才是最好的老师,通过这段时间的实践,通过不断地思考和优化项目结构、组件,以及功能的实现。尽管这只是一个简单的项目,但我已经不再是简单的复制粘贴,而是把它当成一件新鲜的事物,用来检验自己的学习成果。我发现 Tss 非常适合模块化开发,随着熟练度提高用起来也越来越顺手。

如果有同学不需要国际化的版本,可以切换到 github.com/Fridolph/my... 分支的dev/无国际化看这分支。但这个分支比较早,缺少很多细节和后续的优化,再贴一下项目地址:

如果您觉得这对您有所帮助,求个 Star 不过分吧! 如果有不足之处或者观点不一致的地方,请多批评指教,谢谢。

本来想拆成几篇来写,感觉没必要。一步到位,您的点赞对我来说将是最大的认可。支持一下"萌新"吧!在春节期间写文章确实不容易,感谢大家的关注和支持。

恭祝大家工作顺利,平安健康,万事如意,一帆风顺!和我一样没工作的小伙伴在新的一年能找到自己满意的工作,生活不易,共勉之!

相关推荐
百万蹄蹄向前冲40 分钟前
2024不一样的VUE3期末考查
前端·javascript·程序员
轻口味1 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami1 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript
emoji1111112 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼2 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250032 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter