2026年都快到了,还在用老套路写CSS?这些新特性到底该怎么上手?

5,091字
22–32 分钟
in

截至2025年底,CSS领域涌现出一批能彻底改变开发体验的新特性,比如基于条件的视图过渡、更严谨的组件可访问性标注,以及各类惊艳的图形与文字特效。本文将以实操经验为核心,手把手拆解这些新玩意儿如何落地到真实项目里,把那些看似高大上的概念,变成日常开发中随手就能用的“真功夫”。

目录

玩转条件视图过渡

概念唠明白

视图过渡(View Transitions)是让页面在不同状态间切换时能产生丝滑动画效果的API。条件视图过渡,顾名思义,就是根据不同的情况(比如URL变了、某个按钮被点了)触发不一样的过渡动画。过去这事儿得靠JavaScript写一堆if...else来判断,但现在CSS这边也开始发力,未来有望直接通过CSS规则来匹配,省去不少手写逻辑的麻烦。

实操走起

假设现在要搞个博客列表页,点击不同分类时,文章卡片区域要有不同的出场动画。比如点“技术”分类,卡片从左边滑入;点“生活”分类,卡片从右边淡入。

第一步: 先把基础的视图过渡结构搭好。在需要触发过渡的地方,用JavaScript把状态更新包裹在document.startViewTransition里。

// 切换分类的函数
function switchCategory(category) {
  // 开启视图过渡
  const transition = document.startViewTransition(() => {
    // 这里是实际更新DOM的操作
    updateArticleList(category);
  });
}

第二步: 在CSS里,通过::view-transition-old::view-transition-new这两个伪元素来定义过渡前后的样式。关键是给不同的过渡场景命名不同的“视图过渡名称”。

/* 给文章列表容器起个名字 */
.article-list {
  view-transition-name: article-list;
}

/* 定义从左边滑入的动画 */
@keyframes slide-from-left {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* 定义从右边淡入的动画 */
@keyframes fade-from-right {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

第三步:startViewTransition的回调里,给容器加上一个临时类名,用来标记当前是哪种分类。这样在CSS里就能根据这个类名来动态改变view-transition-name或者直接覆盖过渡动画。

function switchCategory(category) {
  const container = document.querySelector('.article-list');
  // 加个临时类,标记分类
  container.classList.add(`category-${category}`);

  const transition = document.startViewTransition(() => {
    updateArticleList(category);
  });

  // 过渡结束后移除临时类
  transition.finished.finally(() => {
    container.classList.remove(`category-${category}`);
  });
}

第四步: 在CSS里,针对不同的临时类,覆盖默认的过渡动画。

/* 默认的动画是淡入淡出,现在根据分类覆盖 */
.category-tech .article-list::view-transition-new(article-list) {
  animation: slide-from-left 0.3s ease;
}

.category-life .article-list::view-transition-new(article-list) {
  animation: fade-from-right 0.3s ease;
}

避坑指南

这里有个很容易踩的坑:view-transition-name必须是唯一的,同一个时刻页面上不能有两个元素拥有相同的view-transition-name。如果列表页存在多个类似的容器,得给每个容器动态生成唯一的名称,比如article-list-1article-list-2。还有,过渡动画里涉及到的元素,如果它的尺寸在过渡前后变化很大,最好给容器设置一个固定的宽高,或者用contain: layout,防止动画过程中布局抖动。另外,如果未来CSS原生支持导航匹配,就不用这么麻烦地加临时类了,但现阶段这种“半自动”的做法依然是最稳的。

可访问性组件标注法

概念唠明白

设计系统里的组件,光长得好看可不行,得让用屏幕阅读器的人也能顺畅使用。可访问性组件标注,就是把这些组件的角色、状态、交互方式,用一种“机器也能看懂”的语言给描述出来。这可不是简单的加个alt属性,而是要深入到HTML结构、ARIA属性、键盘操作逻辑,甚至包括缩放时的表现,把这些都变成一套可复用的“可访问性令牌”(Accessibility Tokens)。

实操走起

就拿最常见的“按钮”组件来举例。一个普通的按钮,如果只是<div class="btn">点我</div>,那对于辅助技术来说,它就是一块不可交互的废铁。

第一步: 从基础HTML开始,必须使用语义化的<button>元素。这是所有可访问性的地基。

<button class="ui-button">提交订单</button>

第二步: 给按钮加上必要的ARIA属性,尤其是当按钮的状态不是“可点击”的默认状态时。比如一个“加载中”的按钮,得明确告诉屏幕阅读器“我现在正在处理,别急”。

<button class="ui-button" aria-busy="true">
  <span>处理中</span>
  <!-- 加载图标,用aria-hidden隐藏掉 -->
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

这里aria-busy="true"就相当于给按钮贴了个“正在忙”的标签。同样,如果按钮是禁用状态,别只用disabled属性,还得确保它仍然能被焦点捕捉到(如果需要的话),并且用aria-disabled来辅助说明。

第三步: 定义一套命名规范。现在越来越多人推荐给语义化类名加个前缀,比如ui-。这样一来,.ui-button.ui-card,一看就知道这是设计系统里的核心组件,方便在全局样式里统一管理。这招特别适合在大型项目里跟第三方样式或业务样式做隔离,防止样式污染,也方便写自动化测试脚本时精准定位。

第四步: 把键盘导航逻辑写进组件里。比如一个下拉菜单按钮,得监听EnterSpace键来展开菜单,用Escape键来收起。这些逻辑要封装在组件的JavaScript里,别指望用户自己去琢磨。

class UIButton {
  constructor(element) {
    this.button = element;
    this.button.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        this.activate();
      }
    });
  }
}

