React Accessible SVG Chart:aria-activedescendant无效

时间:2015-12-05 03:11:20

标签: svg charts reactjs accessibility wai-aria

我试图实施一个非常简单的图表来测试和提高我的可访问性技能。该图表由水果分布组成,我希望图表中的每个元素都可以被屏幕阅读器读取并通过键盘进行导航。我添加了一个tabIndex =" 0"到我的svg根元素并添加一个标题与图表的高级解释。然后,我为向上和向下键添加了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

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

1 个答案:

答案 0 :(得分: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);