Tooltip a11y et SEO

    Objectif d'un « tooltip » accessible

    L'idée de départ est de pouvoir concilier accessibilité, design et référencement avec les fameuses « infobulles » surgissantes sur les pages web.

    Habituellement, ce type de présentation fonctionne sur les événements mouseover et mouseout, ce qui rend l'effet inopérant en navigation au clavier. De plus, les lecteurs d'écran n'interagissent pas (ou mal) avec ce type d'objet. Bien entendu, la bonne pratique est d'alimenter le code avec l'attribut title

    Le fonctionnement de ce tooltip tente donc de concilier toutes ces petites choses.

    Pour le markup :

    <span class='button button-white' data-tooltip title="Lorem ipsum dolore ">Passer la souris ici</span>

    ce qui produit ceci : Passer la souris ici

    ou si la notion de SEO n'est pas utile

    <span data-tooltip="Le contenu de mon tooltip :<br/>(on peut y mettre du <strong <strong style='color:var(--green) !important'>CODE HTML</strong>)">Passer la souris ici</span>

    ce qui produit ceci : Passer la souris ici

    Toute proposition d'amélioration est bien sûr la bienvenue !
    Ça se passe ici : contact

    Les sources

    Si vous souhaitez participer, même modestement, au maintien du site et pour son usage, vous pouvez cliquer sur le joli bouton…

    Utilisation

    // Appel sur un $(document).ready()

    // placer un tooltip sur toutes les balises `abbr`
            $('abbr').attr('data-tooltip','');

    // placer un tooltip sur des balises spécifiques
            $('[data-tooltip]').tooltip();

    Source Javascript $.fn.tooltip

    ;(async ($) => {

        'use strict';

        $.focusable = async function( tag, element_ ) {

            let is_focusable = false;

            if ( /^(input|select|textarea|button|object)$/.test( tag ) ) {

                is_focusable = !element_.disabled;

            } else if ( 'a' === tag ) {

                is_focusable = element_.attr('href') !== undefined;
            }

            if(element_.attr('contenteditable') !== undefined) {
                is_focusable = $.attr(element_, 'contenteditable') !== false;
            }

            return is_focusable && element_.is( ":visible" );
        };

        $.fn.tooltip = async function(o) {

            if('ontouchstart' in window) {
                return;
            }

            let tooltip = $('#tooltip');


            if(!$('#tooltip').length) {
                tooltip = $('<div/>', {
                    'id': 'tooltip',
                    'role': 'tooltip',
                    'aria-live': 'assertive',
                    'aria-hidden': 'true'
                });
                DOM.body.append(tooltip);

            }

            let settings = $.extend({
                    'attribute': 'data-tooltip',
                    'css': null
                }, o),

                setdelay = 1000;

            $(this).each(function() {

                let element_ = $(this),
                    tooltipContent = element_.attr(settings.attribute) || '';

                if(!tooltipContent.length) {

                    if(element_.attr('title') !== undefined) {
                        element_.data({
                            'content': element_.attr('title'),
                            'type': 'title',
                            'css': settings.css
                        });
                    } else {
                        element_.data({
                            'content': null,
                            'type': null
                        });
                    }

                } else {

                    element_.data({
                        'content': tooltipContent,
                        'type': settings.attribute,
                        'css': settings.css
                    });

                }

                if(element_.data('content') != null) {

                    (element_.attr('tabindex') === undefined && !$.focusable(element_.tagName(), element_)) && element_.attr({
                        'tabindex': '0'
                    });

                    element_.attr({
                        'aria-describedby': 'tooltip'
                    }).on({

                        'mouseover focus': function (event_1) {

                            let eventType = event_1.type;

                            tooltip.removeAttr('style, css').html(element_.data('content'));

                            (element_.data('css') != null && tooltip.addClass(element_.data('css')));

                            if (eventType === 'mouseover') {
                                element_.on('click', function() {
                                    element_.off('focus');
                                });
                                event_1.stopPropagation();
                            }

                            element_.data('type') === 'title' ? element_.data('title', element_.data('content')) : element_.data('title', null);

                            element_.removeAttr('title');

                            let x = (element_.offset().left + (element_.outerWidth() / 2)).toFixed(0),
                                y = element_.offset().top + element_.outerHeight(),
                                HTMLouterWidth = DOM.html.outerWidth(true) / 2;

                            element_.data('class', element_.offset().left > HTMLouterWidth ? 'right' : 'left');


                            tooltip.delay(setdelay).queue(function () {

                                let scrolltop = DOM.document.scrollTop(),
                                    tool_width = tooltip.outerWidth(true),
                                    tool_height = tooltip.outerHeight(true),
                                    limit_right = (DOM.window.outerWidth() - tool_width) + parseInt(DOM.html.css('padding-left'), 10) + parseInt(DOM.html.css('padding-right'), 10),
                                    reversing = (y + tool_height - scrolltop + 25) > DOM.window.height();

                                reversing ? tooltip.addClass('reverse') : tooltip.removeClass('reverse');

                                (x -= (tool_width / 2)),
                                    (y += 10),
                                    ((x < 15) && (x = 15)),
                                    ((x > limit_right) && (x = limit_right - 30)),
                                    (reversing && (y -= element_.outerHeight() + tool_height + 20));

                                tooltip
                                    .attr({'aria-hidden': 'false'})
                                    .css({
                                        'z-index': $.zindex(),
                                        'left': x + 'px',
                                        'top': (y - scrolltop) + 'px',
                                        'visibility': 'visible'
                                    })
                                    .dequeue();

                            });

                        },

                        'mouseout blur click': function() {

                            (element_.data('title') != null && element_.attr({
                                    'title': element_.data('title')
                                })
                            );

                            tooltip.dequeue().css({
                                'visibility': 'hidden'
                            }).empty().attr({
                                'aria-hidden': 'true'
                            });

                        }
                    });
                }
                return this;

            });
        };

    })(jQuery);

    Source CSS

    #tooltip {

        --tooltip-color: var(--default, rgba(255, 100));

        border-radius: 0.25rem;
        font-size: 1rem;
        line-height: 1.25rem;
        padding: 0.5rem 0.625rem;
        position: fixed;
        text-align: left;
        visibility: hidden;
        font-family: sans-serif;
        font-weight: 400;
        min-width: 12.5rem;
        max-width: 25vw;
    }

    #tooltip:not([class*=tooltip-]) {
        background-color: var(--tooltip-color);
        color: var(--option-background-color, rgba(0, 100));
        box-shadow: 0 0 0 1px rgba(100, 50) inset;
    }

    /*
    #tooltip:after, #tooltip:before {
        bottom: calc(100% - 1px);
        left: 50%;
        border: solid transparent;
        content: " ";
        height: 0;
        width: 0;
        position: absolute;
        pointer-events: none;
        border-color: rgba(0, 0);
        border-width: 0.625rem;
        font-size: 0;
        -webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);
    }

    #tooltip:before {
        bottom: 100%;
        border-width: 0.625rem;
    }
    */

    /* Default color */
    /*
    #tooltip:not([class*=tooltip-]):after {
        border-bottom-color: var(--tooltip-color);
    }
    */
    /*
    #tooltip:not([class*=tooltip-]):before {
        border-bottom-color: rgba(255, 100);
    }
    */

    /* init reverse on bottom windows */
    /*
    #tooltip.reverse:after {
        top: calc(100% - 1px);
        border-bottom-color: rgba(0, 0);
    }

    #tooltip.reverse:before {
        top: 100%;
        border-bottom-color: rgba(0, 0);
    }

    #tooltip.reverse:not([class*=tooltip-]):after {
        border-top-color: var(--tooltip-color);
    }
    */
    /*
    #tooltip.reverse:not([class*=tooltip-]):before {
        border-top-color: rgba(255, 100);
    }
    */


    /* white */
    /*
    #tooltip.tooltip-white:after {
        border-bottom-color: rgba(255, 100);
    }
    #tooltip.tooltip-white:before {
        border-bottom-color: rgba(0, 40);
    }
    /* white reverse */
    /*
    #tooltip.tooltip-white.reverse:after {
        border-top-color: rgba(255, 100);
    }
    #tooltip.tooltip-white.reverse:before {
        border-top-color: rgba(0, 40);
    }
    */

    Nécessite la fonction $.zindex()

    $.zindex = function (fromDomNode) {
        var r = [];
        $("*", (fromDomNode == null ? $('body') : fromDomNode )).each(function () {
            var z = parseInt($(this).css('z-index'), 10);
            !isNaN(z) && r.push(z)
        });
        var last = r.sort(function (a, b) {
            return a-b
        }).reverse()[0];

        if(last == undefined) {
            last =    0;
        }
        return last + 1;

    };
    Soutenez Aquinum
    Voir aussi…
    • Vérifier un numéro de carte bancaire Vérification de carte bancaire, script de validation de carte bancaire. Contrôle de format de numéro de carte bancaire, algorithme de Luhn carte de crédit Visa, Mastercard, Maestro, CB, Carte Bancaire, Discover Card, American Express, JCB Cards
    • Owl Carousel multiple Gérer plusieurs carrousels/sliders sur une seule page avec Owl Carousel 2. Manage multiple carousels/sliders on one page with Owl Carousel 2
    • Conversion SVG/CSS Encodage SVG pour CSS compatible Sass et exemple d'intégration SVG via xlink compatible CSS. SVG encoder, base64 SVG pour CSS.
    • Optimisation SVG Intégration SVG via xlink/use/symbol, factorisation de symbol SVG
    • Responsive SVG Utilisation des media queries internes au SVG
    • LazyLoad Intersection Observer LazyLoad avec IntersectionObserver API pour images, picture, iframe, etc.
    • Design, flux et accessibilité jQuery Responsive layout logical DOM plugin
    • Lightbox Responsive et Accessible Lightbox images jQuery Responsive accessible a11y. Automatisation d'une Lightbox multipages. Lightbox image unique ou multiple. Lightbox responsive et accessible a11y wcag
    • Tooltip (infobulle) a11y Tooltip (infobulle) compatible a11y et SEO, naviguable au clavier, accessible par lecteur d'écran
    • Zipcode patterns Contrôle automatisé de codes postaux internationaux
    • Responsive background-image jQuery responsive background-image plugin
    • Drapeaux ISO 3366 SVG Drapeaux nationaux vectoriels normés ISO alpha 2, alpha 3 et numérotation Organisation des Nations Unies (ONU). SVG drapeaux pour internationalisation de sites multilingues.
    • Layout rigolo Une autre approche pour définir un layout de page responsive.
    • Générateur de noms API de génération de couples prénom/nom aléatoires.
    2020-07-12