CSS函数有多香?8个无JS妙用让你告别冗余代码

3860 字
19 分钟
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-captionid="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-rightmargin-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)。它能让值在minmax之间自动适应,不用写一堆媒体查询!

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. 实际效果:字体随屏幕大小自动变化#

  • 当屏幕宽度小于 450px4vw = 18px):字体大小是 18px(不会更小);
  • 当屏幕宽度在 450px~800px 之间:字体大小随vw增加(比如 500px 时,4vw=20px;600px 时,4vw=24px);
  • 当屏幕宽度大于 800px4vw=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()帮你做自然的缓动。
    最后送你三个“躺平技巧”:
  1. 优先用 CSS 函数:能不用 JS 就不用,CSS 的性能比 JS 好(浏览器渲染层优化);
  2. 注意可访问性:CSS 生成的内容要加aria属性,保证视障用户能使用;
  3. 用工具提效:不用记复杂的参数,用可视化工具(比如 cubic-bezier.com、clamp 计算器)帮你省时间。
    CSS 的进化,从来不是“让你写更多代码”,而是“让你写更少的代码,做更多的事”。下次写 CSS 时,先问自己:“有没有函数能帮我躺平?” 你会发现,原来写样式可以这么轻松!

参考链接(进一步学习)

以上就是 8 个 CSS 函数的妙用啦!希望你能把这些技巧用到项目里,少写点 JS,多躺平会儿~ 😊

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

CSS函数有多香?8个无JS妙用让你告别冗余代码
https://blog.fridolph.top/posts/2023-08-16__css-fn1/
作者
Fridolph
发布于
2023-09-16
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录