Kortic, Anthony Ladeuil

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 data-tooltip title="Lorem ipsum dolore ">Lorem</span>

    ce qui produit ceci : Lorem

    ou si la notion de SEO n'est pas utile

    <span data-tooltip="Le contenu de mon tooltip (on peut y mettre du <strong>CODE HTML</strong>)">Lorem</span>

    ce qui produit ceci : Lorem

    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

    ;(function ($) {

        'use strict';

        $.focusable = function( tag, element_ ) {

                var 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 = function(o) {

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

            var tooltip = $('#tooltip');


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

            }

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

                setdelay = 1000;

            $(this).each(function() {

                var 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',
                        'focusable': 'true'
                    });

                    element_.attr({
                        'aria-describedby': 'tooltip'
                    }).on({
                        'mouseover focus': function (event_1) {

                            var 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 () {

                                var 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: #444;
        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: 500;
        min-width: 12.5rem;
        max-width: 25vw;
    }

    #tooltip:not([class*=tooltip-]) {
        background-color: var(--tooltip-color);
        color: #ffffff;
    }

    /*
    #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;

    };
    Voir aussi…
    2019-11-22