JS使用classList动态添加、移除和切换CSS类名

作者:小圆点

时间:2026-11-04

浏览:298

深入讲解JavaScript中使用classList接口动态添加、移除、切换与检查CSS类名的具体用法、运行机制、代码示例与工程注意事项。

文章目录
JS使用classList动态添加、移除和切换CSS类名
图 0:JavaScript Element.classList 接口与样式表状态响应架构图

在前端交互开发中,通过 JavaScript 改变界面外观的标准途径是切换 CSS 类名,而不是直接在行内写入 style。在现代浏览器环境中,操纵类名最简洁、安全的方式是使用 DOM 元素提供的 classList 属性。本文将具体介绍 classList 的核心运行原理,并通过完整可运行的代码演示如何使用它实现类名的添加、移除与双向条件切换。

一、认识 classList 与传统 className 的底层差异

在早期 Web 开发中,修改类名通常依赖 element.className。由于 className 暴露的是单一字符串,添加新类名时必须手动拼接空格(如 element.className += ' active'),而移除类名则需要编写正则表达式进行精确匹配与空隙修剪。如果多段业务逻辑同时对同一元素操作,稍有不慎就会因缺少空格导致类名粘连(例如将 btn 误拼成 btnactive),或者发生全量覆盖导致其它模块添加的类名意外丢失。

Element.classList 彻底解决了这一问题。它是一个只读属性,返回一个实时的 DOMTokenList 集合。集合内部以空格为分隔符将 class 特性解析为一组离散、有序且不重复的标识令牌(Tokens)。开发者可以直接调用 add()、remove()、toggle() 等标准化原生方法对指定类名执行增删改查,无需关心空格拼接、去重与正则匹配细节。

JS使用classList动态添加、移除和切换CSS类名
图 1:传统 className 字符串映射与现代 classList (DOMTokenList) 集合模型架构对比
实时性提示(Live Collection):classList 返回的 DOMTokenList 是与底层 DOM 实时绑定的。一旦 DOM 上的 class 属性发生变化,classList 内部的序列与长度会立即同步更新。

二、基础操作:add() 与 remove() 的具体执行与步骤

在日常业务开发中,最常见的需求是在触发事件(如用户点击、接口请求开始与完成)时为元素附加或解除特定样式标识。classList.add() 与 classList.remove() 分别承担这一职责。

JS使用classList动态添加、移除和切换CSS类名
图 2:classList.add() 与 classList.remove() 动态执行与状态同步步骤流程

1. 获取目标元素与基础调用步骤

在调用任何 classList 方法之前,必须确保成功取得了有效的 DOM 元素引用。以下是完整的调用示范:

// 1. 获取目标元素节点
const submitButton = document.querySelector('#submit-btn');

// 2. 防御性校验
if (submitButton) {
  // 3. 动态添加单个类名:按钮进入加载状态
  submitButton.classList.add('is-loading');

  // 4. 模拟异步操作结束后移除该类名
  setTimeout(() => {
    submitButton.classList.remove('is-loading');
  }, 2000);
}

2. 批量添加与批量移除

add() 与 remove() 原生支持同时传入多个参数,每个参数对应一个独立的类名。在执行时,浏览器会原子化地将它们同步到底层属性中,避免多次触发重排:

const card = document.querySelector('.card-item');

// 批量添加多个类名(以逗号分隔多个字符串参数)
card.classList.add('shadow-lg', 'border-primary', 'is-featured');

// 批量移除多个类名
card.classList.remove('shadow-sm', 'border-muted', 'is-draft');
入参格式注意:批量添加时,必须作为多个参数传入(如 add('a', 'b'))。严禁在单个字符串中包含空格(如 add('a b')),否则浏览器会直接抛出 DOMException: Failed to execute 'add' on 'DOMTokenList': The token provided ('a b') contains HTML space characters。

3. 幂等性安全保证

classList.add() 和 classList.remove() 均具备良好的幂等性。如果对同一个元素重复调用 add('active'),classList 内部会自动忽略已存在的类名,不会产生重复项;同理,如果使用 remove('not-exist') 移除一个并不存在的类名,代码会静默通过,不会产生任何运行时报错。

三、状态控制:toggle() 的双向切换与条件控制

在诸如展开/折叠面板、暗黑主题切换、弹窗遮罩显隐等具有二元开闭特性的场景中,直接使用 toggle() 可以省去手动判断当前状态的大量模板代码。

JS使用classList动态添加、移除和切换CSS类名
图 3:classList.toggle() 单参数双向切换与双参数条件控制分支状态机

