CSS函数有多香?8个无JS妙用让你告别冗余代码
你有没有过这样的“崩溃时刻”?
- 为了做个tooltip写了 100 行 JS,结果 hover 时定位不准,调试到凌晨;
- 为了响应式字体写了 5 个媒体查询,改个大小要连改 3 处;
- 想做个毛玻璃效果,找了 3 个 PSD 素材,结果导出的图片模糊到没法用……
其实这些问题,CSS 函数早就帮你解决了——不用写一行 JS,几行 CSS 代码就能实现,甚至更稳定、更高效。
今天就拆 8 个无 JS CSS 函数技巧,每个都附实际项目代码+** accessibility 踩坑指南**,看完你会发现:原来写 CSS 可以这么“躺平”!
一、纯 CSS Tooltip:不用 JS 也能做“精准定位”的提示框
很多人用 JS 写 tooltip,无非是“要动态定位”“要拿文本”。但 CSS 的attr()+伪元素+定位,天生就能解决这些问题——甚至更简单。
1. 先看效果:hover 就出带箭头的提示框
先上一个 Vue3 组件的实际效果(不用 JS,纯 CSS):
<template> <button class="tooltip" data-tooltip="这是纯CSS做的Tooltip!" aria-describedby="tooltip-content" > 点我看提示 🎉 </button></template>
<style scoped>.tooltip { position: relative; /* 伪元素要相对它定位 */ padding: 8px 16px; background: #409eff; color: #fff; border: none; border-radius: 4px; cursor: pointer;}
/* Tooltip 文本框:默认隐藏 */.tooltip::after { content: attr(data-tooltip); /* 从data-tooltip拿文本,不用JS! */ id: tooltip-content; /* 对应aria-describedby,屏幕阅读器能读 */ position: absolute; bottom: 120%; /* 定位在按钮上方 */ left: 50%; transform: translateX(-50%); /* 水平居中 */ padding: 6px 12px; background: #333; color: #fff; border-radius: 4px; font-size: 12px; white-space: nowrap; /* 不换行,避免提示框变宽 */ opacity: 0; visibility: hidden; transition: opacity 0.2s ease;}
/* Tooltip 箭头:用border画三角形 */.tooltip::before { content: ''; position: absolute; bottom: 100%; /* 箭头在文本框下方,指向按钮 */ left: 50%; transform: translateX(-50%); border-width: 5px; border-style: solid; /* 上边框是深色,其他是透明——这样就成了向下的三角形 */ border-color: #333 transparent transparent transparent; opacity: 0; visibility: hidden; transition: opacity 0.2s ease;}
/* hover时显示Tooltip */.tooltip:hover::after,.tooltip:hover::before { opacity: 1; visibility: visible;}</style>2. 关键知识点:为什么用attr()+data属性?
- data 属性:HTML 的
data-*属性是自定义属性,能存任意文本(比如 tooltip 内容、图片描述),不用 JS 就能写在 HTML 里; - attr()函数:CSS 的
attr()能直接获取 HTML 元素的属性值(比如attr(data-tooltip)就是拿data-tooltip的值),不用查 DOM; - aria-describedby:这个属性是给屏幕阅读器用的——指向
tooltip-content(也就是::after的 id),视障用户 hover 时,屏幕阅读器会读 Tooltip 的内容,保证可访问性。
二、attr() + 自定义 Data:一行 HTML 做“动态图片 Caption”
你有没有过这样的需求?图片 hover 时显示标题+描述,还要动态生成(比如从接口拿数据)。用attr()+data属性,一行 HTML 就能搞定,不用 JS 拼接字符串!
1. 实际场景:电商商品图的 Hover 描述
比如你有个商品列表,图片的标题和描述是接口返回的,直接存在data属性里:
<div class="product-img"> <img src="cup.jpg" alt="复古陶瓷杯" /> <div class="img-caption" data-title="复古陶瓷杯" data-desc="手工制作,容量300ml,适合泡咖啡" ></div></div>2. CSS 实现:hover 滑出动态描述
用::before和::after分别生成标题和描述,hover 时滑出:
.product-img { position: relative; width: 240px; height: 180px; overflow: hidden; /* 隐藏超出的caption */}
.product-img img { width: 100%; height: 100%; object-fit: cover;}
/* Caption容器:默认在底部隐藏 */.img-caption { position: absolute; bottom: 0; left: 0; width: 100%; padding: 10px; background: rgba(0, 0, 0, 0.7); /* 半透明黑背景 */ color: #fff; transform: translateY(100%); /* 初始在底部外,看不到 */ transition: transform 0.3s ease; /* 滑入动画 */}
/* 用attr()生成标题:字体加粗,占一行 */.img-caption::before { content: attr(data-title); font-size: 16px; font-weight: bold; display: block; margin-bottom: 4px;}
/* 用attr()生成描述:小字,半透明 */.img-caption::after { content: attr(data-desc); font-size: 12px; opacity: 0.8;}
/* hover时滑入Caption */.product-img:hover .img-caption { transform: translateY(0); /* 从底部滑上来 */}3. 踩坑提醒:CSS 生成内容的可访问性
CSS 用::before/::after生成的内容,默认不会被屏幕阅读器读取!所以:
- 重要信息(比如图片描述)不要只存在
data属性里,要在 HTML 里加aria-describedby指向data-desc; - 比如上面的例子,可以给图片加
aria-describedby="img-desc",然后给img-caption加id="img-desc",这样屏幕阅读器会读描述。
三、CSS 计数器:不用 JS 也能做“动态步骤条”
你有没有写过这样的步骤条?HTML 里写1. 填写信息、2. 验证手机,改顺序时要一个个改序号,麻烦到哭。用 CSS 计数器,序号自动生成,改顺序只需要动 HTML!
1. 实际场景:注册流程的动态步骤条
HTML 不用写序号,直接写步骤内容:
<ul class="steps"> <li class="step" data-status="completed" > 填写信息 </li> <li class="step" data-status="current" > 验证手机 </li> <li class="step">完成注册</li></ul>2. CSS 实现:自动计数+状态切换
用counter-reset初始化计数器,counter-increment递增,counter()显示序号:
.steps { counter-reset: step; /* 初始化计数器,名字叫step */ list-style: none; /* 去掉默认的列表点 */ padding: 0;}
.step { position: relative; padding-left: 30px; /* 给序号留位置 */ margin-bottom: 16px; font-size: 14px;}
/* 用::before生成步骤序号 */.step::before { content: counter(step); /* 显示计数器的值 */ counter-increment: step; /* 每一个step,计数器加1 */ position: absolute; left: 0; top: 0; width: 20px; height: 20px; line-height: 20px; text-align: center; background: #409eff; color: #fff; border-radius: 50%; /* 圆形序号 */}
/* 已完成的步骤:绿色背景+对勾 */.step[data-status='completed']::before { background: #67c23a; content: '✓'; /* 把序号换成对勾 */}
/* 当前步骤:黄色背景+加粗 */.step[data-status='current']::before { background: #e6a23c; font-weight: bold;}3. 为什么好用?
- 自动序号:改 HTML 步骤顺序,序号自动更新,不用手动改;
- 状态灵活:用
data-status控制状态(已完成/当前/未开始),CSS 直接改样式,不用 JS 加 class; - 可扩展性强:比如加“错误”状态,只需要加
data-status="error",然后写step[data-status="error"]::before { background: #f56c6c; content: "!"; }。
四、CSS Filters + Backdrop-Filter:不用 PS 也能做“毛玻璃效果”
你有没有羡慕过苹果 iOS 的毛玻璃效果?以前要 PS 导出模糊图片,现在用 CSS 的backdrop-filter,实时模糊背景,还不影响内容!
1. 基础实现:毛玻璃卡片
先给 body 加个背景图,突出毛玻璃效果:
body { background: url('bg.jpg') center/cover no-repeat; min-height: 100vh; display: flex; align-items: center; justify-content: center;}然后写毛玻璃卡片的 CSS:
.glass-card { padding: 24px; background: rgba(255, 255, 255, 0.2); /* 半透明白色背景 */ border-radius: 12px; box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37); /* 加阴影增强层次感 */ backdrop-filter: blur(10px); /* 核心!背景模糊10px */ -webkit-backdrop-filter: blur(10px); /* Safari 兼容 */}2. 关键区别:backdrop-filter vs filter
backdrop-filter:只模糊元素后面的背景(比如 body 的背景图),元素本身的内容(比如文字、图片)不会模糊;filter:会模糊整个元素(包括内容),比如你给卡片加filter: blur(10px),文字也会变模糊,没法看。
3. 兼容性处理
backdrop-filter支持 Chrome 76+、Firefox 103+、Safari 9+,旧浏览器可以用filter降级:
/* 旧浏览器降级:用伪元素模糊背景 */.glass-card::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: inherit; /* 继承卡片的背景色 */ filter: blur(10px); /* 模糊背景 */ z-index: -1; /* 把模糊层放在内容下面,不影响文字 */}五、element():用 HTML 元素做“动态背景”(Firefox 专属黑科技)
这个函数有点“黑科技”——能把一个 HTML 元素(比如 Canvas、Video)当成另一个元素的背景图。比如你有个 Canvas 做的动画,直接当背景,不用导出成 GIF!
1. 实际场景:Canvas 动画当背景
先写个 Canvas,画个旋转的正方形:
<canvas id="bg-canvas" width="400" height="300"></canvas><div class="canvas-bg">我用Canvas做背景!</div>然后用 JS 让 Canvas 动起来:
const canvas = document.getElementById('bg-canvas')const ctx = canvas.getContext('2d')let angle = 0
function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空画布 ctx.save() // 保存当前状态 ctx.translate(200, 150) // 把原点移到画布中心 ctx.rotate(angle) // 旋转角度 ctx.fillStyle = '#409eff' // 填充色 ctx.fillRect(-50, -50, 100, 100) // 画正方形(中心在原点) ctx.restore() // 恢复状态 angle += 0.01 // 角度递增 requestAnimationFrame(animate) // 循环动画}
animate()2. CSS 实现:用 Canvas 当背景
Firefox 用-moz-element()函数,把 Canvas 当背景:
.canvas-bg { width: 400px; height: 300px; background: -moz-element(#bg-canvas) center/cover; /* 把Canvas当背景 */ color: #fff; text-align: center; line-height: 300px; font-size: 20px;}3. 注意事项
element()目前只有 Firefox 支持(-moz-element),其他浏览器暂不支持;- 适合做实验性效果(比如个人博客、创意项目),生产环境要做降级(比如用静态图片当背景)。
六、calc():不用媒体查询也能做“响应式栅格”
你有没有写过这样的栅格?为了让每行 3 张图,间距一致,写了一堆margin,结果换行时间距乱掉。用calc(),宽度自动计算,间距完美!
1. 实际场景:响应式图片栅格
比如你要做一个每行 3 张图,间距 20px 的栅格,不用媒体查询也能响应式:
<div class="image-grid"> <img src="img1.jpg" alt="图1" /> <img src="img2.jpg" alt="图2" /> <img src="img3.jpg" alt="图3" /> <img src="img4.jpg" alt="图4" /> <img src="img5.jpg" alt="图5" /> <img src="img6.jpg" alt="图6" /></div>2. CSS 实现:自动计算宽度+间距
用calc()计算每张图的宽度:(100% - 间距*间隙数) / 列数。比如每行 3 列,间隙数是 2(3 张图之间有 2 个间隙),所以宽度是(100% - 20px*2) / 3:
.image-grid { display: flex; flex-wrap: wrap; gap: 20px; /* 用gap设置间距,比margin更方便 */}
.image-grid img { width: calc((100% - 20px * 2) / 3); /* 每行3张,间距20px */ height: 200px; object-fit: cover; /* 图片填充容器,保持比例 */}
/* 小屏幕(<=768px):每行2张 */@media (max-width: 768px) { .image-grid img { width: calc((100% - 20px * 1) / 2); /* 每行2张,间隙数1 */ }}3. 为什么用gap而不是margin?
gap是 Flexbox 和 Grid 的属性,直接设置子元素之间的间距(包括水平和垂直),不用写margin-right、margin-bottom;- 比如
gap: 20px,子元素之间的水平和垂直间距都是 20px,比 margin 更简洁。
七、cubic-bezier():不用 JS 也能做“自然的动画”
你有没有觉得默认的ease动画很生硬?比如按钮点击时缩小,弹回时像“弹簧”一样,用cubic-bezier()函数,几行代码就能做自然的弹性动画!
1. 基础理解:贝塞尔曲线是“动画的节奏控制器”
cubic-bezier(x1, y1, x2, y2)里的四个值,其实是贝塞尔曲线的两个控制点——你可以把它想象成“拉绳子”:
- 第一个控制点
(x1, y1):控制动画前半段的节奏(比如开头是“冲”还是“慢”); - 第二个控制点
(x2, y2):控制动画后半段的节奏(比如结尾是“弹回”还是“匀速”)。 常用的曲线值: - 线性:
cubic-bezier(0,0,1,1)(等同于linear,动画匀速); - 减速:
cubic-bezier(0.7,0,0.3,1)(开头快,结尾慢,适合“滑入”效果); - 弹性:
cubic-bezier(0.68, -0.55, 0.27, 1.55)(超出目标后回弹,像弹簧一样)。
2. 实际场景:电商卡片的“弹性弹起”动画
比如你有个商品卡片,hover 时要“轻轻弹起”,比默认的ease更有质感。直接上代码: #### HTML(Vue3 组件):
<template> <div class="product-card"> <img src="cup.jpg" alt="复古陶瓷杯" class="card-img" /> <div class="card-info"> <h4 class="card-title">复古陶瓷杯</h4> <p class="card-price">¥29.9</p> </div> </div></template>CSS 实现:弹性弹起效果
.product-card { position: relative; width: 240px; padding: 16px; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 初始阴影 */ /* 关键:用cubic-bezier做弹性过渡 */ transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55), box-shadow 0.3s ease; cursor: pointer;}.card-img { width: 100%; height: 180px; object-fit: cover; border-radius: 4px; margin-bottom: 12px;} /* Hover时:弹起+加深阴影 */.product-card:hover { transform: translateY(-10px) scale(1.02); /* 上移10px+放大1.02倍 */ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); /* 阴影加深,更立体 */}3. 为什么这个曲线值能产生“弹性”?
我们用的曲线是cubic-bezier(0.68, -0.55, 0.27, 1.55):
- 前半段:
(0.68, -0.55)——x 轴超过 0.5,y 轴是负数,意味着动画开头会“冲”一下(比如弹起时,先向上多走一点,超出目标位置); - 后半段:
(0.27, 1.55)——y 轴超过 1,意味着动画结尾会“回弹”(比如冲出去后,再缩回来一点,回到目标位置)。 这样的效果,比默认的ease更像“真实的物理运动”——就像你按一下弹簧,它会先弹出去,再弹回来,很自然。
4. 不用记参数!用工具可视化调整
你肯定不想记复杂的曲线值,推荐两个工具:
- cubic-bezier.com:拖动控制点,实时预览动画效果,直接复制 CSS 代码;
- Easings.net:预设了常见的缓动效果(比如
easeInBack“easeOutElastic”),直接查对应的cubic-bezier值。 比如你想做“按钮点击时的弹性缩放”,直接在 Easings.net 找easeOutElastic,对应的曲线值是cubic-bezier(0.68, -0.55, 0.27, 1.55),复制过来就能用!
八、clamp():不用媒体查询也能做“响应式字体/宽度”
最后一个函数,是 CSS3 的“响应式神器”——clamp(min, preferred, max)。它能让值在min和max之间自动适应,不用写一堆媒体查询!
1. 核心逻辑:“自适应的安全范围”
clamp(min, preferred, max)的意思很简单:
- 如果
preferred(首选值)小于min(最小值),就用min; - 如果
preferred大于max(最大值),就用max; - 否则,用
preferred(通常用vw做响应式值)。 比如你要做响应式字体:
.page-title { font-size: clamp(18px, 4vw, 32px); /* 最小18px,最大32px,中间按4vw自适应 */}2. 实际效果:字体随屏幕大小自动变化
- 当屏幕宽度小于 450px(
4vw = 18px):字体大小是 18px(不会更小); - 当屏幕宽度在 450px~800px 之间:字体大小随
vw增加(比如 500px 时,4vw=20px;600px 时,4vw=24px); - 当屏幕宽度大于 800px(
4vw=32px):字体大小固定为 32px(不会更大)。
3. 进阶:响应式容器宽度
除了字体,clamp()还能做响应式容器:
比如你有个卡片容器,希望最小 300px,最大 600px,中间占父容器的 80%:
.card-container { width: clamp(300px, 80%, 600px); /* 最小300px,最大600px,中间占父容器80% */ margin: 0 auto; /* 水平居中 */}4. 兼容性:现代浏览器都支持
clamp()支持 Chrome 79+、Firefox 75+、Safari 13.1+,几乎覆盖所有现代浏览器。旧浏览器可以用@supports降级:
@supports not (font-size: clamp(18px, 4vw, 32px)) { .page-title { font-size: 18px; /* 旧浏览器用固定值 */ @media (min-width: 800px) { font-size: 32px; /* 大屏幕用32px */ } }}九、总结:CSS 函数的“躺平哲学”
看到这里,你应该发现了:CSS 函数的核心,是“让 CSS 替你做重复的事”——
- 不用写 JS 拿文本,
attr()帮你从data属性里取; - 不用写媒体查询,
clamp()帮你做响应式; - 不用写 JS 计数,
counter()帮你自动生成序号; - 不用写 JS 动画,
cubic-bezier()帮你做自然的缓动。
最后送你三个“躺平技巧”:
- 优先用 CSS 函数:能不用 JS 就不用,CSS 的性能比 JS 好(浏览器渲染层优化);
- 注意可访问性:CSS 生成的内容要加
aria属性,保证视障用户能使用; - 用工具提效:不用记复杂的参数,用可视化工具(比如 cubic-bezier.com、clamp 计算器)帮你省时间。
CSS 的进化,从来不是“让你写更多代码”,而是“让你写更少的代码,做更多的事”。下次写 CSS 时,先问自己:“有没有函数能帮我躺平?” 你会发现,原来写样式可以这么轻松!
参考链接(进一步学习):
- MDN CSS 函数文档:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Functions
- CSS 计数器的可访问性:https://tink.uk/accessibility-support-for-css-counters/
- clamp()的最佳实践:https://css-tricks.com/using-clamp-for-responsive-design/
以上就是 8 个 CSS 函数的妙用啦!希望你能把这些技巧用到项目里,少写点 JS,多躺平会儿~ 😊
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!