Skip to content

样式布局

样式与组件分离

Teek 并没有和普通的 Vue 项目一样,使用如下模板进行编写组件:

vue
<scriptsetuplang="ts"></script><template><!-- 组件模板 --></template><stylelang="scss"scoped></style>

而是使用:

vue
<scriptsetuplang="ts"></script><template><!-- 组件模板 --></template>

那么组件样式去哪里了呢?

Teek 专门创建 theme-chalk/src/components目录用于存放组件样式,然后将所有的组件样式汇总到一个 index.scss(入口样式文件),最后在 index.ts(入口运行文件)分别引入 theme-chalk/index.scss(入口样式文件)和 layout/index.vue(入口组件)。

*.vue ——>Layout/index.vue ——>index.ts <—— theme-chalk/index.scss <—— *.scss多个功能组件 ——>入口组件 ——>入口运行文件 <—— 入口样式文件 <—— 多个功能组件样式

提示

这也就是为什么在 .vitepress/theme/index.ts里单独引入 Teek 样式的原因。

样式结构设计

Teek 的样式结构设计遵循单一原则:每个样式文件只负责渲染单独的模块(组件),如:

  • nav-var.scss只提供导航栏的 css var变量
  • nav-search-button.scss只渲染导航栏的搜索按钮,内部引用了 nav-var.scss
  • nav-switch-button.scss只渲染导航栏的深色、浅色切换按钮,内部引用了 nav-var.scss
  • nav-translation.scss只渲染导航栏的国际化下拉框,内部引用了 nav-var.scss

这样的好处是,不同的样式之间是独立的,需要根据自己的需求按需引入对应的样式文件,不会存在引入一个内容超大的样式文件,导致不想要的元素也发生了样式改变。

当然,对于不喜欢折腾、研究的小伙伴来说,Teek 也提供了 nav.scss样式文件,该文件内部没有编写任何样式代码,而是引入了 nav-xxx.scss等样式文件,用于快速引入 nav的所有样式文件。

目录结构

下面给出 Teek 的样式目录结构:

sh
theme-chalk/src.├─base.scss# 基础样式文件├─index.scss# 入口样式文件├─common# 通用样式目录├─components# 组件样式目录,对应每个 Vue 组件├─md-plugin# Markdown 插件样式目录├─mixins# 样式混入目录├─module# 模块样式目录├─var# 样式变量目录└─vp-plus# VitePress 样式加强目录

components目录下,Teek 会给每个组件生成对应的样式文件,文件名与组件名相近(字母小写 + -分割来命名)。

index.scss文件是入口样式文件,它将导入 base.scssvar目录下的样式文件,以及 components目录下所有组件的样式文件。

如果需要按需加载组件,您必须要引入 base.scss文件,这是 Teek 的核心主题样式文件,然后按需引入 Vue 组件和 components目录下的对应组件的样式文件。

命名空间

Teek 并没有简单的直接给一个 div元素添加 class="button",然后在 CSS 里 .button {}定义样式,Teek 使用 命名空间的设计思想,给每个组件添加唯一标识,确保不同组件的相同 class发生样式冲突。

提示

命名空间等价于 Vue 组件 stylescoped属性。

SCSS 定义命名空间

命名空间其实是一个唯一的前缀,如 ElementPlus 的命名空间为 el,在某个 class中添加 el-前缀,如 <div class="el-button"></div>

命名空间应该是一个变量,这样只需要修改该变量的值,那么所有的 class以及样式都不会失效,如在 ElementPlus 的命名空间文件里修改 eltk,那么所有的 class都变为 tk开头,且样式不会失效。

提示

Teek 的命名空间为 tk

首先 Teek 在 theme-chalk/mixins/config.scss文件中定义了命名空间变量 $namespace

scss
$namespace:"tk"!default;

此时其他的 SCSS 文件都需要引入该文件,然后使用 $namespace变量:

scss
@use"../mixins/config";.#{$namespace}-button{}

当然这只是简单的 Demo,实际的使用请看 样式文件使用 BEM

JS/TS 使用命名空间

定义命名空间中通过 $namespace定义了命名空间,那么 Vue 组件里的 template元素如何使用呢?总不能直接写 <div class="tk-button"></div>,一旦这样,修改 $namespace的值,那么所有的 class都会失效,因此需要想办法直接在 template使用 $namespace变量。

通过 SCSS Module API 可以将 $namespace变量导出到 jsts文件里,在 theme-chalk/module/namespace.module.scss文件导出 $namespace

