Accessibility for Bilibili Web

Bilibili 可访问性优化。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
})();