样式布局
样式与组件分离
Teek 并没有和普通的 Vue 项目一样,使用如下模板进行编写组件:
<scriptsetuplang="ts"></script><template><!-- 组件模板 --></template><stylelang="scss"scoped></style>
而是使用:
<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 的样式目录结构:
theme-chalk/src.├─base.scss# 基础样式文件├─index.scss# 入口样式文件│├─common# 通用样式目录├─components# 组件样式目录,对应每个 Vue 组件├─md-plugin# Markdown 插件样式目录├─mixins# 样式混入目录├─module# 模块样式目录├─var# 样式变量目录└─vp-plus# VitePress 样式加强目录
在 components
目录下,Teek 会给每个组件生成对应的样式文件,文件名与组件名相近(字母小写 + -
分割来命名)。
index.scss
文件是入口样式文件,它将导入 base.scss
、var
目录下的样式文件,以及 components
目录下所有组件的样式文件。
如果需要按需加载组件,您必须要引入 base.scss
文件,这是 Teek 的核心主题样式文件,然后按需引入 Vue 组件和 components
目录下的对应组件的样式文件。
命名空间
Teek 并没有简单的直接给一个 div
元素添加 class="button"
,然后在 CSS 里 .button {}
定义样式,Teek 使用 命名空间的设计思想,给每个组件添加唯一标识,确保不同组件的相同 class
发生样式冲突。
提示
命名空间等价于 Vue 组件 style
的 scoped
属性。
SCSS 定义命名空间
命名空间其实是一个唯一的前缀,如 ElementPlus 的命名空间为 el
,在某个 class
中添加 el-
前缀,如 <div class="el-button"></div>
命名空间应该是一个变量,这样只需要修改该变量的值,那么所有的 class
以及样式都不会失效,如在 ElementPlus 的命名空间文件里修改 el
为 tk
,那么所有的 class
都变为 tk
开头,且样式不会失效。
提示
Teek 的命名空间为 tk
。
首先 Teek 在 theme-chalk/mixins/config.scss
文件中定义了命名空间变量 $namespace
:
$namespace:"tk"!default;
此时其他的 SCSS 文件都需要引入该文件,然后使用 $namespace
变量:
@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
变量导出到 js
或 ts
文件里,在 theme-chalk/module/namespace.module.scss
文件导出 $namespace
:
@use"../mixins/config"as*;:export {namespace:#{$namespace};}
信息
SCSS Module API 只对 .module.scss
结尾的文件提供变量暴露功能。
如果是 Typescript 环境使用,则还需要在同级目录下定义一个 namespace.module.scss.d.ts
文件:
exportinterfaceScssVariables{[x:string]:unknown;namespace:string;}exportletvariables:ScssVariables;exportdefaultvariables;
最后在 Vue 组件引入 namespace.module.scss
:
<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(块)
- 独立的功能模块,可以独立存在
- 示例:
button
、menu
、input
Element(元素)
- 属于某个 Block 的一部分,不能单独存在
- 使用双下划线
__
连接 Block 和 Element - 示例:
menu__item
、button__text
Modifier(修饰符)
- 用于改变 Block 或 Element 的外观或行为
- 使用双横线
--
表示 - 示例:
button--large
、menu__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
<divclass="button"><spanclass="button__text">文字按钮</span><buttonclass="button--large">large 按钮</button><spanclass="button__text--bold">文字加粗按钮</span></div>
.button{}.button__text{}.button--large{}.button__text--bold{}
组件元素使用 BEM
定义一个 Hooks 文件 useNamespace.ts来封装命名空间和 BEM 规范,命名空间和 BEM 规范在上文已经介绍过了。
在组件中引入 useNamespace.ts
文件,使用命名空间 + BEM 规范来编写 class
,如:
<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>
等于:
<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
来编写样式,如:
@use"../mixins/bem"as*;@includeb("button") {@includee("text") {@includem("bold") {}}@includem("large") {}.button{@includewhen("primary") {}}}
等于:
.tk-button{.tk-button__text{&--bold{}}.tk-button--large{}.button{&.is-primary{}}}
具体使用请看 Teek 的样式源码。
CSS Var 变量使用命名空间
Teek 给所有的 CSS Var 变量都添加命名空间,如:
--tk-text-color-secondary:#86909c;
为了共用 $namespace
变量,所以 Teek 提供了 set-css-var
Mixin 和 getCss-Var
函数来进行封装:
@use"./function"as*;@mixinset-css-var($name,$value) {#{joinVarName($name)}:#{$value};}
$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 变量:
@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);}
等于
:root{--tk-button-width:84px;--tk-button-height:32px;--tk-button-color:#3451b2;--tk-button-font-size:16px;}
使用 CSS Var 变量:
@use"../mixins/function"as*;.demo{width:getCssVar("button-width");height:getCssVar("button-height");color:getCssVar("button-color");font-size:getCssVar("button-font-size");}
等于
.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 源码。