【问题标题】:React Accessible SVG Chart: aria-activedescendant not working反应可访问的 SVG 图表:aria-activedescendant 不起作用
【发布时间】:2016-03-10 02:56:35
【问题描述】:

我正在尝试实现一个非常简单的图表来测试和提高我的可访问性技能。该图表由一个水果分布组成,我希望图表中的每个元素都可以被屏幕阅读器阅读并可以通过键盘导航。我在我的 svg 根元素中添加了一个 tabIndex="0" 并放置了一个对图表进行高级解释的标题。然后,我为 up 和 down 键添加了 keydown 侦听器,并在图表数据内移动(正确更改活动元素)。视觉上一切正常,我可以看到 aria-activedescendant 已更改为正确的元素。我希望屏幕阅读器(在我的情况下是画外音)能够理解活动后代已更改并阅读每个图表数据的标签,但这并没有发生。

这是图表代码:

var Graph = React.createClass({

  getInitialState: function () {
    return {
      activeBarIndex: 0
    }
  },

  componentDidMount: function () {
    var chartNode = ReactDOM.findDOMNode(this);  
    chartNode.addEventListener("keydown", this._onKeyDown);
  },

  componentWillUnmount: function () {
    var chartNode = ReactDOM.findDOMNode(this);  
    chartNode.removeEventListener("keydown", this._onKeyDown);
  },

  _onPreviousBar: function () {
    var totalBars = (
      ReactDOM.findDOMNode(this.refs.barGroup).childNodes.length
    );

    var barIndex = this.state.activeBarIndex - 1;
    if (barIndex < 0) {
      barIndex = totalBars - 1;
    }

    this.setState({activeBarIndex: barIndex});
  },

  _onNextBar: function () {
    var totalBars = (
      ReactDOM.findDOMNode(this.refs.barGroup).childNodes.length
    );

    var barIndex = this.state.activeBarIndex + 1;
    if (barIndex >= totalBars) {
      barIndex = 0;
    }

    this.setState({activeBarIndex: barIndex});  
  },

  _onKeyDown: function (e) {
    var key = (e.keyCode ? e.keyCode : e.which);

    switch (key) {
      case 38:
        this._onPreviousBar();
        break;
      case 40:
        this._onNextBar();
        break;
    }
  },

  render: function () {

    var data = [
      {
        fruit: 'apples',
        count: 4
      },
      {
        fruit: 'oranges',
        count: 8
      },
      {
        fruit: 'pinnaples',
        count: 12
      }
    ];

    var activeDescendant = 'apples_bar';
    var barGroups = data.map(function (bar, index) {
      var barClass = 'bar';
      if (this.state.activeBarIndex === index) {
        barClass += ' bar--active';
        activeDescendant = bar.fruit + '_bar';
      }

      return (
        <g id={bar.fruit + '_bar'} role="img" className={barClass} aria-labelledby={bar.fruit + '_text'}>
          <rect role="presentation" width={bar.count * 10} height="19" y={20 * index}></rect>
          <text id={bar.fruit + '_text'} x={(bar.count * 10) + 5} y={(20 * index) + 10} dy=".30em">{bar.count + ' ' + bar.fruit}</text>
        </g>
      );
    }.bind(this));

    return (
      <svg version="1.1" className="chart" ref="chart" width="420" height="150"
        aria-labelledby="chart_title" role="img" aria-activedescendant={activeDescendant} tabIndex="0">
        <title id="chart_title">Bar chart with fruit distribution</title>
        <g ref="barGroup">
          {barGroups}
        </g>
      </svg>
    );
  }
});

var element = document.getElementById('content');
ReactDOM.render(React.createElement(Graph), element);

您可以实时观看here

问题:屏幕阅读器缺少什么来理解我正在更改活动元素?

【问题讨论】:

    标签: svg charts reactjs accessibility wai-aria


    【解决方案1】:

    我可以通过在 SVG 根标签中添加 grid 角色来使其工作。和gridcell 在每个内部组中。

    这是运行良好的更新代码。我还更新了我的 codepen 脚本。

    如果您认为有更好的解决方案,请发布答案!

    var Graph = React.createClass({
    
      getInitialState: function () {
        return {
          activeBarIndex: 0
        }
      },
    
      componentDidMount: function () {
        var chartNode = ReactDOM.findDOMNode(this);  
        chartNode.addEventListener("keydown", this._onKeyDown);
      },
    
      componentWillUnmount: function () {
        var chartNode = ReactDOM.findDOMNode(this);  
        chartNode.removeEventListener("keydown", this._onKeyDown);
      },
    
      _onPreviousBar: function () {
        var totalBars = (
          ReactDOM.findDOMNode(this.refs.barGroup).childNodes.length
        );
    
        var barIndex = this.state.activeBarIndex - 1;
        if (barIndex < 0) {
          barIndex = totalBars - 1;
        }
    
        this.setState({activeBarIndex: barIndex});
      },
    
      _onNextBar: function () {
        var totalBars = (
          ReactDOM.findDOMNode(this.refs.barGroup).childNodes.length
        );
    
        var barIndex = this.state.activeBarIndex + 1;
        if (barIndex >= totalBars) {
          barIndex = 0;
        }
    
        this.setState({activeBarIndex: barIndex});  
      },
    
      _onKeyDown: function (e) {
        var key = (e.keyCode ? e.keyCode : e.which);
    
        switch (key) {
          case 38:
            this._onPreviousBar();
            break;
          case 40:
            this._onNextBar();
            break;
        }
      },
    
      render: function () {
    
        var data = [
          {
            fruit: 'apples',
            count: 4
          },
          {
            fruit: 'oranges',
            count: 8
          },
          {
            fruit: 'pinnaples',
            count: 12
          }
        ];
    
        var activeDescendant = 'apples_bar';
        var barGroups = data.map(function (bar, index) {
          var barClass = 'bar';
          if (this.state.activeBarIndex === index) {
            barClass += ' bar--active';
            activeDescendant = bar.fruit + '_bar';
          }
    
          return (
            <g id={bar.fruit + '_bar'} role="gridcell" className={barClass}>
              <rect role="presentation" width={bar.count * 10} height="19" y={20 * index}></rect>
              <text x={(bar.count * 10) + 5} y={(20 * index) + 10} dy=".30em">{bar.count + ' ' + bar.fruit}</text>
            </g>
          );
        }.bind(this));
    
        return (
          <svg version="1.1" className="chart" ref="chart" width="420" height="150" aria-readonly="true"
            aria-labelledby="chart_title" role="grid" aria-activedescendant={activeDescendant} tabIndex="0">
            <title id="chart_title">Bar chart with fruit distribution</title>
            <g ref="barGroup" role="row">
              {barGroups}
            </g>
          </svg>
        );
      }
    });
    
    var element = document.getElementById('content');
    ReactDOM.render(React.createElement(Graph), element);
    

    【讨论】:

    • aria-activedescendant 目前仅适用于某些角色。这可能是最好的解决方案,但您需要进行测试以确保它在其他 AT 中有效。例如Windows 上的 NVDA/JAWS。您可能想要解决的另一个问题是键盘命令在带有蓝牙键盘的 iOS 上不起作用,因此您可能需要添加对其他快捷键的支持。您需要研究的另一个问题是确保网格标记是完整的。 Gridcell 需要一个行父级。
    • 感谢您的反馈。我会确保 Jaws 和 Nvda 也会被测试。关于父行,它在父组中。
    猜你喜欢
    • 2018-06-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    • 2020-06-24
    • 2019-11-16
    • 2015-07-28
    • 2017-10-07
    • 2018-11-27
    相关资源
    最近更新 更多