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 sur la bienvenue !
Ça se passe ici : contact

Source Javascript $.fn.tooltip

https://www.kortic.com/v2.tooltip.js /*******************************************************************
* JS [pretty source] file updated on Fri, 15 Mar 2019 11:33:10 +0100
* File https://www.kortic.com/v2.tooltip.js
*******************************************************************/

/* 50_tooltip.js***************************************************/ 
;(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-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);

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

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 CSS

https://www.kortic.com/v2.tooltip.css @charset "UTF-8";
/********************************************************************
* CSS [pretty source] file updated on Sun, 24 Mar 2019 21:46:38 +0100
* File https://www.kortic.com/v2.tooltip.css
********************************************************************/

/* 11_tooltip.css***************************************************/ 
#tooltip {
    border-radius: var(--radius);
    font-weight: normal;
    font-size: smaller;
    line-height: 1.0625rem;
    padding: 0.25rem 0.4375rem;
    position: fixed;
    text-align: left;
    visibility: hidden;
    font-family: sans-serif;
    font-weight: 100;
    max-width: 12.5rem;
}

#tooltip:not([class*=tooltip-]) {
    background-color: rgb(20, 125, 125);
    color: rgba(255,255,255, 0.9);
}

#tooltip.tooltip-white {
    background-color: rgba(255,255,255, 1);
    color: black;
    box-shadow: 0 0 0 1px rgba(0,0,0, 0.4) 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,0, 0);
    border-width: 0.4375rem;
    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.5rem;
}

/* Default color */
#tooltip:not([class*=tooltip-]):after {
    border-bottom-color: rgb(20, 125, 125);
}

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

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

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

#tooltip.reverse:not([class*=tooltip-]):after {
    border-top-color: rgba(0,0,0, 1);
}

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


/* white */
#tooltip.tooltip-white:after {
    border-bottom-color: rgba(255,255,255, 1);
}
#tooltip.tooltip-white:before {
    border-bottom-color: rgba(0,0,0, 0.4);
}
/* white reverse */
#tooltip.tooltip-white.reverse:after {
    border-top-color: rgba(255,255,255, 1);
}
#tooltip.tooltip-white.reverse:before {
    border-top-color: rgba(0,0,0, 0.4);
}
Voir aussi…