ניהול פוקוס לנגישות אתר: יישום מלא

אובדן פוקוס בעבודה עם חלונות מודאליים ותוכן דינמי הופך את האתר לבלתי נגיש למשתמשי מקלדת ומסכי קריאה. אנו מיישמים ניהול פוקוס תקין בהתאם לתקני WCAG ומספקים את הפרויקט במפתח מלא, מהביקורת ועד לתמיכה. הצוות שלנו מבטיח ניווט מקלדת יציב כך שהאתר שלך נגיש לכולם.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
ניהול פוקוס לנגישות אתר: יישום מלא
בינוני
מ- 1 יום עד 3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

יישום ניהול פוקוס לנגישות אתרים

לאחר סגירת חלון מודאלי, הפוקוס של קורא המסך אובד והמשתמש אינו יכול להמשיך בניווט. לפי ההערכות שלנו, למעלה מ-70% מממשקי SPA סובלים מבעיות בניהול פוקוס מקלדת. זו בעיה קלאסית: האתר מקבל תלונות ונכשל בביקורות. ב-90% מהמקרים, יישום של כמה דפוסים מבטל 80% מהתלונות. אנו עוזרים ליישם בקרת פוקוס נכונה. במהלך עבודתנו, ערכנו למעלה מ-50 ביקורות נגישות וזיהינו מלכודות נפוצות. לצוות שלנו יש 5 שנות ניסיון בנגישות והוא השלים למעלה מ-100 ביקורות. הבעיה השכיחה ביותר היא אובדן פוקוס בעת סגירת חלונות מודאליים (65% מהפרויקטים). השנייה היא העדר העברת פוקוס לאחר ניווט SPA (45%). בעיות אלו נפתרות באמצעות הוקים מותאמים אישית. סטטיסטית, 80% מבעיות המקלדת קשורות לאובדן פוקוס.

ניהול פוקוס פותר בעיות רבות

פוקוס נכון הוא הבסיס לממשקים דינמיים נגישים. הנה תרחישים אופייניים שבהם זה קריטי:

  • חלון מודאלי: בעת פתיחה, הפוקוס בתוך החלון; בעת סגירה, חזרה לכפתור שהפעיל אותו.
  • ניווט SPA: בעת שינוי נתיב, הפוקוס עובר לכותרת או לתוכן הראשי.
  • אימות טופס: לאחר שליחה, הפוקוס עובר לשדה הראשון עם שגיאה.
  • תוכן דינמי: לאחר טעינת בלוק חדש, הפוקוס מוצב על האלמנט הראשון הניתן לניהול.
  • מחיקת אלמנט: אם אלמנט נמחק, הפוקוס עובר לאלמנט הבא או הקודם.

כל דפוס דורש גישה נפרדת, אך כולם מכוונים לצפות היכן המשתמש מצפה שהפוקוס יהיה לאחר פעולה.