scss
@use"../mixins/config"as*;:export {namespace:#{$namespace};}

信息

SCSS Module API 只对 .module.scss结尾的文件提供变量暴露功能。

如果是 Typescript 环境使用,则还需要在同级目录下定义一个 namespace.module.scss.d.ts文件:

ts
exportinterfaceScssVariables{[x:string]:unknown;namespace:string;}exportletvariables:ScssVariables;exportdefaultvariables;

最后在 Vue 组件引入 namespace.module.scss

vue
<scriptsetuplang="ts">importnamespaceModule from"../theme-chalk/module/namespace.module.scss";</script><template><div:class="`${namespaceModule}-button`"></div></template>

当然这只是简单的 Demo,实际的使用请看 组件元素使用 BEM

提示

$namespace:tk改为 $namespace:xx(xx 为你的项目名/框架名),那么没人知道它是 teek,它已经完全属于你。

什么是 BEM

Teek 使用 BEM 规范进行样式编写,并使用 SCSS 进行样式编写。

BEM 是一种前端开发方法论,全称是 Block Element Modifier(块、元素、修饰符)。它提供了一种命名约定,用于组织和管理 CSS 类名,从而提高代码的可维护性、可扩展性和复用性。

  • Block(块)

    • 独立的功能模块,可以独立存在
    • 示例:buttonmenuinput
  • Element(元素)

    • 属于某个 Block 的一部分,不能单独存在
    • 使用双下划线 __连接 Block 和 Element
    • 示例:menu__itembutton__text
  • Modifier(修饰符)

    • 用于改变 Block 或 Element 的外观或行为
    • 使用双横线 --表示
    • 示例:button--largemenu__item--active

BEM 方法的引入主要是为了解决传统 CSS 开发中常见的问题,尤其是在大型项目或团队协作中,这些问题会变得更加突出。以下是使用 BEM 的主要原因以及它解决的痛点:

  • 样式冲突:在传统的 CSS 开发中,类名可能会重复或不够具体,导致样式冲突。例如,多个开发者可能都定义了一个名为 button的样式,但它们的行为和外观完全不同
  • 可维护性差:随着项目的增长,CSS 文件变得越来越复杂,难以找到特定样式的定义位置,或者修改一个样式时意外影响到其他部分
  • 样式复用困难:在没有明确规范的情况下,开发者可能需要重复编写类似的样式代码,增加了冗余
  • 团队协作困难:在多人协作的项目中,不同开发者可能采用不同的命名习惯,导致代码风格不一致,难以统一管理
  • 样式与结构分离不清晰:在某些情况下,开发者可能直接通过 HTML 结构(如标签选择器、后代选择器)来定义样式,这会导致样式与结构紧密耦合,难以迁移或重构
  • 缺乏扩展性:当需要对现有样式进行扩展或修改时,可能会因为复杂的嵌套关系或不清晰的命名规则而感到困难

BEM 命名规则

  • Block:blockName
  • Block + Element:blockName__elementName
  • Block + Modifier:blockName--modifierName
  • Element + Modifier:blockName__elementName--modifierName
html
<divclass="button"><spanclass="button__text">文字按钮</span><buttonclass="button--large">large 按钮</button><spanclass="button__text--bold">文字加粗按钮</span></div>
css
.button{}.button__text{}.button--large{}.button__text--bold{}

组件元素使用 BEM

定义一个 Hooks 文件 useNamespace.ts来封装命名空间和 BEM 规范,命名空间BEM 规范在上文已经介绍过了。

在组件中引入 useNamespace.ts文件,使用命名空间 + BEM 规范来编写 class,如:

vue
<scriptsetuplang="ts"name="BEMDemo">import{useNamespace } from"../../../composables/useNamespace";constns=useNamespace("button");</script><template><div:class="ns.b()"><span:class="ns.e('text')">文字按钮</span><button:class="ns.m('large')">large 按钮</button><span:class="ns.em('text','bold')">文字加粗按钮</span><button:class="['button',ns.is('primary')]">primary 按钮</button></div></template>

等于:

vue
<scriptsetuplang="ts"name="BEMDemo"></script><template><divclass="tk-button"><spanclass="tk-button__text">文字按钮</span><buttonclass="tk-button--large">large 按钮</button><spanclass="tk-button__text--bold">文字加粗按钮</span><buttonclass="button is-primary">primary 按钮</button></div></template>

具体使用请看 Teek 的组件源码。

样式文件使用 BEM

定义 SCSS 文件 bem.scss来封装命名空间和 BEM 规范,

在样式文件引入 bem.scss文件,使用 bem.scss文件提供的 mixins来编写样式,如:

scss
@use"../mixins/bem"as*;@includeb("button") {@includee("text") {@includem("bold") {}}@includem("large") {}.button{@includewhen("primary") {}}}

等于:

scss
.tk-button{.tk-button__text{&--bold{}}.tk-button--large{}.button{&.is-primary{}}}

具体使用请看 Teek 的样式源码。

CSS Var 变量使用命名空间

Teek 给所有的 CSS Var 变量都添加命名空间,如:

css
--tk-text-color-secondary:#86909c;

为了共用 $namespace变量,所以 Teek 提供了 set-css-varMixin 和 getCss-Var函数来进行封装:

scss
@use"./function"as*;@mixinset-css-var($name,$value) {#{joinVarName($name)}:#{$value};}
scss
$namespace:"tk"!default;@functionjoinVarName($list) {$name:"--"+config.$namespace;@each$itemin$list{@if$item!=""{$name:$name+"-"+$item;}}@return$name;}@functiongetCssVar($args...) {@returnvar(#{joinVarName($args)});}

使用 set-css-var来定义 CSS Var 变量:

scss
@use"../mixins/mixins"as*;:root{@includeset-css-var("button-width",84px);@includeset-css-var("button-height",32px);@includeset-css-var("button-color",#3451b2);@includeset-css-var("button-font-size",16px);}

等于

scss
:root{--tk-button-width:84px;--tk-button-height:32px;--tk-button-color:#3451b2;--tk-button-font-size:16px;}

使用 CSS Var 变量:

scss
@use"../mixins/function"as*;.demo{width:getCssVar("button-width");height:getCssVar("button-height");color:getCssVar("button-color");font-size:getCssVar("button-font-size");}

等于

scss
.demo{width:var(--tk-button-width);height:var(--tk-button-height);color:var(--tk-button-color);font-size:var(--tk-button-font-size);}

具体使用请看 Teek 的 CSS Var 源码。

最近更新