Skip to content

Latest commit

 

History

History
executable file
·
467 lines (345 loc) · 22.9 KB

File metadata and controls

executable file
·
467 lines (345 loc) · 22.9 KB

Ionic

Nous allons étudier les composants d'interface d'Ionic en version 1.0.1 "vanadium-vaquita" (2015-06-30).

Il n’y a pas de documentation ou de composants dédiés à l’accessibilité dans Ionic, et l’une des seules références est une anomalie pour rendre Ionic accessible mais annulée car trop vague. On remarque que l'accessibilité n'est pas une priorité de l'équipe d'Ionic pour la version 1.0.1.

Corrections spécifiques à Ionic

Module JavaScript

Les directives d'Ionic ne sont pas très flexibles car les templates ne sont pas customisables. Ce qui veut dire que toute correction devra se faire dans une nouvelle directive en recopiant la fautive. Dans le module et les directives, Ionic a renommé les fonctions angular pour simplifier l'écriture. En recopiant une directive il faudra donc migrer certaines fonctions. Voici la liste :

  • extend = angular.extend
  • forEach = angular.forEach
  • isDefined = angular.isDefined
  • isNumber = angular.isNumber
  • isString = angular.isString
  • jqLite = angular.element
  • noop = angular.noop

Si nous copions une directive contenant forEach, il faudra rennommer la fonction en angular.forEach (Exemple avec ion-toggle).

En recopiant une directive, il faudra la renommer pour ne pas interférer avec l'ancienne. Dans notre exemple, toutes les directives recopiées ont été renommées en ajoutant -ally (exemple: ion-toggle devient ion-toggle-ally).

Module CSS

Le module CSS corrigera les règles erronées dans Ionic. Elles peuvent être de plusieurs natures :

  • Les évémements sont bloqués par la propriété pointer-events:none et les clics ne sont plus transmis à l'application JavaScript ;
  • Un élément HTML n'est pas focusable et il n'est donc pas utilisable par le lecteur d'écran ;
  • Les contrastes ne sont pas suffisants et il est nécessaire de les corriger.

Corrections d'ordre générale

Lors de l'utilisation du framework, les erreurs relevées sont :

  • Les événements du lecteur d'écran sont interceptés par Ionic sous Android 4.3 et 4.4 empêchant toute intéraction par utilisateur.
  • Il est impossible d'utiliser le scroll avec le lecteur d'écran iOS.

Correctifs appliqués pour interpréter les événements du lecteur d'écran Android

Lors des tests sous Android 4.3 et Android 4.4, il est impossible de cliquer ou de changer l’état d'une case à cocher avec talkback. Les événements sont interceptés par Ionic pour réduire le délai de latence des 300ms. http://blog.ionic.io/hybrid-apps-and-the-curse-of-the-300ms-delay/ Il faut donc désactiver cette interception en rajoutant l’attribut data-tap-disabled="true" sur la balise body.

<body ng-app="starter" data-tap-disabled="true"></body>

La désactivation du délai de tap rend l'application beaucoup plus lente lorsque le lecteur d'écran est inactif. Il faut donc corriger cette impression de latence.

Correction de l'impression de latence sous Ionic

L'une des forces de l'implémentation hybride est de pouvoir accéder à une partie de l'API native depuis notre application écrite en JavaScript. On peut par exemple connaître l'état du lecteur d'écran pour corriger une erreur inhérente au framework Ionic. Le but est de modifier l'attribut data-tap-disabled en fonction du lecteur d'écran.

angular.module('a11y-ionic', ['$mobileAccessibility'])
.run(function($rootScope, $mobileA11yScreenReaderStatus) {
  function isScreenReaderRunningCallback(event, boolean) {
    var element = document.body;
    if (boolean) {
      console.log("Screen reader: ON");
      element.setAttribute("data-tap-disabled", "true");
    } else {
      console.log("Screen reader: OFF");
      element.setAttribute("data-tap-disabled", "false");
    }
  }

  $rootScope.$on('$mobileAccessibilityScreenReaderStatus:status', isScreenReaderRunningCallback);
})

De cette façon, si le lecteur d'écran est allumé, les événements click ne seront pas bloqués par Ionic et on pourra changer l'état des checkbox. Et lorsque le lecteur d'écran sera éteint, le délai de 300 ms sera supprimé pour donner une impression d'application native.

Correction du scrolling sous iOS

Ionic fut créé lorsque les événements scroll natifs web n'étaient pas implémentés. Ils ont dû pour cela réimplémenter les événements scroll en JavaScript. Depuis, Android 4.1 a implémenté les événements scroll natifs, mais les WebViews iOS ne supportent pas cette implémentation. Donc les scrolls VoiceOver dans iOS sont interceptés par Ionic. Cela empêche de défiler correctement vers le bas avec un lecteur d'écran.

