Tailwind CSS 实战:性能优化最佳实践
在现代网页开发中,性能优化就像是一场精心策划的马拉松。记得在一个电商项目中,我们通过一系列的性能优化措施,让页面加载时间减少了 60%,转化率提升了 25%。今天,我想和大家分享如何使用 Tailwind CSS 进行性能优化。
优化理念
性能优化就像是在打磨一块璞玉。我们需要通过各种技术手段,让网站在各种场景下都能保持出色的性能表现。在开始优化之前,我们需要考虑以下几个关键点:
- 构建优化,减少不必要的代码
- 运行时优化,提升执行效率
- 加载优化,优化资源加载
- 渲染优化,提升渲染性能
构建优化
首先,让我们从构建优化开始:
// tailwind.config.js
module.exports = {// 配置 JIT 模式mode: 'jit',// 配置 purgecontent: ['./src/**/*.{js,jsx,ts,tsx,vue}','./public/index.html',],// 配置主题theme: {extend: {// 自定义断点screens: {'xs': '475px',},// 自定义颜色colors: {primary: {50: '#f8fafc',// ... 其他色阶900: '#0f172a',},},},},// 配置变体variants: {extend: {// 只启用需要的变体opacity: ['hover', 'focus'],backgroundColor: ['hover', 'focus', 'active'],},},// 配置插件plugins: [// 只引入需要的插件require('@tailwindcss/forms'),require('@tailwindcss/typography'),],
}
PostCSS 优化
配置 PostCSS 以提升构建性能:
// postcss.config.js
module.exports = {plugins: [// 配置 Tailwind CSSrequire('tailwindcss'),// 配置 autoprefixerrequire('autoprefixer'),// 生产环境优化process.env.NODE_ENV === 'production' && require('cssnano')({preset: ['default', {// 优化选项discardComments: {removeAll: true,},normalizeWhitespace: false,}],}),].filter(Boolean),
}
按需加载优化
实现样式的按需加载:
// 路由配置
const routes = [{path: '/',component: () => import(/* webpackChunkName: "home" */ './views/Home.vue'),// 预加载样式beforeEnter: (to, from, next) => {import(/* webpackChunkName: "home-styles" */ './styles/home.css').then(() => next())},},// 其他路由...
]// 样式模块
// home.css
@layer components {.home-specific {@apply bg-white dark:bg-gray-900;}.home-card {@apply rounded-lg shadow-lg p-6;}
}// 组件中使用
<template><div class="home-specific"><div class="home-card"><!-- 内容 --></div></div>
</template>
类名优化
优化类名的使用方式:
<!-- 使用 @apply 抽取重复的类名 -->
<style>
.btn-primary {@apply px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2;
}.card-base {@apply bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden;
}.input-base {@apply block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500;
}
</style><!-- 使用组合类替代多个独立类 -->
<div class="card-base"><div class="p-6"><input type="text" class="input-base"><button class="btn-primary">提交</button></div>
</div><!-- 使用动态类名 -->
<script>
const buttonClasses = {primary: 'bg-blue-500 hover:bg-blue-600',secondary: 'bg-gray-500 hover:bg-gray-600',danger: 'bg-red-500 hover:bg-red-600',
}export default {computed: {buttonClass() {return buttonClasses[this.type] || buttonClasses.primary}}
}
</script>
响应式优化
优化响应式设计的性能:
<!-- 使用容器查询替代媒体查询 -->
<div class="container-query"><style>@container (min-width: 640px) {.card {@apply grid grid-cols-2 gap-4;}}</style><div class="card"><!-- 内容 --></div>
</div><!-- 使用视口单位优化 -->
<style>
.responsive-text {font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem);
}.responsive-spacing {padding: clamp(1rem, 3vw, 2rem);
}
</style><!-- 使用 aspect-ratio 优化图片布局 -->
<div class="aspect-w-16 aspect-h-9"><img src="/image.jpg"class="object-cover"loading="lazy">
</div>
图片优化
优化图片资源:
<!-- 使用响应式图片 -->
<picture><sourcemedia="(min-width: 1024px)"srcset="/image-lg.webp"type="image/webp"><sourcemedia="(min-width: 640px)"srcset="/image-md.webp"type="image/webp"><imgsrc="/image-sm.jpg"class="w-full h-auto"loading="lazy"decoding="async"alt="响应式图片">
</picture><!-- 使用 blur-up 技术 -->
<div class="relative"><imgsrc="/image-placeholder.jpg"class="absolute inset-0 w-full h-full filter blur-lg transform scale-110"><imgsrc="/image-full.jpg"class="relative w-full h-full"loading="lazy">
</div><!-- 使用 SVG 优化 -->
<svg class="w-6 h-6 text-gray-500"><use href="#icon-sprite"></use>
</svg>
动画优化
优化动画性能:
<!-- 使用 CSS 变量优化动画 -->
<style>
:root {--animation-timing: 200ms;--animation-easing: cubic-bezier(0.4, 0, 0.2, 1);
}.animate-fade {animation: fade var(--animation-timing) var(--animation-easing);
}@keyframes fade {from { opacity: 0; }to { opacity: 1; }
}
</style><!-- 使用 will-change 优化动画性能 -->
<div class="transform hover:scale-105 transition-transform will-change-transform"><!-- 内容 -->
</div><!-- 使用 CSS transforms 替代位置属性 -->
<style>
.slide-enter {transform: translateX(100%);
}.slide-enter-active {transform: translateX(0);transition: transform var(--animation-timing) var(--animation-easing);
}
</style>
渲染优化
优化渲染性能:
<!-- 虚拟列表优化 -->
<template><div class="h-screen overflow-auto" ref="container"><div class="relative":style="{ height: totalHeight + 'px' }"><divv-for="item in visibleItems":key="item.id"class="absolute w-full":style="{ transform: `translateY(${item.offset}px)` }"><!-- 列表项内容 --></div></div></div>
</template><script>
export default {data() {return {items: [], // 完整数据visibleItems: [], // 可见数据itemHeight: 50, // 每项高度containerHeight: 0, // 容器高度scrollTop: 0, // 滚动位置}},computed: {totalHeight() {return this.items.length * this.itemHeight},visibleCount() {return Math.ceil(this.containerHeight / this.itemHeight)},startIndex() {return Math.floor(this.scrollTop / this.itemHeight)},endIndex() {return Math.min(this.startIndex + this.visibleCount + 1,this.items.length)},},methods: {updateVisibleItems() {this.visibleItems = this.items.slice(this.startIndex, this.endIndex).map((item, index) => ({...item,offset: (this.startIndex + index) * this.itemHeight,}))},onScroll() {this.scrollTop = this.$refs.container.scrollTopthis.updateVisibleItems()},},mounted() {this.containerHeight = this.$refs.container.clientHeightthis.updateVisibleItems()this.$refs.container.addEventListener('scroll', this.onScroll)},beforeDestroy() {this.$refs.container.removeEventListener('scroll', this.onScroll)},
}
</script>
代码分割优化
优化代码分割:
// webpack.config.js
module.exports = {optimization: {splitChunks: {chunks: 'all',minSize: 20000,maxSize: 244000,cacheGroups: {// 提取公共样式styles: {name: 'styles',test: /\.(css|scss)$/,chunks: 'all',enforce: true,},// 提取公共组件commons: {name: 'commons',minChunks: 2,priority: -10,},// 提取第三方库vendors: {test: /[\\/]node_modules[\\/]/,name(module) {const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]return `vendor.${packageName.replace('@', '')}`},priority: -9,},},},},
}// 路由级代码分割
const routes = [{path: '/dashboard',component: () => import(/* webpackChunkName: "dashboard" */'./views/Dashboard.vue'),children: [{path: 'analytics',component: () => import(/* webpackChunkName: "dashboard-analytics" */'./views/dashboard/Analytics.vue'),},{path: 'reports',component: () => import(/* webpackChunkName: "dashboard-reports" */'./views/dashboard/Reports.vue'),},],},
]
缓存优化
优化缓存策略:
// 配置 Service Worker
// sw.js
const CACHE_NAME = 'app-cache-v1'
const STATIC_CACHE = ['/','/index.html','/css/app.css','/js/app.js',
]self.addEventListener('install', (event) => {event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_CACHE)))
})self.addEventListener('fetch', (event) => {event.respondWith(caches.match(event.request).then((response) => {if (response) {return response}return fetch(event.request).then((response) => {if (!response || response.status !== 200 || response.type !== 'basic') {return response}const responseToCache = response.clone()caches.open(CACHE_NAME).then((cache) => {cache.put(event.request, responseToCache)})return response})}))
})// 注册 Service Worker
if ('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js').then((registration) => {console.log('SW registered:', registration)}).catch((error) => {console.log('SW registration failed:', error)})})
}
监控优化
实现性能监控:
// 性能监控
const performanceMonitor = {// 初始化init() {this.observePaint()this.observeLCP()this.observeFID()this.observeCLS()},// 观察绘制时间observePaint() {const observer = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {console.log(`${entry.name}: ${entry.startTime}`)}})observer.observe({ entryTypes: ['paint'] })},// 观察最大内容绘制observeLCP() {const observer = new PerformanceObserver((list) => {const entries = list.getEntries()const lastEntry = entries[entries.length - 1]console.log('LCP:', lastEntry.startTime)})observer.observe({ entryTypes: ['largest-contentful-paint'] })},// 观察首次输入延迟observeFID() {const observer = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {console.log('FID:', entry.processingStart - entry.startTime)}})observer.observe({ entryTypes: ['first-input'] })},// 观察累积布局偏移observeCLS() {let clsValue = 0let clsEntries = []const observer = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {if (!entry.hadRecentInput) {const firstFrame = entry.firstFrame || 0const lastFrame = entry.lastFrame || 0const impactedFrames = lastFrame - firstFrame + 1clsValue += entry.valueclsEntries.push(entry)console.log('CLS:', clsValue, 'Impacted Frames:', impactedFrames)}}})observer.observe({ entryTypes: ['layout-shift'] })},
}// 初始化监控
performanceMonitor.init()
写在最后
通过这篇文章,我们详细探讨了如何使用 Tailwind CSS 进行性能优化。从构建优化到运行时优化,从加载优化到渲染优化,我们不仅关注了技术实现,更注重了实际效果。
记住,性能优化就像是一场永无止境的马拉松,需要我们持续不断地改进和优化。在实际开发中,我们要始终以用户体验为中心,在功能和性能之间找到最佳平衡点。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍
相关文章:
Tailwind CSS 实战:性能优化最佳实践
在现代网页开发中,性能优化就像是一场精心策划的马拉松。记得在一个电商项目中,我们通过一系列的性能优化措施,让页面加载时间减少了 60%,转化率提升了 25%。今天,我想和大家分享如何使用 Tailwind CSS 进行性能优化。 优化理念 性能优化就像是在打磨一块璞玉。我们需要通过各…...
[redux] useDispatch的两种用法
先重写2个方法先, 方便ts类型推导,如果你看不懂为什么这么写, 先看我这篇 [redux] ts声明useSelector和useDispatch-CSDN博客 export type RootState ReturnType<typeof store.getState>; export type AppDispatch typeof store.dispatch; export const useAppDispat…...
Postgresql 命令还原数据库
因为PgAdmin打不开,但是数据库已经安装成功了,这里借助Pg命令来还原数据库 C:\Program Files\PostgreSQL\15\bin\psql.exe #链接数据库 psql -U postgres -p 5432#创建数据库 CREATE DATABASE "数据库名称"WITHOWNER postgresENCODING UTF8…...
电脑找不到mfc110.dll文件要如何解决?Windows缺失mfc110.dll文件快速解决方法
一、mfc110.dll文件的重要性 mfc110.dll,全称Microsoft Foundation Class Library 110,是Microsoft Visual C Redistributable for Visual Studio 2012的一部分。这个动态链接库(DLL)文件对于支持基于MFC(Microsoft F…...
Elasticsearch与数据库数据一致性:最佳实践与解决方案
在现代应用程序中,Elasticsearch(ES)作为一个高效的分布式搜索引擎,常常与数据库一同使用,以提供强大的搜索、分析和数据可视化功能。然而,数据库和Elasticsearch之间的同步与一致性常常成为一个挑战。如何…...
vue导入导出excel、设置单元格文字颜色、背景色、合并单元格(使用xlsx-js-style库)
npm i xlsx-js-style <template><button click"download">下载 Excel 表格</button><el-table :data"tableData" style"width: 100%"><el-table-column prop"date" label"日期" width"180…...
电子电气架构 --- 中央处理器HPC及软件架构
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源,以现象替代逻辑,以情绪代替思考,把消极接受现实的懦弱,伪装成乐观面对不幸的…...
代码实战:基于InvSR对视频进行超分辨率重建
Diffusion Models专栏文章汇总:入门与实战 前言:上一篇博客《使用Diffusion Models进行图像超分辩重建》中讲解了InvSR的原理,博主实测的效果是非常不错的,和PASD基本持平。这篇博客就讲解如何利用InvSR对视频进行超分辨率重建。 目录 环境准备 代码讲解 环境准备...
一文读懂主成分分析法(PCA)
主成分分析法(PCA) 主成分分析法(PCA)主成分分析的基本思想主成分的计算主成分分析的原理主成分分析的特点主成分分析的应用 主成分分析法(PCA) 主成分分析的基本思想 PCA是1901 年Pearson在研究回归分析…...
Redis(基础篇 + 实践篇 )
01 | 基本架构:一个键值数据库包含什么? Redis 作为一个内存数据存储系统,它的架构设计非常简洁,但功能非常强大。理解其核心架构对高效使用 Redis 至关重要。 客户端与服务器架构: 客户端通过 TCP 协议连接到 Redis …...
高质量C++小白教程:2.10-预处理器简介
当你在编译项目时,你可能希望编译器完全按照你编写的方式编译每一个代码文件,当事实并非如此。 相反,在编译之前,每一个.cpp文件都会经历一个预处理的阶段,在此阶段中,称为预处理器的程序对代码文件的文本进行各种更改. 预处理器实际上不会以任何方式修改原始代码文件,预处理…...
一、二极管(模电理论篇)
导论:PN结(结电容)是构成二极管,三极管,场效应管的原理基础 1.二极管特性(单向导电性) 1.1 P型半导体与N型半导体 在单晶体硅(原子核为正四价电子,可以形成四条共价键&…...
JAVA学习笔记_JVM
文章目录 初识jvm内存结构程序计数器(寄存器) 栈问题辨析内存溢出 线程诊断本地方法栈Heap堆内存溢出内存诊断 方法区内存溢出常量池 stringTable直接内存垃圾回收 初识jvm JRE JVM 基础类库 JDK JRE 编译工具 JavaSE JDK IDE工具 JavaEE JDK 应用服务器 IDE工具 jvm是…...
SQL 中复杂 CASE WHEN 嵌套逻辑优化
目标:优化复杂的 CASE WHEN 逻辑,提升 SQL 语句的可读性与执行效率,减少多层嵌套带来的复杂性。 1. CASE WHEN 的常见问题 嵌套过深:多个条件判断嵌套,难以阅读和维护。重复逻辑:相似逻辑在多个分支中重复…...
STM32-笔记34-4G遥控灯
4G接线 一、项目需求 服务器通过4G模块远程遥控开关灯。 二、项目实现 复制项目文件夹38-wifi控制风扇项目 重命名为39-4G遥控点灯 打开项目文件 加载文件 main.c #include "sys.h" #include "delay.h" #include "led.h" #include "ua…...
被催更了,2025元旦源码继续免费送
“时间从来不会停下,它只会匆匆流逝。抓住每一刻,我们才不会辜负自己。” 联系作者免费领💖源💖码。 三联支持:点赞👍收藏⭐️留言📝欢迎留言讨论 更多内容敬请期待。如有需要源码可以联系作者免…...
Java(day1)
注释 在Java中注释分为单行注释、多行注释还有文档注释 //我是单行注释/*我 是多行 注释 *//** 我是文档注释*/ 关键字 关键字:是被Java赋予了特定含义的英文单词 特点:关键字的字母都是c 在常用的代码编辑器中关键字都有特殊的高亮标记 在这个里…...
PDF文件提示-文档无法打印-的解决办法
背景信息 下载了几个签名的PDF文件,想要打印纸质版,结果打印时 Adobe Acrobat Reader 提示【文档无法打印】: 解决办法 网上的方案是使用老版本的PDF阅读器, 因为无法打印只是一个标识而已。 PDF文件不能打印的五种解决方案-zhihu 这些方…...
ubuntu操作系统安装SSH服务
1、更新仓库 sudo apt-get update 2、安装SSH服务 #安装SSH服务 apt-get install openssh-server#启用SSH服务 service ssh start#查看SSH服务运行状态 service ssh status 3、修改SSH配置文件 sudo vi /etc/ssh/sshd_config 4、开启ssh端口 sudo ufw allow ssh 5、重启SSH…...
Beamer-LaTeX学习(教程批注版)【1】
该文档总体由beamer-latex的教程而来,由耳东小白以自身学习路径整理。因其中要点基本按照教程的顺序和结构整理,故而不能称之为完全原创,但也不是翻译,更不是抄袭,是个人自学笔记和批注,其中添加了小白个人…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...
Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...
