标签 TinyVue 下的文章

本文由TinyVue贡献者程锴原创。

一、前言:为什么要统一管理动效

在前端开发中,动画不仅是锦上添花的“视觉糖”,更是交互体验的重要组成部分:
它能引导用户关注、反馈操作结果、缓解等待焦虑、提升产品质感。

但当项目变大、组件增多后,你可能遇到这些问题:

  • 同样的淡入淡出,在不同组件中表现不一致
  • 想调整动画速度,却要修改多个文件
  • 动画样式难以复用、维护困难

这些问题的根源在于:动画定义分散、缺乏统一管理
为此,TinyVue 引入了一套全新的 全局动效体系,基于 LESS + CSS 变量 实现集中配置与动态控制。

二、为什么选择 LESS + CSS 变量

常见的动画实现方式有两种:

方式优点缺点
1️⃣ 直接在组件中定义@keyframes简单直观,局部可定制无法统一、修改麻烦
2️⃣ 全局管理动画可复用、风格一致静态,难以动态调整

TinyVue 采用 LESS + CSS 变量结合方案,兼顾两者优势:

变量化控制
所有动效的时长、透明度、位移量都由 CSS 变量控制

可局部覆盖
组件可根据需求覆盖变量,灵活调整动画参数

主题可切换
只需在不同主题文件中修改变量,即可快速切换全局动效风格

三、环境搭建与示例预览

1. 拉取 TinyVue 仓库:

git clone https://github.com/opentiny/tiny-vue.git
cd tiny-vue
pnpm i

1.PNG

2. 启动TinyVue项目

pnpm dev

浏览器访问:http://localhost:7130

2.png

3. 打开配置文件:

/packages/theme/src/base/vars.less

3.png

1). 修改变量即可实时生效:

--tv-motion-slide-speed: 1.2s;

刷新页面后,可在抽屉(Drawer)组件中观察滑动动效速度变化。

4.gif

同样地:

--tv-motion-fade-offset-y: 100px;

会影响对话框(DialogBox)的淡入位移动画。

5.gif

四、全局动效的设计思路

1. 统一变量管理

所有动画相关参数集中在 /packages/theme/src/base/vars.less

:root {
  /* 淡入淡出 */
  --tv-motion-fade-speed: 0.3s;

  /* 滑动类 */
  --tv-motion-slide-speed: 0.4s;
  --tv-motion-slide-offset-left: -30px;
  --tv-motion-slide-offset-left-mid: -10px;
  --tv-motion-slide-opacity-mid: 0.5;

  /* 蚂蚁线 */
  --tv-motion-ants-shift: 8px;
  --tv-motion-ants-speed: 0.8s;
}
修改任意变量即可影响全局动效表现。

2. 按类型分类管理

为方便维护和扩展,动效按类型拆分为多个 LESS 文件:

motion/
  fade.less       // 淡入淡出
  slide.less      // 滑动
  zoom.less       // 缩放
  rotate.less     // 旋转
  bounce.less     // 弹跳
  ants.less       // 蚂蚁线
  ...
  index.less      // 汇总引入

每个文件独立维护一类动效,结构清晰,修改成本低。

3. 动效命名规范

统一命名规则:
{type}-{direction}-{state}

示例:

  • fade-in:淡入
  • slide-left-in:从左滑入
  • zoom-in:放大进入
  • ants-x-rev:蚂蚁线反向滚动
保证语义清晰、全局唯一,方便引用与调试。

五、动效实现示例

1️⃣ 淡入淡出动效