1. 基础双向切换

在单参数模式下,toggle() 会检查元素是否已经包含该类名:若存在,则将其移除并返回 false;若不存在,则将其添加并返回 true。返回值可直接用于后续逻辑判断:

const menuDrawer = document.querySelector('#mobile-menu');
const toggleBtn = document.querySelector('#menu-toggle-btn');

toggleBtn.addEventListener('click', () => {
  // 切换菜单的打开状态,并捕获当前结果
  const isOpen = menuDrawer.classList.toggle('menu-open');
  
  // 根据返回值同步修改按钮的无障碍属性
  toggleBtn.setAttribute('aria-expanded', String(isOpen));
});

2. 第二个参数 force:由条件直接断言类名存在性

toggle() 的精髓在于其支持第二个可选参数 force(布尔值)。当传入 force 时,无论元素当前包含与否:

  • 如果 force 计算为 true,则强制添加该类名(等同于 add());
  • 如果 force 计算为 false,则强制移除该类名(等同于 remove())。

该特性能够以单行代码优雅替代冗长的 if...else 结构。例如监听页面滚动距离来为导航栏固定阴影:

const navBar = document.querySelector('header.navbar');

window.addEventListener('scroll', () => {
  // 当页面垂直滚动超过 60px 时添加 scrolled 类,否则移除
  // 无需自行编写 if (scrollY > 60) add() else remove()
  navBar.classList.toggle('scrolled', window.scrollY > 60);
});

四、辅助方法:contains() 与 replace()

除了增删改,classList 还内置了状态检测与就地替换方法,使得对样式状态的操作体系完全闭环。

1. 使用 contains() 检查特定类名

element.classList.contains('class-name') 返回布尔值,用于精准判断元素当前是否具备某个特定样式标记。与正则表达式相比,contains() 能够严格匹配独立的 token,绝不会发生前缀或子串误判:

const dialog = document.querySelector('#confirm-dialog');

if (dialog.classList.contains('is-visible')) {
  console.log('对话框当前处于可见交互状态');
}

2. 使用 replace() 进行原子级类名置换

当组件从一种互斥状态流转到另一种状态时(例如从浅色模式过渡到深色模式、或者从等待状态切换到成功状态),使用 replace(oldToken, newToken) 能够一步完成旧类名的移除与新类名的注入:

const statusBadge = document.querySelector('.status-badge');

// 将 'status-pending' 原子替换为 'status-success'
const replaced = statusBadge.classList.replace('status-pending', 'status-success');

if (!replaced) {
  // 若原类名不存在,replace 会返回 false 且不做任何修改
  statusBadge.classList.add('status-success');
}

五、classList 接口规范总结与开发避坑指南

为了便于在工程实践中快速核对,下表及图示归纳了 classList 主要 API 的入参、返回值与行为准则:

JS使用classList动态添加、移除和切换CSS类名
图 4:classList 接口全景方法速查矩阵与工程最佳实践规范
方法名称 入参规范 返回值类型 关键行为与边界保障
add(...) 1 个或多个类名字符串 undefined 自动去重,幂等安全;禁止包含空格。
remove(...) 1 个或多个类名字符串 undefined 不存在时静默跳过,幂等安全;禁止包含空格。
toggle(name, [force]) 类名字符串 + [可选布尔值] boolean 单参数时双向切换;双参数时依据 force 布尔值强制增删。
contains(name) 单一类名字符串 boolean 严格匹配单一 Token,不修改 DOM,无副作用。
replace(old, new) 两个类名字符串 boolean 旧类存在则置换并返 true;旧类不存在则返 false。

常见避坑要点:

  1. 不可对 classList 属性直接赋值:classList 属性是只读的,执行 element.classList = 'new-class' 在非严格模式下会被静默忽略,在严格模式("use strict")下会直接抛出类型错误(TypeError)。如需整串重置,应改用 element.className = 'new-class'。
  2. 从数组导入类名应使用展开运算符:如果有一组存储在数组中的类名列表,切勿直接将数组传入 add(array)。正确做法是使用 ES6 展开运算符:element.classList.add(...classListArray)。
  3. 遍历集合需留意实时变动:DOMTokenList 提供了类似于数组的 length 属性与 forEach 遍历方法,但它并不是真正的 JavaScript 原生数组。在循环遍历 classList 的同时对其执行 remove() 会实时改变集合长度,导致索引偏移。如果需要在循环中修改类名,建议先通过 Array.from(element.classList) 制作静态副本后再遍历。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章