Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Thêm/xóa nhiều class cùng lúc với classList trong HTML và JavaScript


classList API cơ bản

element.classList trả về DOMTokenList - tập hợp các class với các method:

  • add(...classes) - Thêm một hoặc nhiều class
  • remove(...classes) - Xóa một hoặc nhiều class
  • toggle(class, force) - Bật/tắt class
  • contains(class) - Kiểm tra có class không
  • replace(oldClass, newClass) - Thay thế class
  • item(index) - Lấy class tại index

Thêm/xóa nhiều class một lần (Modern API)

Từ Chrome 61+, Firefox 55+, Safari 10.1+, Edge 16+ hỗ trợ multiple arguments:

const el = document.getElementById('myElement');

// Thêm nhiều class
el.classList.add('class1', 'class2', 'class3');

// Xóa nhiều class
el.classList.remove('class1', 'class2', 'class3');

// Toggle nhiều class (mỗi class独立 toggle)
el.classList.toggle('active');
el.classList.toggle('visible', true); // force add
el.classList.toggle('hidden', false); // force remove

Polyfill cho trình duyệt cũ (IE11, Safari cũ)

// Polyfill add/remove multiple cho DOMTokenList
if (!DOMTokenList.prototype.addMany) {
  DOMTokenList.prototype.addMany = function(classes) {
    const classArray = typeof classes === 'string' ? classes.split(/\s+/) : classes;
    classArray.forEach(cls => {
      if (cls) this.add(cls);
    });
  };
}

if (!DOMTokenList.prototype.removeMany) {
  DOMTokenList.prototype.removeMany = function(classes) {
    const classArray = typeof classes === 'string' ? classes.split(/\s+/) : classes;
    classArray.forEach(cls => {
      if (cls) this.remove(cls);
    });
  };
}

if (!DOMTokenList.prototype.toggleMany) {
  DOMTokenList.prototype.toggleMany = function(classes, force) {
    const classArray = typeof classes === 'string' ? classes.split(/\s+/) : classes;
    classArray.forEach(cls => {
      if (cls) this.toggle(cls, force);
    });
  };
}

// Sử dụng
element.classList.addMany('foo bar baz');
element.classList.removeMany(['foo', 'bar']);
element.classList.toggleMany('active visible', true);

Utility Functions hiện đại (Không cần polyfill prototype)

// Helper functions - an toàn, không modify prototype
const classUtil = {
  // Thêm nhiều class từ string hoặc array
  add(el, ...classes) {
    const flat = classes.flat().filter(Boolean);
    el.classList.add(...flat);
    return el;
  },
  
  // Xóa nhiều class
  remove(el, ...classes) {
    const flat = classes.flat().filter(Boolean);
    el.classList.remove(...flat);
    return el;
  },
  
  // Toggle nhiều class
  toggle(el, classes, force) {
    const classArray = typeof classes === 'string' ? classes.split(/\s+/) : classes;
    classArray.forEach(cls => el.classList.toggle(cls, force));
    return el;
  },
  
  // Thay thế: xóa old, thêm new
  replace(el, oldClasses, newClasses) {
    const oldArr = typeof oldClasses === 'string' ? oldClasses.split(/\s+/) : oldClasses;
    const newArr = typeof newClasses === 'string' ? newClasses.split(/\s+/) : newClasses;
    oldArr.forEach(cls => el.classList.remove(cls));
    newArr.forEach(cls => el.classList.add(cls));
    return el;
  },
  
  // Set classes chính xác (xóa tất cả, thêm mới)
  set(el, ...classes) {
    const flat = classes.flat().filter(Boolean);
    el.className = ''; // Clear all
    if (flat.length) el.classList.add(...flat);
    return el;
  },
  
  // Kiểm tra có bất kỳ class nào trong danh sách
  hasAny(el, ...classes) {
    const flat = classes.flat().filter(Boolean);
    return flat.some(cls => el.classList.contains(cls));
  },
  
  // Kiểm tra có TẤT CẢ class trong danh sách
  hasAll(el, ...classes) {
    const flat = classes.flat().filter(Boolean);
    return flat.every(cls => el.classList.contains(cls));
  }
};