Lorsque l'on désactive le CSS, le défilement fonctionne correctement. Il faut donc trouver la classe puis la propriété en erreur pour corriger cette anomalie.

Cette recherche peut se révéler très fastidieuse voire impossible sans l'aide du déboguage distant. Avec des essais successifs, on peut se rendre compte que les classes .scroll-content et .pane sont en cause, il faut revenir en position statique. De plus en ajoutant les classes .platform-ios et .sr-on, nous pouvons faire cette correction uniquement pour la plateforme iOS ayant un lecteur d'écran actif.

.platform-ios.sr-on .pane,
.platform-ios.sr-on .scroll-content{
  position: static;
}

En ajoutant cette correction, il est nécessaire de faire un ajustement de style pour le header.

.platform-ios.sr-on .scroll-content{
  overflow-y: auto;
}

.scroll-content.has-header > .scroll {
  margin-top: 44px;
}

De cette façon, le défilement dans iOS fonctionne correctement. Néanmoins il est possible que d'autres régressions de style soient présentes dans des cas particuliers d'utilisation de Ionic. Le mieux serait une correction faite par l'équipe d'Ionic qui a une meilleure vue d'ensemble du projet.

ion Forms

Documentation des champs de formulaire Ionic : http://ionicframework.com/docs/components/#forms

Ionic fournit plusieurs styles d'input :

  • Placeholder Labels
  • Inline Labels
  • Stacked Labels
  • Floating Labels
  • Inset Forms
  • Inset Inputs
  • Input Icons
  • Header Inputs

Pour les composants Placeholder Labels, Inset Forms, Inset Inputs, Input Icons, Header Inputs, les erreurs relevées sont :

  • L'utlisation de placeholder n'est pas une alternative au label, en effet le placeholder n'est plus visible dès que l'on commence à remplir le champ ;
  • Le label est vide.

Pour le composant Floating Labels, l'erreur relevée est :

  • L'étiquette de champ et son champ associé ne sont pas accolés. L'étiquette n'est pas visible.

Pour les composants Inline Labels et Stacked Labels, l'erreur relevée est :

  • Absence de l'attribut for sur le label et l'id correspondant sur l'input.

Composant non accessible

Les composants Placeholder Labels, Inset Forms, Inset Inputs, Input Icons, Header Inputs, Floating Labels ne peuvent pas être corrigés sans changer complètement l'aspect voulu par Ionic.

Correctifs appliqués

Pour corriger les problèmes d'accessibilité sur les composants Inline Labels et Stacked Labels, nous avons ajouté l'attribut for sur le label et l'id.

Correction de l'input Inline Labels

Inline Labels

<div class="list">
  <label for="username" class="item item-input">
    <span class="input-label">Username</span>
    <input id="username" type="text">
  </label>
</div>

Correction de l'input Stacked Labels

Stacked Labels

<div class="list">
  <label for="first-name" class="item item-input item-stacked-label">
    <span class="input-label">First Name</span>
    <input id="first-name" type="text" placeholder="John">
  </label>
</div>

ion-checkbox

Documentation de ion-checkbox dans Ionic: http://ionicframework.com/docs/1.0.1/api/directive/ionCheckbox/

une liste de 3 éléments ion-checkbox

Pour le composant ion-checkbox l'erreur relevée est :

  • La checkbox n’est pas focusable avec le lecteur d’écran.

Correctifs appliqués

On va forcer la checkbox en display:block puis la rendre visible uniquement au lecteur d’écran.

body .checkbox.checkbox-input-hidden input {
  display: block !important;
}

.checkbox.checkbox-input-hidden input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  border: 0;
}

On peut effectuer les tests de restitution sous iOS et Android. La prise de focus fonctionne bien, le label est énoncé par le lecteur d’écran, son type (“case à cocher”) et son état (“non coché”).

ion-toggle

Documentation de ion-toggle dans Ionic: http://ionicframework.com/docs/1.0.1/api/directive/ionToggle/

une liste de 3 éléments ion-toggle

Pour le composant ion-toggle les erreurs relevées sont :

  • L'input checkbox n’est pas focusable avec le lecteur d’écran.
  • Le label est vide.
  • Les événements click ne sont pas envoyé sous Android 5.0.

Correctifs appliqués

On va forcer la checkbox en display:block puis la rendre visible uniquement au lecteur d’écran.

body .toggle input{
  display: block !important;
}

.toggle input{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  border: 0;
}

Nous voyons aussi que la structure HTML n’est pas correcte : le label est vide. Malheureusement le template de la directive n’est pas modifiable, nous devons recopier la directive sous un autre nom pour la modifier :

