Accessibility for Bilibili Web

Bilibili 可访问性优化。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name Accessibility for Bilibili Web
// @namespace https://www.viyf.org
// @version 0.1.4
// @description Bilibili 可访问性优化。
// @author ABitGlow
// @match        https://www.bilibili.com/*
// @grant        none
// ==/UserScript==

/** 被扫除的障碍清单
 * “点赞”、“投币”、“收藏”、“分享” 按钮缺乏键盘操作能力;
 * “搜索” 按钮无可访问性文字描述;
 * 弹幕、 CC 字幕没有添加 ARIA Live Region 相关属性;
 * “弹幕” 开关无可访问性文字描述;
 * “发送” 弹幕按钮无键盘聚焦能力;
 * 当播放器组件加载完成后 “播放”/“暂停” 按钮没有获取焦点;
 */

(function () {
    'use strict';

    /* 给选择器指定的元素添加属性值。 */
    function setElementAttribute(selector, name, value) {
        var list = document.querySelectorAll(selector);
        Array.prototype.forEach.call(list, function (e) {
            e.setAttribute(name, value);
        });
        return list.length;
    }

    /* 用于 KeyboardEvent 的回调函数。 当回车键和空格键按下引发该元素的 click 事件。 */
    function keySpaceOrEnterToClick(event) {
        if (event.key === 'Enter' || event.key === ' ') {
            this.click();
        }
    }

    /* 页面加载完成后对一些元素进行处理。 */
    function processLoadedHTML() {
        setElementAttribute('#arc_toolbar_report > div.ops > span', 'tabindex', '0');
        setElementAttribute('#arc_toolbar_report > div.ops > span', 'role', 'button');

        var list = document.querySelectorAll('#arc_toolbar_report > div.ops > span');
        Array.prototype.forEach.call(list, function (e) {
            e.addEventListener('keydown', keySpaceOrEnterToClick, null);
        });
    }

    setTimeout(processLoadedHTML, 1000);

    /* 处理搜索按钮。 */
    (function () {
        var countdown = 10;
        var intervalID = setInterval(function () {
            if (setElementAttribute('#nav_searchform > div > button', 'aria-label', '搜索') > 0) {
                clearInterval(intervalID);
            }
            if (countdown < 1) {
                clearInterval(intervalID);
            }
            countdown--;
        }, 1000);
    })();

    /* 处理播放器组件。 */
    function processPlayer() {
        setElementAttribute('div.subtitle-wrap, div.bilibili-player-video-danmaku', 'aria-live', 'polite');
        setElementAttribute('div.bilibili-player-video-danmaku', 'aria-live', 'polite');
        setElementAttribute('div.bui-button', 'role', 'button');
        setElementAttribute('div.bui-button', 'tabindex', '0');
        setElementAttribute('div.bilibili-player-video-danmaku-switch > input', 'aria-label', '弹幕');

        /* 播放器加载完成后让 “播放/暂停” 按钮获取焦点。 */
        var btnElement = document.querySelector('div.bilibili-player-video-btn.bilibili-player-video-btn-start > button');
        if (btnElement) {
            btnElement.focus();
        }
    }

    /* 给播放器组件添加观察器。 */
    var elementToObserve = document.getElementById('bilibili-player');
    var config = {
        childList: true,
        subtree: true
    };
    var callback = function (mutationsList, observer) {
        mutationsList.forEach(function (record) {
            if (record.target.classList.contains('bilibili-player-video-wrap')) {
                processPlayer();
            }
        });
    };
    var observer = new MutationObserver(callback);
    observer.observe(elementToObserve, config);
})();