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;
        }

/*
        if(typeof body != 'object') {
            var body = $('body:first');
        }

        if(typeof html != 'object') {
            var html = $('html');
        }
*/

        var window_ = $(window),
            body = typeof body != 'object' ? $('body').first() : body,
            html = typeof html != 'object' ? $(':root') : html;


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

        } else {
            var tooltip = $('#tooltip');
        }


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

            document_ = $(document),
            setdelay = 1000;

        $(this).each(function() {

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

            if(tooltipContent.length == 0) {

                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');

                    var x = (element_.offset().left + (element_.outerWidth()/2)).toFixed(0),
                        y = element_.offset().top + element_.outerHeight(),
                        html_width = html.outerWidth(true),
                        positionclass = element_.offset().left > (html_width/2) ? 'right' : 'left';

                    element_.data('class', element_.offset().left > html_width/2 ? 'right' : 'left');


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

                        var scrolltop = document_.scrollTop(),
                            tool_width = tooltip.outerWidth(true),
                            tool_height = tooltip.outerHeight(true),
                            limit_right = (window_.outerWidth() - tool_width) + parseInt(html.css('padding-left'), 10) + parseInt(html.css('padding-right'), 10),
                            reversing = (y + tool_height - scrolltop + 25) > 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();

                        ( eventType == 'mouseover' && document_.off('mousemove') );

                    });

                }).on('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: 400;
    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-09-15