.directive('ionToggleAlly', [
  '$timeout',
  '$ionicConfig',
function($timeout, $ionicConfig) {

  return {
    restrict: 'E',
    replace: true,
    require: '?ngModel',
    transclude: true,
    template:
      '<label class="item item-toggle">' +
        '<div ng-transclude></div>' +
        '<div class="toggle">' +
          '<input type="checkbox">' +
          '<div class="track">' +
            '<div class="handle"></div>' +
          '</div>' +
        '</div>' +
      '</label>',

    compile: function(element, attr) {
      var input = element.find('input');
      angular.forEach({
        'name': attr.name,
        'ng-value': attr.ngValue,
        'ng-model': attr.ngModel,
        'ng-checked': attr.ngChecked,
        'ng-disabled': attr.ngDisabled,
        'ng-true-value': attr.ngTrueValue,
        'ng-false-value': attr.ngFalseValue,
        'ng-change': attr.ngChange,
        'ng-required': attr.ngRequired,
        'required': attr.required
      }, function(value, name) {
        if (angular.isDefined(value)) {
          input.attr(name, value);
        }
      });

      if (attr.toggleClass) {
        element[0].getElementsByTagName('div')[1].classList.add(attr.toggleClass);
      }

      element.addClass('toggle-' + $ionicConfig.form.toggle());

      return function($scope, $element) {
        var el = $element[0].getElementsByTagName('div')[1];
        var checkbox = el.children[0];
        var track = el.children[1];
        var handle = track.children[0];

        var ngModelController = angular.element(checkbox).controller('ngModel');

        $scope.toggle = new ionic.views.Toggle({
          el: el,
          track: track,
          checkbox: checkbox,
          handle: handle,
          onChange: function() {
            if (ngModelController) {
              ngModelController.$setViewValue(checkbox.checked);
              $scope.$apply();
            }
          }
        });

        $scope.$on('$destroy', function() {
          $scope.toggle.destroy();
        });
      };
    }

  };
}])

Avec ce nouveau template, le label n'est plus vide. Mais en effectuant les tests sous Android 5.0 la checkbox ne change pas d'état. En effet les événements sont bloqués par la propriété CSS pointer-events:none; sur l'item toggle. Il faut donc remettre à la valeur par défaut.

.item-toggle{
  pointer-events: auto;
}

De cette manière les événements click sont correctement interceptés.

ion-radio

Documentation de ion-radio dans Ionic: http://ionicframework.com/docs/1.0.1/api/directive/ionRadio/

une liste de 4 éléments ion-radio

Pour le composant ion-toggle les erreurs relevées sont :

  • L'input radio n'est pas focusable sous Android 5.0.
  • L'icône est focusable sous Android 5.0

Correctifs appliqués

Cette fois-ci, une erreur apparaît lors de l'activation de l'input radio sous Android 5.0, on ne peut pas changer l'état. Pour diagnostiquer l'erreur, il faut partir d'une version sans modification de style, et retirer l'intégralité des classes CSS. En ajoutant une à une les classes, nous pouvons ainsi trouver celle qui comporte la propriété CSS qui pose problème.

L'erreur vient de la propriété left: -9999px; sur l'input qui est mal interprétée par Talkback dans Android 5.0. On va donc changer la propriété à 0 et cacher l'input aux lecteurs d'écran par précaution.

.item-radio input {
  left: 0;
}
.item-radio input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  border: 0;
}

L'icône présente pour marquer l'état reste focusable mais n'est pas vocalisée sous Android 5.0. L'état étant déjà vocalisé par l'input, il est préférable de la cacher complètement avec la propriété aria-hidden="true" dans la directive.

.directive('ionRadioAlly', function() {
  return {
    restrict: 'E',
    replace: true,
    require: '?ngModel',
    transclude: true,
    template:
      '<label class="item item-radio">' +
        '<input type="radio" name="radio-group">' +
        '<div class="item-content disable-pointer-events" ng-transclude></div>' +
        '<i aria-hidden="true" class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
      '</label>',

    compile: function(element, attr) {
      if (attr.icon) {
        element.children().eq(2).removeClass('ion-checkmark').addClass(attr.icon);
      }

      var input = element.find('input');
      angular.forEach({
          'name': attr.name,
          'value': attr.value,
          'disabled': attr.disabled,
          'ng-value': attr.ngValue,
          'ng-model': attr.ngModel,
          'ng-disabled': attr.ngDisabled,
          'ng-change': attr.ngChange,
          'ng-required': attr.ngRequired,
          'required': attr.required
      }, function(value, name) {
        if (angular.isDefined(value)) {
            input.attr(name, value);
          }
      });

      return function(scope, element, attr) {
        scope.getValue = function() {
          return scope.ngValue || attr.value;
        };
      };
    }
  };
})

$ionicGesture

