WCAG 2.5.7: Ruchy przeciągania

WCAG 2.5.7: Ruchy Przeciągania (Poziom AA)

Wprowadzenie do Kryterium WCAG 2.5.7

Kryterium sukcesu WCAG 2.5.7, znane jako „Ruchy Przeciągania”, jest częścią wytycznych WCAG 2.1 i ma na celu zapewnienie dostępności interfejsów wymagających złożonych gestów wskaźnika. Stwierdza ono: „Funkcjonalność, która wymaga od użytkownika wykonywania ruchów przeciągania, może być obsługiwana za pomocą alternatywnej metody interakcji (np. pojedynczego wskazania), chyba że przeciąganie jest niezbędne.” Jest to kryterium na poziomie AA, co oznacza, że jest wymagane dla większości organizacji dążących do wysokiej zgodności z WCAG.

Głównym celem tego kryterium jest usunięcie barier dla osób, które mają trudności z precyzyjnym lub ciągłym sterowaniem myszką, trackpadem, ekranem dotykowym lub innym urządzeniem wskazującym. Obejmuje to zarówno długie, ciągłe ruchy, jak i utrzymywanie naciśnięcia podczas przesuwania.

Dlaczego to Kryterium jest Ważne? (Wpływ na Dostępność)

Wymóg wykonywania ruchów przeciągania może stanowić znaczącą barierę dla wielu grup użytkowników. Zapewnienie alternatywnych metod interakcji jest kluczowe dla stworzenia inkluzywnego doświadczenia:

  • Użytkownicy z niepełnosprawnościami ruchowymi: Osoby z drżeniem rąk, ograniczoną sprawnością manualną, chorobami takimi jak choroba Parkinsona, czy paraliżem mogą mieć ogromne trudności z precyzyjnym utrzymaniem wskaźnika i wykonaniem ciągłego ruchu przeciągania. Proste kliknięcie jest dla nich znacznie łatwiejsze.
  • Użytkownicy korzystający z alternatywnych metod wprowadzania: Osoby używające specjalistycznego sprzętu, takiego jak myszy sterowane głową, trackball’e, przełączniki, sterowanie wzrokiem lub inne technologie wspomagające, mogą mieć ograniczone możliwości wykonywania skomplikowanych gestów przeciągania. Często te urządzenia symulują tylko pojedyncze kliknięcia.
  • Użytkownicy urządzeń z trudnymi w obsłudze interfejsami: Nawet osoby bez zdiagnozowanej niepełnosprawności mogą napotkać trudności, korzystając z małych trackpadów na laptopach, nieprecyzyjnych ekranów dotykowych, lub w sytuacjach, gdy precyzja jest utrudniona (np. podczas podróży, w warunkach wibracji).
  • Użytkownicy z niepełnosprawnościami poznawczymi: Dla niektórych osób zrozumienie i wykonanie złożonego gestu przeciągania może być trudniejsze niż prosty, bezpośredni gest (np. kliknięcie).

Zapewnienie alternatywy dla przeciągania nie tylko zwiększa dostępność, ale często poprawia ogólną użyteczność dla wszystkich użytkowników, oferując większą elastyczność w interakcji z interfejsem.

Kryterium Sukcesu i Wymagania WCAG 2.5.7

Oficjalne brzmienie kryterium sukcesu 2.5.7 Ruchy Przeciągania jest następujące:

2.5.7 Ruchy Przeciągania (Poziom AA): Funkcjonalność, która wymaga od użytkownika wykonywania ruchów przeciągania, może być obsługiwana za pomocą alternatywnej metody interakcji (np. pojedynczego wskazania), chyba że przeciąganie jest niezbędne.