בהתבסס על למעלה מ-50 ביקורות, מצאנו: השגיאות הנפוצות ביותר הן אי-החזרת פוקוס לכפתור המפעיל (65% מהפרויקטים), שימוש ב-function useModal() { const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef<HTMLButtonElement>(null); const modalRef = useRef<HTMLDivElement>(null); const open = useCallback(() => { setIsOpen(true); }, []); const close = useCallback(() => { setIsOpen(false); // Вернуть фокус на элемент, открывший модалку triggerRef.current?.focus(); }, []); // Перенести фокус в модалку при открытии useEffect(() => { if (isOpen) { const firstFocusable = modalRef.current?.querySelector<HTMLElement>( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); firstFocusable?.focus(); } }, [isOpen]); return { isOpen, open, close, triggerRef, modalRef }; } function DeleteConfirmation({ item }) { const { isOpen, open, close, triggerRef, modalRef } = useModal(); return ( <> <button ref={triggerRef} onClick={open}> Удалить {item.name} </button> {isOpen && ( <div role="dialog" aria-modal="true" aria-labelledby="modal-title" ref={modalRef} > <h2 id="modal-title">Подтвердите удаление</h2> <p>Удалить «{item.name}»? Это действие необратимо.</p> <button onClick={() => { deleteItem(item.id); close(); }}> Удалить </button> <button onClick={close}>Отмена</button> </div> )} </> ); } ב-React (40%), ואי-טיפול במחיקת אלמנטים (30%).

כיצד אנו מיישמים ניהול פוקוס ב-React

בפרויקטים שלנו, אנו משתמשים בהוקים מותאמים אישית — זה מעביר את הלוגיקה מחוץ לרכיבים ומפשט את הבדיקות. להלן דוגמה מלאה של // useFocusOnNavigate.ts export function useFocusOnNavigate() { const location = useLocation(); useEffect(() => { // Маленькая задержка — дать React отрендерить новую страницу const timer = setTimeout(() => { const main = document.getElementById('main-content'); if (main) { main.focus(); main.scrollIntoView(); } }, 50); return () => clearTimeout(timer); }, [location.pathname]); } עם החזרת פוקוס.

function useModal() {
  const [isOpen, setIsOpen] = useState(false);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const modalRef = useRef<HTMLDivElement>(null);

  const open = useCallback(() => {
    setIsOpen(true);
  }, []);

  const close = useCallback(() => {
    setIsOpen(false);
    // Вернуть фокус на элемент, открывший модалку
    triggerRef.current?.focus();
  }, []);

  // Перенести фокус в модалку при открытии
  useEffect(() => {
    if (isOpen) {
      const firstFocusable = modalRef.current?.querySelector<HTMLElement>(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      firstFocusable?.focus();
    }
  }, [isOpen]);

  return { isOpen, open, close, triggerRef, modalRef };
}

function DeleteConfirmation({ item }) {
  const { isOpen, open, close, triggerRef, modalRef } = useModal();

  return (
    <>
      <button ref={triggerRef} onClick={open}>
        Удалить {item.name}
      </button>
      {isOpen && (
        <div
          role="dialog"
          aria-modal="true"
          aria-labelledby="modal-title"
          ref={modalRef}
        >
          <h2 id="modal-title">Подтвердите удаление</h2>
          <p>Удалить «{item.name}»? Это действие необратимо.</p>
          <button
            onClick={() => {
              deleteItem(item.id);
              close();
            }}
          >
            Удалить
          </button>
          <button onClick={close}>Отмена</button>
        </div>
      )}
    </>
  );
}

אנו גם מוסיפים function Form() { const [errors, setErrors] = useState<Record<string, string>>({}); const firstErrorRef = useRef<HTMLElement | null>(null); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); const validationErrors = validate(formData); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); // Перенести фокус на первое поле с ошибкой const firstErrorField = document.querySelector('[aria-invalid="true"]'); (firstErrorField as HTMLElement)?.focus(); } }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="email">Email</label> <input id="email" type="email" aria-invalid={!!errors.email} aria-describedby={errors.email ? 'email-error' : undefined} /> {errors.email && ( <span id="email-error" role="alert"> {errors.email} </span> )} </div> </form> ); } לכל התוכן מחוץ לחלון המודאלי כדי למנוע מטכנולוגיות מסייעות לקרוא תוכן לא פעיל, בהתאם ל-WCAG 2.1.

מדוע useRef עדיף על getElementById

ב-React, function TodoList() { const [items, setItems] = useState(initialItems); const itemRefs = useRef<Record<number, HTMLButtonElement>>({}); const deleteItem = (id: number, index: number) => { setItems(prev => prev.filter(item => item.id !== id)); // Перенести фокус на следующий элемент, или на предыдущий если удалили последний setTimeout(() => { const newItems = items.filter(item => item.id !== id); const focusIndex = Math.min(index, newItems.length - 1); if (focusIndex >= 0) { itemRefs.current[newItems[focusIndex].id]?.focus(); } }, 0); }; return ( <ul> {items.map((item, index) => ( <li key={item.id}> {item.text} <button ref={el => { if (el) itemRefs.current[item.id] = el; }} onClick={() => deleteItem(item.id, index)} aria-label={`Удалить: ${item.text}`} > × </button> </li> ))} </ul> ); } עדיף על document.getElementById. סיבה: SSR: בשרת אין DOM, ו-getElementById יזרוק שגיאה. כמו כן, useRef נותן גישה לאלמנט לאחר ההרכבה ללא צורך בחיפוש לפי סלקטור בכל פעם. זה חשוב במיוחד כאשר הרכיב מוצג בפורטל.

קריטריון useRef document.getElementById
בטוח ל-SSR כן לא
ביצועים ללא חיפוש DOM חיפוש DOM
עקביות קוד כן סלקטורים מפוזרים
יכולת בדיקה קל למדל ref קשה

ניהול פוקוס בניווט SPA

עבור React Router, אנו משתמשים בהוק שמעביר פוקוס ל-#main-content לאחר שינוי כתובת URL:

// useFocusOnNavigate.ts
export function useFocusOnNavigate() {
  const location = useLocation();
  useEffect(() => {
    // Маленькая задержка — дать React отрендерить новую страницу
    const timer = setTimeout(() => {
      const main = document.getElementById('main-content');
      if (main) {
        main.focus();
        main.scrollIntoView();
      }
    }, 50);
    return () => clearTimeout(timer);
  }, [location.pathname]);
}

אותה טכניקה עובדת ב-Next.js עם App Router וב-Vue עם Vue Router.

אימות טופס: פוקוס על השגיאה הראשונה

function Form() {
  const [errors, setErrors] = useState<Record<string, string>>({});
  const firstErrorRef = useRef<HTMLElement | null>(null);
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    const validationErrors = validate(formData);
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      // Перенести фокус на первое поле с ошибкой
      const firstErrorField = document.querySelector('[aria-invalid="true"]');
      (firstErrorField as HTMLElement)?.focus();
    }
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? 'email-error' : undefined}
        />
        {errors.email && (
          <span id="email-error" role="alert">
            {errors.email}
          </span>
        )}
      </div>
    </form>
  );
}

חשוב: לשדה עם שגיאה חייבים להיות aria-invalid="true" ו-aria-describedby עבור הודעת השגיאה. להודעה חייב להיות role="alert". זה מספק משוב נכון לטכנולוגיות מסייעות.

מחיקת אלמנט מרשימה

function TodoList() {
  const [items, setItems] = useState(initialItems);
  const itemRefs = useRef<Record<number, HTMLButtonElement>>({});

  const deleteItem = (id: number, index: number) => {
    setItems(prev => prev.filter(item => item.id !== id));
    // Перенести фокус на следующий элемент, или на предыдущий если удалили последний
    setTimeout(() => {
      const newItems = items.filter(item => item.id !== id);
      const focusIndex = Math.min(index, newItems.length - 1);
      if (focusIndex >= 0) {
        itemRefs.current[newItems[focusIndex].id]?.focus();
      }
    }, 0);
  };

  return (
    <ul>
      {items.map((item, index) => (
        <li key={item.id}>
          {item.text}
          <button
            ref={el => {
              if (el) itemRefs.current[item.id] = el;
            }}
            onClick={() => deleteItem(item.id, index)}
            aria-label={`Удалить: ${item.text}`}
          >
            ×
          </button>
        </li>
      ))}
    </ul>
  );
}

המפתח הוא לדעת את האינדקס של האלמנט שנמחק ולהעביר את הפוקוס לאלמנט הסמוך. אם האחרון נמחק, יש לעבור לקודם.

כיצד ליישם ניהול פוקוס ב-5 שלבים

  1. ביקורת על המצב הנוכחי: זיהוי כל הרכיבים שבהם הפוקוס אובד.
  2. בחירת אסטרטגיה: קביעת השיטה לכל דפוס (הוקים, refs).
  3. יישום הוקים: כתיבה ובדיקה של הוקים מותאמים אישית.
  4. שילוב ברכיבים: החלפת קריאות מפוזרות בגישה אחידה.
  5. בדיקה עם קוראי מסך: אימות עם NVDA, JAWS, VoiceOver.

כמה זמן לוקח היישום?

ניהול פוקוס בסיסי (מודאליים, ניווט SPA) לוקח 2–3 ימי עבודה. מחיר: $2,500. מערכת מלאה המטפלת בכל הדפוסים (טפסים, מחיקה, בלוקים דינמיים) לוקחת 4–5 ימים. מחיר: $4,500. לקוחות חוסכים בממוצע $3,000 בעבודות חוזרות ו-$1,500 בבדיקות נגישות. לוחות הזמנים תלויים בארכיטקטורת הפרויקט ובכמות הרכיבים הקיימים.

מה כלול בעבודה

  • תיעוד של דפוסי פוקוס
  • גישה למאגר הקוד עם ההוקים
  • מפגש הדרכה אחד לצוות שלך
  • שבועיים של תמיכה בדוא"ל
  • בדיקה עם קוראי מסך אמיתיים (NVDA, VoiceOver)

לאחר המסירה, אנו נשארים לתמיכה — עוזרים בשאלות ושיפורים. צור קשר לביקורת של הפרויקט שלך. קבל ייעוץ — ננתח את הפרויקט שלך. הזמן ביקורת נגישות לאתר שלך כבר עכשיו.

מידע נוסף על ניהול פוקוס

ניהול פוקוס הוא חלק מקריטריון ההצלחה 2.4.3 של WCAG 2.1. הוא מבטיח שכאשר רכיב מאבד פוקוס, האלמנט הבא שניתן לפוקוס יהיה צפוי.

מידע נוסף ניתן למצוא בתיעוד MDN: תפקיד dialog ב-ARIA.

ניהול פוקוס ב-React באמצעות useRef ומאפייני aria חיוני לניווט מקלדת ולתמיכה בקוראי מסך בחלונות מודאליים ובניווט SPA, בהתאם להנחיות WCAG.