// Sử dụng
classUtil.add(element, 'foo', 'bar', 'baz');
classUtil.add(element, ['foo', 'bar'], 'baz'); // Array cũng được
classUtil.remove(element, 'foo bar'); // String space-separated
classUtil.toggle(element, 'active visible', true);
classUtil.replace(element, 'old-class', 'new-class');
classUtil.set(element, 'only', 'these', 'classes');

Ví dụ thực tế: State-based Classes

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>ClassList Multi Demo</title>
  <style>
    .card { 
      width: 300px; padding: 1.5rem; border-radius: 12px; 
      transition: all 0.3s ease; margin: 1rem auto;
      border: 2px solid transparent;
    }
    .card.loading { opacity: 0.6; pointer-events: none; }
    .card.error { border-color: #e74c3c; background: #fdf2f2; }
    .card.success { border-color: #27ae60; background: #f0fff4; }
    .card.editing { border-color: #3498db; box-shadow: 0 0 0 3px rgba(52,152,219,0.2); }
    .card.compact { padding: 0.75rem; }
    .card.hidden { display: none; }
    
    .btn-group { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; margin: 1rem; }
    button { padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; background: #3498db; color: white; }
    button.active { background: #27ae60; }
    button.danger { background: #e74c3c; }
  </style>
</head>
<body>
  <div id="card" class="card">
    <h3>Trạng thái Card</h3>
    <p id="status">Normal</p>
    <div class="class-list" style="font-family: monospace; font-size: 0.8rem; color: #666;"></div>
  </div>
  
  <div class="btn-group">
    <button onclick="setState('normal')">Normal</button>
    <button onclick="setState('loading')">Loading</button>
    <button onclick="setState('error')">Error</button>
    <button onclick="setState('success')">Success</button>
    <button onclick="setState('editing')">Editing</button>
    <button onclick="setState('compact')">Compact</button>
    <button class="danger" onclick="setState('hidden')">Hidden</button>
    <button onclick="setState('visible')">Show</button>
  </div>

  <script>
    const card = document.getElementById('card');
    const statusEl = document.getElementById('status');
    const classListEl = document.querySelector('.class-list');
    
    const states = {
      normal: [],
      loading: ['loading'],
      error: ['error'],
      success: ['success'],
      editing: ['editing'],
      compact: ['compact'],
      hidden: ['hidden'],
      visible: [] // Will remove hidden
    };
    
    function setState(stateName) {
      // Remove all known state classes
      const allStateClasses = Object.values(states).flat();
      card.classList.remove(...allStateClasses);
      
      // Add new state classes
      const newClasses = states[stateName] || [];
      if (newClasses.length) {
        card.classList.add(...newClasses);
      }
      
      // Special: visible removes hidden
      if (stateName === 'visible') {
        card.classList.remove('hidden');
      }
      
      // Update UI
      statusEl.textContent = stateName.charAt(0).toUpperCase() + stateName.slice(1);
      classListEl.textContent = 'Classes: ' + (card.className || '(none)');
    }
    
    // Demo add/remove multiple
    function demoAddRemove() {
      const el = document.createElement('div');
      el.className = 'base-class';
      
      // Add multiple
      el.classList.add('a', 'b', 'c');
      console.log('After add:', el.className); // "base-class a b c"
      
      // Remove multiple
      el.classList.remove('a', 'c');
      console.log('After remove:', el.className); // "base-class b"
      
      // Toggle multiple
      el.classList.toggle('x');
      el.classList.toggle('y', true); // force add
      el.classList.toggle('z', false); // force remove
      console.log('After toggle:', el.className); // "base-class b x y"
    }
    
    demoAddRemove();
  </script>
</body>
</html>

Performance Tips

  • classList.add(...classes) một lần tốt hơn gọi nhiều lần add()
  • Tránh đọc className rồi set lại (gây reflow) - dùng classList
  • Batch DOM reads/writes: đọc hết xong mới write
  • Dùng requestAnimationFrame cho animation class changes

Browser Support

MethodChromeFirefoxSafariEdge
add/remove/toggle (single)8+3.6+5.1+12+
add/remove (multiple args)61+55+10.1+16+
replace()61+49+10.1+17+