Kluczowe elementy tego kryterium to:

  • „Funkcjonalność, która wymaga […] ruchów przeciągania”: Obejmuje to wszelkie operacje, gdzie użytkownik musi nacisnąć i przytrzymać element, a następnie przesunąć wskaźnik, aby aktywować lub zakończyć daną funkcję (np. sortowanie elementów metodą „drag and drop”, zmiana rozmiaru okna/elementu poprzez przeciąganie uchwytu, rysowanie linii).
  • „może być obsługiwana za pomocą alternatywnej metody interakcji”: Oznacza to, że musi istnieć co najmniej jedna inna metoda osiągnięcia tej samej funkcji, która nie wymaga przeciągania. Preferowaną alternatywą jest „pojedyncze wskazanie” (np. kliknięcie, naciśnięcie, aktywacja klawiszem).
  • „chyba że przeciąganie jest niezbędne”: Istnieją bardzo rzadkie przypadki, gdy sam ruch przeciągania jest integralną częścią funkcji i nie można go logicznie zastąpić. Przykłady mogą obejmować interfejsy do precyzyjnego rysowania odręcznego, malowania lub komponowania muzyki, gdzie charakter ruchu jest kluczowy dla samej twórczości. W większości typowych zastosowań webowych przeciąganie nie jest „niezbędne”. Nawet w przypadku rysowania, często możliwe jest np. rysowanie punkt po punkcie, co nie wymaga ciągłego przeciągania.

Należy pamiętać, że przewijanie strony czy elementu (scrollowanie) nie jest uważane za „ruch przeciągania” w kontekście tego kryterium, ponieważ jest to standardowa funkcja przeglądarki lub systemu operacyjnego, dostępna za pomocą wielu metod (kółko myszy, klawisze strzałek, paski przewijania, gesty dotykowe).

Praktyczne Wskazówki do Zgodności

Aby spełnić kryterium 2.5.7, należy wziąć pod uwagę następujące wytyczne:

  • Zawsze oferuj alternatywę: Jeśli jakakolwiek funkcja wymaga przeciągania, zapewnij równoważną metodę interakcji, która opiera się na prostszych gestach, takich jak kliknięcia, naciśnięcia lub interakcje klawiaturowe.
  • Projektuj intuicyjne alternatywy: Upewnij się, że alternatywne metody są łatwe do znalezienia i zrozumienia. Mogą to być przyciski „przenieś w górę/dół”, pola wyboru z przyciskami „zastosuj”, menu kontekstowe lub pola do wprowadzania wartości.
  • Nie ukrywaj alternatyw: Alternatywne metody interakcji powinny być widoczne lub łatwo dostępne, a nie ukryte w menu, które wymaga wielu kroków do znalezienia.
  • Zapewnij ekwiwalentną funkcjonalność: Alternatywna metoda powinna pozwalać na wykonanie tej samej akcji z podobną skutecznością jak przeciąganie. Oznacza to, że nie może być znacznie wolniejsza czy bardziej skomplikowana.
  • Dostępność klawiatury: Upewnij się, że wszystkie alternatywne metody są w pełni dostępne z klawiatury.
  • Testowanie: Regularnie testuj interfejs z różnymi urządzeniami wskazującymi i technologiami wspomagającymi, aby upewnić się, że funkcje wymagające przeciągania są dostępne dla wszystkich.

Przykłady Implementacji

Przykład 1: Sortowanie elementów listy

Typowym scenariuszem, gdzie wykorzystuje się przeciąganie, jest sortowanie listy elementów (np. lista zadań, kolejność produktów). Należy zapewnić alternatywną metodę zmiany kolejności.

Niezgodna Implementacja (tylko przeciąganie)

W tym przykładzie użytkownik może zmienić kolejność elementów listy tylko poprzez przeciąganie ich myszką.

<ul id="sortableList">
  <li draggable="true">Element A</li>
  <li draggable="true">Element B</li>
  <li draggable="true">Element C</li>
</ul>

<style>
  #sortableList { list-style-type: none; padding: 0; }
  #sortableList li {
    padding: 10px;
    margin-bottom: 5px;
    border: 1px solid #ccc;
    background-color: #f9f9f9;
    cursor: grab;
  }