Nous nous basons ici sur les critères présents dans la proposition d'extension du RGAA pour les mobiles/tactiles (voir https://github.com/DISIC/referentiel-mobile-tactile/blob/master/refentiel-mobile-tactile-liste-criteres.md ).

Le critère 14.3 comporte le test suivant :

Test 14.3.1 : Chaque interaction gestuelle déclenchant une action respecte-t-elle ces conditions ?

  • l'action est déclenchée uniquement à la fin de l'interaction gestuelle ;
  • l'action n'est pas déclenchée si l'élément déclencheur perd le focus.

Le premier test invalide plusieurs gestes :

  • Le geste on-hold va déclencher l'action pendant l'appui et non à la fin de l'interaction ;
  • Le geste on-touch va déclencher l'action avant la fin de touchend ou mouseup ;
  • Les actions on-drag, on-drag-* vont déclencher l'action avant la fin de touchend ou mouseup.

De la même manière le deuxième test invalide plusieurs gestes :

  • Les gestes on-swipe, on-swipe-* peuvent être déclenchés même si le focus est perdu ;
  • Le geste on-release est déclenché peu importe où le focus se trouve.

Il reste 2 gestes valides :

  • on-tap pour les appuis courts ;
  • on-double-tap pour les doubles appuis.

$ionicModal

Documentation de $ionicModal dans Ionic: http://ionicframework.com/docs/1.0.1/api/service/$ionicModal/

La fenêtre modale Ionic affiche du contenu temporaire à l'utilisateur. On peut aussi bien afficher des actions, du contenu ou un formulaire à l'intérieur.

Pour le composant $ionicModal les erreurs relevées sont :

  • L'utilisateur utilisant un lecteur d'écran ne peut pas interagir avec les éléments à l'intérieur de la modale.
  • Le focus n'est pas renvoyé sur le premier élément à l'ouverture.
  • Le focus peut sortir de la fenêtre modale en cours d'ouverture.
  • À la fermeture le focus ne revient pas sur l'élément ayant permis d'ouvrir la fenêtre.
  • La touche Echap ne ferme pas la fenêtre.
  • Absence de l'attribut role="dialog".
  • Absence de label pour la modale.

L'attribut data-tap-disabled="true" ne permet pas de désactiver la réduction du délai de 300 ms. La désactivation du CSS et des propriétés pointer-event sont sans effet. Il est donc impossible pour un utilisateur avec un lecteur d'écran actif d'utiliser les fenêtres modales Ionic et aucune correction simple n'a été mise en évidence.

Il est préférable d'utiliser une fenêtre modale déjà accessible. AngularJs étant un framework assez flexible, on peut ajouter aussi bien une fenêtre modale jQuery, React ou AngularJS.

Le même problème se posera pour $ionicPopover et $ionicActionSheet.

Conclusion

Lors de ce tutoriel, nous remarquons bien que Ionic n'a pas été conçu pour être accessible. La première raison est sûrement l'interception des événements click pour réduire le délai de 300ms, qui empêche toutes les actions avec un lecteur d'écran. Ensuite, nous nous rendons compte qu'aucun test n'a été fait avec un lecteur d'écran VoiceOver, car il est impossible de défiler dans l'application avec un lecteur d'écran. Sur l'ensemble des composants, seulement une petit partie sera effectivement corrigeable (ion Forms: Inline Labels et Stacked Labels, ion-checkbox, ion-toggle, ion-radio) ce qui limite l'intérêt de l'utilisation d'Ionic pour créer une application accessible.

Si vous souhaitez faire une application iOS, il est préférable d'attendre que l'anomalie sur le défilement soit corrigée, il est possible d'utiliser Ionic mais cela risque d'être très chronophage en l'état et de provoquer des régressions ou des comportements inattendus.

Si vous souhaitez faire une application Android, il est très important de tester l'application sur Android > 5 et Android 4.4. Il peut y avoir beaucoup de comportements différents entre les deux versions OS, en effet la WebView n'est pas la même et l'accessibilité peut comporter des anomalies sur une seule des versions.

De manière générale, Ionic 1.0.1 n'est pas recommandable pour créer une application accessible. Il est préférable d'utiliser Cordova et de créer sa propre application. Nous pourrions éventuellement travailler sans le module JavaScript Ionic et charger seulement la feuille CSS, mais il faudrait prendre garde à la structure HTML, aux propriétés pointer-events:none et aux problèmes éventuels de contrastes.

Licence

Ce document est la propriété du Secrétariat général à la modernisation de l'action publique français (SGMAP). Il est placé sous la licence ouverte 1.0 ou ultérieure, équivalente à une licence Creative Commons BY. Pour indiquer la paternité, ajouter un lien vers la version originale du document disponible sur le compte Github de la DInSIC.