第五步: 考虑缩放场景。当用户把浏览器页面放大到200%时,按钮里的文字不能跑出边界,也不能被截断。这就要求给按钮设置一个合理的min-width,并用overflow-wrap: break-word来让长单词换行。这些样式规则,也应该作为“可访问性令牌”的一部分,写进组件文档里。

.ui-button {
  min-width: 4rem; /* 保证足够宽,能容纳放大后的文字 */
  overflow-wrap: break-word;
  /* 确保有足够的对比度,WCAG要求至少4.5:1 */
  color: #ffffff;
  background-color: #1a73e8;
}

避坑指南

标注的时候,最容易忽略的是“动态变化”的状态。比如一个展开/收起的按钮,它的aria-expanded属性必须随着实际状态同步更新,光靠CSS样式变化是不够的。另外,ARIA属性的值要尽可能简洁,比如aria-label别写成“点击这里来提交你刚才在表单里填写的信息”,直接写“提交订单”就行,啰嗦反而会干扰阅读效率。还有,千万别把tabindex设成大于0的值,这会破坏页面的自然焦点顺序,让键盘用户直接懵圈。

SVG与CSS打造卡通文字

概念唠明白

“卡通文字”效果,就是那种像老动画片里,文字有描边、有阴影、甚至带点立体感的字体样式。过去要实现这种效果,得掏出一堆滤镜、叠加层,或者直接上图片。现在用CSS的paint-order属性,再加上SVG的滤镜,就能用几行代码让文字“活”起来。paint-order这个属性,可以控制文字绘制时先描边还是先填色,从而实现那种“描边在外,填色在内”的经典卡通感。

实操走起

目标:做出一段黄底红边、带点外发光效果的“新年快乐”文字,让它看起来就像是从老动画片里蹦出来的。

第一步: 写HTML结构,用一个<h1>就够。

<h1 class="cartoon-text">新年快乐</h1>

第二步: 在CSS里给文字加描边和填充,用上paint-order

.cartoon-text {
  font-size: 6rem;
  font-weight: bold;
  font-family: 'Impact', 'Arial Black', sans-serif;
  color: #ffcc00; /* 亮黄色填充 */
  -webkit-text-stroke: 4px #ff3300; /* 红色描边 */
  paint-order: stroke fill; /* 关键:先画描边,再画填充 */
}

这样一设置,描边就会出现在填充色的外围,而不是盖住填充色。那种经典的“描边包裹填充”的效果就出来了。

第三步: 加上一点立体感,用text-shadow来搞。

.cartoon-text {
  /* ... 前面的样式 ... */
  text-shadow: 
    2px 2px 0 #aa2222,  /* 右下深色阴影,营造立体感 */
    4px 4px 0 #662222;   /* 再远一点,更深 */
}

第四步: 如果想搞点更炫酷的效果,比如文字边缘像融化一样抖动,可以用SVG的滤镜。先在HTML里定义一个SVG滤镜,位置随便放,比如放在页面最底下。

<svg style="position: absolute; width: 0; height: 0;">
  <defs>
    <filter id="gooey-effect">
      <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur" />
      <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9" result="gooey" />
      <feComposite in="SourceGraphic" in2="gooey" operator="atop" />
    </filter>
  </defs>
</svg>

第五步: 在CSS里给文字加上这个滤镜。注意,要加滤镜的元素必须是“有边界”的元素,比如不能是纯内联元素,可以把<h1>设成display: inline-blockblock

.cartoon-text {
  /* ... 前面的样式 ... */
  filter: url(#gooey-effect);
  display: inline-block;
}

这个“gooey-effect”滤镜会让文字的边缘产生一种像融化巧克力一样的粘稠效果,特别适合那种卡通风格的动态标题。

避坑指南

使用paint-order时,要留意它的兼容性。虽然主流浏览器基本都支持了,但不同版本对描边宽度的解析可能略有差异,用-webkit-text-stroke时最好在多个浏览器里预览一下。SVG滤镜效果很吃性能,如果页面上有大量文字同时应用了这种滤镜,滚动时可能会掉帧。建议只在标题或重要文字上少量使用。另外,滤镜里的stdDeviation值不是越大越好,太大会让文字糊成一团,要根据文字大小反复调试,找个平衡点。