</style>

<script>
  const list = document.getElementById('sortableList');
  let draggedItem = null;

  list.addEventListener('dragstart', (e) => {
    draggedItem = e.target;
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', e.target.textContent);
    setTimeout(() => e.target.style.opacity = '0.5', 0);
  });

  list.addEventListener('dragend', (e) => {
    e.target.style.opacity = '1';
    draggedItem = null;
  });

  list.addEventListener('dragover', (e) => {
    e.preventDefault();
    const afterElement = getDragAfterElement(list, e.clientY);
    const currentDragged = document.querySelector('.dragging');
    if (afterElement == null) {
      list.appendChild(draggedItem);
    } else {
      list.insertBefore(draggedItem, afterElement);
    }
  });

  function getDragAfterElement(container, y) {
    const draggableElements = [...container.querySelectorAll('li:not(.dragging)')];
    return draggableElements.reduce((closest, child) => {
      const box = child.getBoundingClientRect();
      const offset = y - box.top - box.height / 2;
      if (offset < 0 && offset > closest.offset) {
        return { offset: offset, element: child };
      } else {
        return closest;
      }
    }, { offset: Number.NEGATIVE_INFINITY }).element;
  }
</script>
Zgodna Implementacja (przeciąganie z alternatywą)

Oprócz funkcji przeciągania, każdy element listy posiada przyciski „Przenieś w górę” i „Przenieś w dół”, które umożliwiają zmianę kolejności za pomocą kliknięcia lub klawiatury.

<ul id="sortableListAlt">
  <li>
    <span>Element A</span>
    <button class="move-up" aria-label="Przenieś Element A w górę">&#9650;</button>
    <button class="move-down" aria-label="Przenieś Element A w dół">&#9660;</button>
  </li>
  <li>
    <span>Element B</span>
    <button class="move-up" aria-label="Przenieś Element B w górę">&#9650;</button>
    <button class="move-down" aria-label="Przenieś Element B w dół">&#9660;</button>
  </li>
  <li>
    <span>Element C</span>
    <button class="move-up" aria-label="Przenieś Element C w górę">&#9650;</button>
    <button class="move-down" aria-label="Przenieś Element C w dół">&#9660;</button>
  </li>
</ul>

<style>
  #sortableListAlt { list-style-type: none; padding: 0; }
  #sortableListAlt li {
    padding: 10px;
    margin-bottom: 5px;
    border: 1px solid #ccc;
    background-color: #f9f9f9;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  #sortableListAlt li span { flex-grow: 1; }
  #sortableListAlt button { margin-left: 5px; cursor: pointer; }
</style>

<script>
  const listAlt = document.getElementById('sortableListAlt');

  listAlt.addEventListener('click', (e) => {
    const target = e.target;
    if (target.classList.contains('move-up')) {
      const currentItem = target.closest('li');
      const prevItem = currentItem.previousElementSibling;
      if (prevItem) {
        listAlt.insertBefore(currentItem, prevItem);
      }
    } else if (target.classList.contains('move-down')) {
      const currentItem = target.closest('li');
      const nextItem = currentItem.nextElementSibling;
      if (nextItem) {
        listAlt.insertBefore(nextItem, currentItem);
      }
    }
    // TODO: Add drag-and-drop functionality here if desired, alongside the buttons.
    // For WCAG 2.5.7, having the buttons is sufficient.
    // Full drag-and-drop script similar to the incorrect example would go here if needed.
  });
</script>

Przykład 2: Zmiana rozmiaru elementu

Elementy interfejsu, których rozmiar można zmieniać poprzez przeciąganie uchwytu, również wymagają alternatywy.

Niezgodna Implementacja (tylko przeciąganie)

W tym przykładzie użytkownik może zmienić rozmiar bloku tylko poprzez przeciąganie uchwytu w prawym dolnym rogu.

<div id="resizableBox">
  <div class="handle"></div>
</div>