@keyframes fade-in {
  0% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes fade-out {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

调用方式:

.fade-enter-active {
  animation: fade-in var(--tv-motion-fade-speed) ease-out both;
}
.fade-leave-active {
  animation: fade-out var(--tv-motion-fade-speed) ease-in both;
}

2️⃣ 滑动动效

@keyframes slide-left-in {
  0% {
    opacity: 0;
    transform: translateX(var(--tv-motion-slide-offset-left));
  }
  50% {
    opacity: var(--tv-motion-slide-opacity-mid);
    transform: translateX(var(--tv-motion-slide-offset-left-mid));
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

通过变量可灵活调整动画节奏和距离。

3️⃣ 蚂蚁线动画(Ants)

@keyframes ants-x {
  0% { background-position: 0 0; }
  100% { background-position: var(--tv-motion-ants-shift, 8px) 0; }
}

在组件中调用:

.copyed-borders {
  --tv-motion-ants-shift: 13px;
  .border-top {
    animation: ants-x var(--tv-motion-ants-speed) linear infinite;
  }
}

六、组件集成方式

方式描述
全局引入motion/index.less 统一引入所有动效,确保全局可用
局部调用组件通过类名或 animation 属性使用对应动效
变量覆盖通过覆盖 CSS 变量实现不同组件动效差异化

七、实践经验与优化建议

保持命名规范:保证语义清晰、避免重复
文件分类明确:不同类型动效分文件管理
加注释和示例:便于团队协作与复用

关于OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:https://opentiny.design
OpenTiny 代码仓库:https://github.com/opentiny
TinyVue源码:https://github.com/opentiny/tiny-vue

欢迎进入代码仓库 Star🌟TinyVue、TinyEngine、TinyPro、TinyNG、TinyCLI、TinyEditor
如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~

本文由TinyPro贡献者王晨光同学原创。

一、背景:让 TinyPro 真正“走到掌心里”

TinyPro 是一套基于 TinyVue 打造的前后端分离后台管理系统,支持菜单配置、国际化、多页签、权限管理等丰富特性。
TinyPro 在桌面端具备良好的体验和模块化架构,但随着移动办公、平板展示等场景增多,移动端体验的短板逐渐显现:

  • 页面缩放不均衡,布局出现溢出或错位;
  • 模态框在小屏上遮挡内容;
  • 图表和表格在横屏与竖屏间切换时无法自适应;
  • 操作区过于密集,不符合触控习惯。

为此启动了 TinyPro 移动端适配项目,目标是在不破坏现有结构的前提下,实现“一次开发,跨端流畅”的体验。

二、技术选型与总体架构

本次移动端适配要求在复杂的中后台系统中实现「一次开发,多端自适应」,既要保证样式灵活,又要维持可维护性和构建性能。

在技术选型阶段,综合评估了三种常见方案:

方案优点缺点
纯 CSS 媒体查询简单直接、依赖少样式分散、逻辑重复、维护困难
TailwindCSS 响应式类社区成熟、类名直观、生态完善样式表体积大、断点固定、不够灵活
UnoCSS 原子化方案按需生成、性能极轻、断点与变体完全可定制需要自行配置规范与规则体系

最终选择了 UnoCSS + Less 的混合架构

  • UnoCSS:负责通用布局、间距、排版等高频样式,原子化写法提升开发效率;
  • Less 媒体查询:用于模态框、导航栏等复杂场景的精细控制;
  • 统一断点配置:集中管理屏幕尺寸分级,保持视觉一致性;
  • 自定义变体(max-<bp>:支持“桌面端优先”策略,通过 max-width 实现移动端自适应,样式逻辑更直观。

UnoCSS:轻量、灵活、即时生成

UnoCSS 是一个 按需生成的原子化 CSS 引擎,最大的特点是 零冗余与高度可定制
不同于 TailwindCSS 的预编译方式,UnoCSS 会在构建阶段根据实际使用的类名即时生成样式规则,从而显著提升构建性能与灵活性.

在配置中通过 presetMini()presetAttributify() 组合使用,使开发者既可以写:

<div class="p-4 text-center bg-gray-100 max-md:p-2"></div>

也可以使用属性化语法:

<div p="4" text="center" bg="gray-100" max-md:p="2"></div>

presetMini 提供轻量原子类体系,presetAttributify 则允许以声明式方式书写样式,更直观、组件化友好。

断点配置与响应式策略

TinyPro 的适配核心之一,是在 uno.config.ts 中建立统一的断点体系,并通过自定义 max-<bp> 前缀实现“桌面端优先”的响应式策略。

const breakpoints = {
  sm: '641px',     // 手机(小屏)
  md: '769px',     // 平板竖屏
  lg: '1025px',    // 平板横屏 / 小型笔电
  xl: '1367px',    // 常规笔电
  '2xl': '1441px', // 高清笔电
  '3xl': '1921px', // 桌面大屏
}

并通过自定义 variants 扩展 max-<bp> 前缀:

variants: [
    (matcher) => {
      const match = matcher.match(/^max-([a-z0-9]+):/)
      if (match) {
        const bp = match[1]
        const value = breakpoints[bp]
        if (!value) return
        return {
          matcher: matcher.replace(`max-${bp}:`, ''),
          parent: `@media (max-width: ${value})`,
        }
      }
    },
  ]

让开发者能自然地书写:

<div class="w-1/2 max-md:w-full"></div>

含义:

默认宽度为 50%,在宽度小于 769px 的设备上改为 100%。

TinyPro 采用「桌面端优先(max-width)」的布局策略:默认以桌面端布局为基础,在移动设备上再进行针对性优化。相比常见的「移动端优先(min-width)」方式,这种做法更符合中后台系统的特性,同时让 UnoCSS 的断点逻辑更直观,并确保主屏体验的稳定性。

三、样式与编码策略

  • 优先级

    • 简单场景:使用 UnoCSS 原子类。
    • 复杂样式:使用 Less 媒体查询。
  • 布局与滚动

    • 首页及核心业务模块完成适配,小屏模式下侧边栏默认收起、导航栏折叠,确保主要内容可见。
    • 页面主要容器避免横向滚动,必要时在小屏下开启局部横向滚动。
    • 表格与大区块在不同断点下自动调整宽度、栅格与间距,小屏下支持横向滚动;分页与密度支持响应式控制。

    布局与滚动.gif

  • 图表自适应

    • 图表组件接入 resize 监听,在侧边栏展开/收起、窗口缩放、语言切换等场景下保持自适应。
    • 小屏下使用 vw 宽度与较小字号,保证图表展示效果与可读性。

    图表自适应.gif

  • 表单与模态框

    • 接入 useResponsiveSize(),控制弹窗在小屏下铺满显示,大屏保持固定宽度。
    • 表单项在不同断点下动态调整排布与间距,优化触控体验。

    表单与模态框.gif

  • 导航与交互

    • 小屏下隐藏导航栏非关键元素,操作聚合到"折叠菜单"。
    • 移动端默认收起侧边菜单栏,提升主要内容展示区域。

    导航与交互.gif

  • 性能优化

    • responsive.ts 中对 resize 事件处理增加节流机制,避免窗口缩放等场景下的频繁无效渲染。

四、常用代码片段

  1. 基于栅格系统 + 响应式断点工具类,通过为 tiny-row 和 tiny-col 添加不同屏幕宽度下的样式规则,实现自适应布局:
<tiny-layout>
    <tiny-row class="flex justify-center max-md:flex-wrap">
        <tiny-col class="w-1/4 max-md:w-1/2 max-sm:w-full max-md:mb-4">···</tiny-col>
        ···
        <tiny-col class="w-1/4 max-md:w-1/2 max-sm:w-full max-md:mb-4">···</tiny-col>
    </tiny-row>
</tiny-layout>
<div class="theme-line flex max-sm:grid max-sm:grid-cols-4 max-sm:gap-2">
  <div···
  </div>
</div>
  1. 基于 响应式工具类 + 自定义响应式 Hook,解决(1)对话框宽度自适应;(2)表格尺寸和密度自适应;(3)逻辑层响应式控制
<template>
  <section class="p-4 sm:p-6 lg:p-8 max-sm:text-center">
    <tiny-dialog :width="modalSize">...</tiny-dialog>
  </section>
</template>

<script setup lang="ts">
import { useResponsiveSize } from '@/hooks/responsive'
const { modalSize } = useResponsiveSize() // 小屏 100%,大屏 768px
</script>
<template>
  <div class="container">
    <tiny-grid ref="grid" :fetch-data="fetchDataOption" :pager="pagerConfig" :size="gridSize" :auto-resize="true" align="center">
      ···
    </tiny-grid>
  </div>
</template>

<script setup lang="ts">
import { useResponsiveSize } from '@/hooks/responsive'
const { gridSize } = useResponsiveSize() // 小屏为mini grid,大屏为medium grid
</script>
  1. 通过 useResponsive 获取屏幕断点状态 sm/md/lg,如:在模板中结合 v-if="!lg" 控制分隔线的渲染,从而实现了小屏下纵向菜单才显示分隔线的效果
<template>
  <ul class="right-side" :class="{ open: menuOpen }">
    <!-- 小屏下才显示分隔线 -->
    <li v-if="!lg">
      <div class="divider"></div>
    </li>
    ···
  </ul>
</template>

<script lang="ts" setup>
import { useResponsive } from '@/hooks/responsive'
const { lg } = useResponsive()
</script>

五、结语

通过本次移动端适配, TinyPro 实现了“从桌面到掌心”的统一体验:
开发者可以继续沿用熟悉的组件体系与布局方式,同时享受 UnoCSS 带来的原子化灵活性与性能优势。在不改变核心架构的前提下,TinyPro 变得更轻盈、更顺滑,也更符合移动时代的使用场景。

关于OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:https://opentiny.design
OpenTiny 代码仓库:https://github.com/opentiny
TinyPro源码:https://github.com/opentiny/tiny-pro

欢迎进入代码仓库 Star🌟TinyPro、TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor
如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献\~