仅在模态窗格中保持标签

Chr*_*rth 31 html javascript jquery

在我目前的项目中,我们有一些模式窗格可以打开某些操作.我试图得到它,以便当该模态窗格打开时,您不能选项卡到它之外的元素.jQuery UI对话框和Malsup jQuery块插件似乎都是这样做的,但我试图获得这一个功能并将其应用到我的项目中,并不是很明显我们是如何做到这一点的.

我已经看到有些人认为标签不应该被禁用,我可以看到这个观点但是我被赋予禁用它的指令.

小智 27

这只是扩展了基督徒的答案,增加了额外的输入类型,同时也考虑了shift + tab.

var inputs = $element.find('select, input, textarea, button, a').filter(':visible');
var firstInput = inputs.first();
var lastInput = inputs.last();

/*set focus on first input*/
firstInput.focus();

/*redirect last tab to first input*/
lastInput.on('keydown', function (e) {
   if ((e.which === 9 && !e.shiftKey)) {
       e.preventDefault();
       firstInput.focus();
   }
});

/*redirect first shift+tab to last input*/
firstInput.on('keydown', function (e) {
    if ((e.which === 9 && e.shiftKey)) {
        e.preventDefault();
        lastInput.focus();
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 认为你可以使用`:tabbable` (2认同)

Chr*_*rth 15

我终于能够通过在模式窗格打开时将焦点放在模态窗格中的第一个表单元素上然后如果在焦点位于模式窗格中的最后一个表单元素时按Tab键然后按下Tab键来完成此操作焦点可以追溯到第一个表单元素而不是DOM中的下一个元素,否则它将获得焦点.很多这样的脚本来自jQuery:如何捕获文本框中的TAB按键:

$('#confirmCopy :input:first').focus();

$('#confirmCopy :input:last').on('keydown', function (e) { 
    if ($("this:focus") && (e.which == 9)) {
        e.preventDefault();
        $('#confirmCopy :input:first').focus();
    }
});
Run Code Online (Sandbox Code Playgroud)

我可能需要进一步细化这个以检查按下其他一些键,例如箭头键,但基本的想法就在那里.


nia*_*ell 8

Christian和jfutch的好解决方案.

值得一提的是,劫持Tab键击有一些陷阱:

  • 可以在模态窗格内的某些元素上设置tabindex属性,使得元素的dom顺序不遵循Tab键顺序.(例如,在最后一个tabbable元素上设置tabindex ="10"可以使它在Tab键顺序中排在第一位)
  • 如果用户与模态外部的元素交互而不触发模态关闭,则可以在模态窗口外部选项卡.(例如,单击位置栏并开始切换回页面,或在VoiceOver等屏幕阅读器中打开页面地标并导航到页面的其他部分)
  • 检查元素是否:visible会在dom变脏时触发重排
  • 该文档可能没有:focussed元素.在chrome中,可以通过单击不可聚焦的元素然后按Tab键来更改"插入符号"位置.用户可以将插入位置设置为超过最后一个tabbable元素.

我认为更强大的解决方案是通过在所有可列表内容上将tabindex设置为-1来"隐藏"页面的其余部分,然后在关闭时"取消隐藏".这将使Tab键顺序保持在模态窗口内,并遵循tabindex设置的顺序.

var focusable_selector = 'a[href], area[href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]';

var hide_rest_of_dom = function( modal_selector ) {

    var hide = [], hide_i, tabindex,
        focusable = document.querySelectorAll( focusable_selector ),
        focusable_i = focusable.length,
        modal = document.querySelector( modal_selector ),
        modal_focusable = modal.querySelectorAll( focusable_selector );

    /*convert to array so we can use indexOf method*/
    modal_focusable = Array.prototype.slice.call( modal_focusable );
    /*push the container on to the array*/
    modal_focusable.push( modal );

    /*separate get attribute methods from set attribute methods*/
    while( focusable_i-- ) {
        /*dont hide if element is inside the modal*/
        if ( modal_focusable.indexOf(focusable[focusable_i]) !== -1 ) {
            continue;
        }
        /*add to hide array if tabindex is not negative*/
        tabindex = parseInt(focusable[focusable_i].getAttribute('tabindex'));
        if ( isNaN( tabindex ) ) {
            hide.push([focusable[focusable_i],'inline']);
        } else if ( tabindex >= 0 ) {
            hide.push([focusable[focusable_i],tabindex]);
        } 

    }

    /*hide the dom elements*/
    hide_i = hide.length;
    while( hide_i-- ) {
        hide[hide_i][0].setAttribute('data-tabindex',hide[hide_i][1]);
        hide[hide_i][0].setAttribute('tabindex',-1);
    }

};
Run Code Online (Sandbox Code Playgroud)

要取消隐藏dom,您只需使用'data-tabindex'属性查询所有元素,并将tabindex设置为属性值.

var unhide_dom = function() {

    var unhide = [], unhide_i, data_tabindex,
        hidden = document.querySelectorAll('[data-tabindex]'),
        hidden_i = hidden.length;

    /*separate the get and set attribute methods*/
    while( hidden_i-- ) {
        data_tabindex = hidden[hidden_i].getAttribute('data-tabindex');
        if ( data_tabindex !== null ) {
            unhide.push([hidden[hidden_i], (data_tabindex == 'inline') ? 0 : data_tabindex]);
        }
    }

    /*unhide the dom elements*/
    unhide_i = unhide.length;
    while( unhide_i-- ) {
        unhide[unhide_i][0].removeAttribute('data-tabindex');
        unhide[unhide_i][0].setAttribute('tabindex', unhide[unhide_i][1] ); 
    }

}
Run Code Online (Sandbox Code Playgroud)

当模态打开时,使其余的dom隐藏在咏叹调中稍微容易一些.循环浏览模态窗口的所有亲属并将aria-hidden属性设置为true.

var aria_hide_rest_of_dom = function( modal_selector ) {

    var aria_hide = [],
        aria_hide_i,
        modal_relatives = [],
        modal_ancestors = [],
        modal_relatives_i,
        ancestor_el,
        sibling, hidden,
        modal = document.querySelector( modal_selector );


    /*get and separate the ancestors from the relatives of the modal*/
    ancestor_el = modal;
    while ( ancestor_el.nodeType === 1 ) {
        modal_ancestors.push( ancestor_el );
        sibling = ancestor_el.parentNode.firstChild;
        for ( ; sibling ; sibling = sibling.nextSibling ) {
            if ( sibling.nodeType === 1 && sibling !== ancestor_el ) {
                modal_relatives.push( sibling );
            }
        }
        ancestor_el = ancestor_el.parentNode;
    }

    /*filter out relatives that aren't already hidden*/
    modal_relatives_i = modal_relatives.length;
    while( modal_relatives_i-- ) {

        hidden = modal_relatives[modal_relatives_i].getAttribute('aria-hidden');
        if ( hidden === null || hidden === 'false' ) {
            aria_hide.push([modal_relatives[modal_relatives_i]]);
        }

    }

    /*hide the dom elements*/
    aria_hide_i = aria_hide.length;
    while( aria_hide_i-- ) {

        aria_hide[aria_hide_i][0].setAttribute('data-ariahidden','false');
        aria_hide[aria_hide_i][0].setAttribute('aria-hidden','true');

    }       

};
Run Code Online (Sandbox Code Playgroud)

当模态关闭时,使用类似的技术取消隐藏咏叹调dom元素.这里更好的是删除aria-hidden属性而不是将其设置为false,因为在这些情况下优先执行aria-hidden的元素上可能存在一些冲突的css可见性/显示规则在浏览器中不一致(请参阅https: //www.w3.org/TR/2016/WD-wai-aria-1.1-20160721/#aria-hidden)

var aria_unhide_dom = function() {

    var unhide = [], unhide_i, data_ariahidden,
        hidden = document.querySelectorAll('[data-ariahidden]'),
        hidden_i = hidden.length;

    /*separate the get and set attribute methods*/
    while( hidden_i-- ) {
        data_ariahidden = hidden[hidden_i].getAttribute('data-ariahidden');
        if ( data_ariahidden !== null ) {
            unhide.push(hidden[hidden_i]);
        }
    }

    /*unhide the dom elements*/
    unhide_i = unhide.length;
    while( unhide_i-- ) {
        unhide[unhide_i].removeAttribute('data-ariahidden');
        unhide[unhide_i].removeAttribute('aria-hidden');
    }

}
Run Code Online (Sandbox Code Playgroud)

最后,我建议在动画结束后调用这些函数.下面是在transition_end上调用函数的抽象示例.

我正在使用modernizr来检测负载的转换持续时间.transition_end事件冒泡dom,因此如果在模态窗口打开时多个元素正在转换,它可以多次触发,因此在调用hide dom函数之前检查event.target.

/* this can be run on page load, abstracted from 
 * http://dbushell.com/2012/12/22/a-responsive-off-canvas-menu-with-css-transforms-and-transitions/
 */
var transition_prop = Modernizr.prefixed('transition'),
    transition_end = (function() {
        var props = {
            'WebkitTransition' : 'webkitTransitionEnd',
            'MozTransition'    : 'transitionend',
            'OTransition'      : 'oTransitionEnd otransitionend',
            'msTransition'     : 'MSTransitionEnd',
            'transition'       : 'transitionend'
        };
        return props.hasOwnProperty(transition_prop) ? props[transition_prop] : false;
    })();


/*i use something similar to this when the modal window is opened*/
var on_open_modal_window = function( modal_selector ) {

    var modal = document.querySelector( modal_selector ),
        duration = (transition_end && transition_prop) ? parseFloat(window.getComputedStyle(modal, '')[transition_prop + 'Duration']) : 0;

    if ( duration > 0 ) {
        $( document ).on( transition_end + '.modal-window', function(event) {
            /*check if transition_end event is for the modal*/
            if ( event && event.target === modal ) {
                hide_rest_of_dom();
                aria_hide_rest_of_dom();    
                /*remove event handler by namespace*/
                $( document ).off( transition_end + '.modal-window');
            }               
        } );
    } else {
        hide_rest_of_dom();
        aria_hide_rest_of_dom();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我刚刚对Alexander Puchkov的解决方案进行了少许更改,并使其成为了JQuery插件。它解决了容器中动态DOM更改的问题。如果有任何控件将其有条件地添加到容器中,则可以正常工作。

(function($) {

    $.fn.modalTabbing = function() {

        var tabbing = function(jqSelector) {
            var inputs = $(jqSelector).find('select, input, textarea, button, a[href]').filter(':visible').not(':disabled');

            //Focus to first element in the container.
            inputs.first().focus();

            $(jqSelector).on('keydown', function(e) {
                if (e.which === 9) {

                    var inputs = $(jqSelector).find('select, input, textarea, button, a[href]').filter(':visible').not(':disabled');

                    /*redirect last tab to first input*/
                    if (!e.shiftKey) {
                        if (inputs[inputs.length - 1] === e.target) {
                            e.preventDefault();
                            inputs.first().focus();
                        }
                    }
                    /*redirect first shift+tab to last input*/
                    else {
                        if (inputs[0] === e.target) {
                            e.preventDefault();
                            inputs.last().focus();
                        }
                    }
                }
            });
        };

        return this.each(function() {
            tabbing(this);
        });

    };
})(jQuery);
Run Code Online (Sandbox Code Playgroud)