<style>
  #resizableBox {
    width: 200px;
    height: 150px;
    border: 2px solid #333;
    position: relative;
    background-color: #e0e0e0;
    overflow: hidden;
  }
  .handle {
    width: 15px;
    height: 15px;
    background-color: #333;
    position: absolute;
    bottom: 0;
    right: 0;
    cursor: se-resize;
  }
</style>

<script>
  const resizableBox = document.getElementById('resizableBox');
  const handle = resizableBox.querySelector('.handle');
  let isResizing = false;

  handle.addEventListener('mousedown', (e) => {
    isResizing = true;
    document.addEventListener('mousemove', resize);
    document.addEventListener('mouseup', stopResize);
  });

  function resize(e) {
    if (!isResizing) return;
    const newWidth = e.clientX - resizableBox.getBoundingClientRect().left;
    const newHeight = e.clientY - resizableBox.getBoundingClientRect().top;
    resizableBox.style.width = Math.max(50, newWidth) + 'px';
    resizableBox.style.height = Math.max(50, newHeight) + 'px';
  }

  function stopResize() {
    isResizing = false;
    document.removeEventListener('mousemove', resize);
    document.removeEventListener('mouseup', stopResize);
  }
</script>
Zgodna Implementacja (przeciąganie z alternatywą)

Oprócz uchwytu do przeciągania, dostępne są również pola do wprowadzania wartości szerokości i wysokości, które umożliwiają precyzyjną zmianę rozmiaru za pomocą klawiatury lub prostego kliknięcia/naciśnięcia.

<div id="resizableBoxAlt">
  <div class="handle"></div>
</div>
<div style="margin-top: 10px;">
  <label for="boxWidth">Szerokość:</label>
  <input type="number" id="boxWidth" value="200" min="50"> px
  <label for="boxHeight">Wysokość:</label>
  <input type="number" id="boxHeight" value="150" min="50"> px
</div>

<style>
  #resizableBoxAlt {
    width: 200px;
    height: 150px;
    border: 2px solid #333;
    position: relative;
    background-color: #e0e0e0;
    overflow: hidden;
  }
  #resizableBoxAlt .handle {
    width: 15px;
    height: 15px;
    background-color: #333;
    position: absolute;
    bottom: 0;
    right: 0;
    cursor: se-resize;
  }
  input[type="number"] { width: 60px; margin-right: 10px; }
</style>

<script>
  const resizableBoxAlt = document.getElementById('resizableBoxAlt');
  const handleAlt = resizableBoxAlt.querySelector('.handle');
  const boxWidthInput = document.getElementById('boxWidth');
  const boxHeightInput = document.getElementById('boxHeight');
  let isResizingAlt = false;

  // Initial update of inputs based on box size
  boxWidthInput.value = resizableBoxAlt.offsetWidth;
  boxHeightInput.value = resizableBoxAlt.offsetHeight;

  handleAlt.addEventListener('mousedown', (e) => {
    isResizingAlt = true;
    document.addEventListener('mousemove', resizeAlt);
    document.addEventListener('mouseup', stopResizeAlt);
  });

  function resizeAlt(e) {
    if (!isResizingAlt) return;
    const newWidth = e.clientX - resizableBoxAlt.getBoundingClientRect().left;
    const newHeight = e.clientY - resizableBoxAlt.getBoundingClientRect().top;
    resizableBoxAlt.style.width = Math.max(50, newWidth) + 'px';
    resizableBoxAlt.style.height = Math.max(50, newHeight) + 'px';
    boxWidthInput.value = Math.round(Math.max(50, newWidth));
    boxHeightInput.value = Math.round(Math.max(50, newHeight));
  }

  function stopResizeAlt() {
    isResizingAlt = false;
    document.removeEventListener('mousemove', resizeAlt);
    document.removeEventListener('mouseup', stopResizeAlt);
  }

  boxWidthInput.addEventListener('change', () => {
    const val = parseInt(boxWidthInput.value);
    if (!isNaN(val) && val >= 50) {
      resizableBoxAlt.style.width = val + 'px';
    } else {
      boxWidthInput.value = resizableBoxAlt.offsetWidth; // Revert if invalid
    }
  });

  boxHeightInput.addEventListener('change', () => {
    const val = parseInt(boxHeightInput.value);
    if (!isNaN(val) && val >= 50) {
      resizableBoxAlt.style.height = val + 'px';
    } else {
      boxHeightInput.value = resizableBoxAlt.offsetHeight; // Revert if invalid
    }
  });
</script>

Najlepsze Praktyki i Typowe Pułapki

Najlepsze Praktyki

  • Priorytetyzuj alternatywy: Jeśli to możliwe, rozważ, czy przeciąganie jest naprawdę najbardziej efektywną metodą interakcji. Czasami proste kliknięcia lub przyciski są lepsze dla wszystkich.
  • Zapewnij jasne instrukcje: Jeśli funkcja przeciągania jest nadal preferowaną metodą, ale istnieją alternatywy, upewnij się, że użytkownicy wiedzą o ich istnieniu. Możesz to zrobić poprzez krótkie teksty pomocy, tooltipy lub instrukcje w dokumentacji.
  • Testuj z użytkownikami: Najlepszym sposobem na zapewnienie dostępności jest przeprowadzenie testów użyteczności z osobami, które korzystają z technologii wspomagających lub mają różnego rodzaju niepełnosprawności. Ich opinie są bezcenne.
  • Projektuj responsywnie: Upewnij się, że alternatywne metody są dobrze zaprojektowane i działają prawidłowo na różnych urządzeniach i rozmiarach ekranu.
  • Wizualne wskazówki: Nawet jeśli przeciąganie jest wspierane, rozważ dodanie wizualnych wskazówek, które ułatwią zrozumienie interakcji, np. zmiana kursora, podświetlenie miejsca docelowego.

Typowe Pułapki

  • Brak alternatywy: Najczęstszym błędem jest całkowite pominięcie alternatywnej metody interakcji, polegając wyłącznie na przeciąganiu.
  • Ukryte alternatywy: Alternatywna metoda jest dostępna, ale użytkownik musi wykonać wiele kroków lub szukać jej, aby ją znaleźć (np. głęboko zagnieżdżone w menu kontekstowym).
  • Gorsza alternatywa: Alternatywa jest tak nieefektywna, wolna lub skomplikowana w użyciu, że praktycznie nie spełnia swojej funkcji jako równoważna metoda.
  • Przewijanie jako przeciąganie: Błędne uznawanie standardowego przewijania elementów (scrollowania) za „ruch przeciągania” objęty tym kryterium. Przewijanie jest uważane za funkcję systemu operacyjnego/przeglądarki i jest domyślnie dostępne w wielu formach.

Podsumowanie

Kryterium WCAG 2.5.7 „Ruchy Przeciągania” podkreśla znaczenie elastyczności interakcji w nowoczesnych interfejsach użytkownika. Zapewniając alternatywne metody dla funkcji wymagających przeciągania, projektanci i deweloperzy mogą znacząco zwiększyć dostępność swoich stron i aplikacji, otwierając je na szersze grono użytkowników. Pamiętaj, aby zawsze myśleć o różnorodności sposobów, w jaki ludzie wchodzą w interakcję z technologią i dążyć do tworzenia doświadczeń, które są intuicyjne i dostępne dla wszystkich.

Przegląd prywatności

Ta strona korzysta z ciasteczek, aby zapewnić Ci najlepszą możliwą obsługę. Informacje o ciasteczkach są przechowywane w przeglądarce i wykonują funkcje takie jak rozpoznawanie Cię po powrocie na naszą stronę internetową i pomaganie naszemu zespołowi w zrozumieniu, które sekcje witryny są dla Ciebie najbardziej interesujące i przydatne.