您现在的位置是:网站首页 > 无障碍访问设计文章详情

无障碍访问设计

无障碍访问设计的重要性

无障碍访问设计确保所有用户都能平等地获取和使用数字产品。对于数据可视化库ECharts来说,这意味着图表需要能被屏幕阅读器识别、支持键盘操作、提供足够的颜色对比度等。忽视无障碍设计会导致视障用户、运动障碍用户等人群无法正常使用图表功能。

屏幕阅读器兼容性

ECharts图表默认生成的SVG或Canvas元素对屏幕阅读器不友好。解决方案是通过ARIA属性增强可访问性:

option = {
  aria: {
    enabled: true,
    label: {
      description: '这是一个展示2023年季度销售额的柱状图。第一季度销售额为43万元,第二季度为82万元,第三季度为65万元,第四季度为97万元。'
    }
  },
  xAxis: {
    type: 'category',
    data: ['第一季度', '第二季度', '第三季度', '第四季度'],
    axisLabel: {
      aria: {
        enabled: true,
        label: {
          description: '季度'
        }
      }
    }
  },
  yAxis: {
    type: 'value',
    axisLabel: {
      aria: {
        enabled: true,
        label: {
          description: '销售额(万元)'
        }
      }
    }
  },
  series: [{
    data: [43, 82, 65, 97],
    type: 'bar',
    itemStyle: {
      aria: {
        enabled: true,
        label: {
          description: function(params) {
            return `${params.name}销售额:${params.value}万元`;
          }
        }
      }
    }
  }]
};

键盘导航支持

为图表添加键盘交互需要监听键盘事件并实现焦点管理:

myChart.getZr().on('keydown', function(e) {
  const keyCode = e.event.keyCode;
  // 左右箭头切换数据项
  if (keyCode === 37 || keyCode === 39) {
    const currentIndex = highlightedIndex || 0;
    const newIndex = keyCode === 37 ? Math.max(0, currentIndex - 1) : Math.min(data.length - 1, currentIndex + 1);
    highlightDataPoint(newIndex);
  }
  // 回车键触发工具提示
  else if (keyCode === 13 && highlightedIndex !== null) {
    showTooltip(highlightedIndex);
  }
});

function highlightDataPoint(index) {
  myChart.dispatchAction({
    type: 'downplay',
    seriesIndex: 0
  });
  myChart.dispatchAction({
    type: 'highlight',
    seriesIndex: 0,
    dataIndex: index
  });
  highlightedIndex = index;
}

颜色对比度与模式

确保图表有足够的颜色对比度,并提供高对比度模式:

  1. 使用WCAG 2.1 AA标准(至少4.5:1的对比度)
  2. 避免仅靠颜色传达信息
  3. 提供高对比度主题选项
// 高对比度配色方案
const highContrastColors = [
  '#000000', // 黑色
  '#FFFFFF', // 白色
  '#FF0000', // 红色
  '#00FF00', // 绿色
  '#0000FF'  // 蓝色
];

function applyHighContrastTheme() {
  myChart.setOption({
    color: highContrastColors,
    backgroundColor: '#FFFFFF',
    textStyle: {
      color: '#000000'
    },
    grid: {
      borderColor: '#000000'
    }
  });
}

动态内容更新通知

当图表数据动态更新时,需要通知辅助技术用户:

function updateChart(newData) {
  myChart.setOption({
    series: [{
      data: newData
    }]
  });
  
  // 创建隐藏的ARIA实时区域
  const ariaLiveRegion = document.getElementById('chart-aria-live') || 
    document.createElement('div');
  ariaLiveRegion.id = 'chart-aria-live';
  ariaLiveRegion.setAttribute('aria-live', 'polite');
  ariaLiveRegion.setAttribute('aria-atomic', 'true');
  ariaLiveRegion.style.position = 'absolute';
  ariaLiveRegion.style.left = '-9999px';
  document.body.appendChild(ariaLiveRegion);
  
  // 更新通知内容
  ariaLiveRegion.textContent = `图表已更新,最新数据点值为:${newData[newData.length - 1]}`;
}

可缩放矢量图形(SVG)的优化

当使用SVG渲染时,可以添加额外的无障碍属性:

option = {
  renderer: 'svg',
  graphic: {
    elements: [{
      type: 'text',
      style: {
        text: '重要数据趋势',
        fontSize: 16,
        fill: '#333'
      },
      left: 'center',
      top: 10,
      extra: {
        'aria-label': '图表标题:重要数据趋势',
        role: 'heading',
        'aria-level': '2'
      }
    }]
  }
};

复杂图表的描述策略

对于包含多个系列的复杂图表,需要分层级提供描述:

option = {
  aria: {
    enabled: true,
    label: {
      general: {
        description: '这是一个包含销售额和利润的双Y轴组合图表,横轴表示时间。'
      },
      series: [
        {
          description: '蓝色柱状图表示每月销售额,单位为万元。'
        },
        {
          description: '红色折线图表示每月利润率,单位为百分比。'
        }
      ]
    }
  }
};

触摸设备兼容性

确保图表在触摸设备上也能无障碍操作:

myChart.on('touchstart', function(params) {
  // 放大点击区域
  if (params.componentType === 'series') {
    const point = params.dataIndex;
    highlightDataPoint(point);
    // 振动反馈
    if (navigator.vibrate) {
      navigator.vibrate(50);
    }
  }
});

// 增加点击区域
myChart.getZr().on('click', function(params) {
  const pointInPixel = [params.offsetX, params.offsetY];
  const pointInGrid = myChart.convertFromPixel('grid', pointInPixel);
  const dataIndex = myChart.getModel().getSeries()[0].coordinateSystem.dataToPointIndex(pointInGrid[0]);
  highlightDataPoint(dataIndex);
});

数据表格替代方案

为图表提供数据表格作为替代访问方式:

function renderDataTable(containerId, chartData) {
  const container = document.getElementById(containerId);
  let html = '<table class="chart-data-table" aria-label="图表数据表格"><thead><tr><th>类别</th><th>值</th></tr></thead><tbody>';
  
  chartData.forEach(item => {
    html += `<tr><td>${item.name}</td><td>${item.value}</td></tr>`;
  });
  
  html += '</tbody></table>';
  container.innerHTML = html;
}

// 与图表同步
myChart.on('rendered', function() {
  const chartData = myChart.getOption().series[0].data;
  renderDataTable('chart-data-table', chartData);
});

自定义无障碍控件

添加专门的无障碍控制界面:

<div class="chart-controls" role="group" aria-label="图表控制">
  <button id="play-pause" aria-label="播放/暂停动画">▶️</button>
  <button id="prev-point" aria-label="上一个数据点">◀</button>
  <button id="next-point" aria-label="下一个数据点">▶</button>
  <select id="theme-selector" aria-label="选择图表主题">
    <option value="default">默认主题</option>
    <option value="high-contrast">高对比度</option>
    <option value="dark">暗黑模式</option>
  </select>
</div>
document.getElementById('play-pause').addEventListener('click', toggleAnimation);
document.getElementById('prev-point').addEventListener('click', showPreviousPoint);
document.getElementById('next-point').addEventListener('click', showNextPoint);
document.getElementById('theme-selector').addEventListener('change', changeTheme);

性能与无障碍的平衡

在实现无障碍功能时需考虑性能影响:

  1. 延迟加载非关键ARIA属性
  2. 使用虚拟滚动处理大数据集
  3. 对频繁更新的内容使用aria-live="polite"
// 大数据集的无障碍处理
function loadLargeDataSet(data) {
  // 初始只加载可见区域数据
  const visibleData = data.slice(0, 50);
  myChart.setOption({
    series: [{
      data: visibleData,
      progressive: 400,
      progressiveThreshold: 3000
    }]
  });
  
  // 为屏幕阅读器提供摘要
  const summary = `共${data.length}条数据,当前显示前50条。使用页面滚动查看更多数据。`;
  document.getElementById('data-summary').textContent = summary;
}

多语言支持

无障碍设计需要考虑多语言用户:

const i18n = {
  en: {
    chartTitle: 'Sales Trend',
    xAxis: 'Quarter',
    yAxis: 'Amount (10K)',
    description: 'Bar chart showing quarterly sales data.'
  },
  zh: {
    chartTitle: '销售趋势',
    xAxis: '季度',
    yAxis: '金额(万元)',
    description: '展示季度销售数据的柱状图。'
  }
};

function setLanguage(lang) {
  myChart.setOption({
    title: {
      text: i18n[lang].chartTitle,
      aria: {
        label: i18n[lang].chartTitle
      }
    },
    aria: {
      label: {
        description: i18n[lang].description
      }
    },
    xAxis: {
      name: i18n[lang].xAxis,
      axisLabel: {
        aria: {
          label: {
            description: i18n[lang].xAxis
          }
        }
      }
    },
    yAxis: {
      name: i18n[lang].yAxis,
      axisLabel: {
        aria: {
          label: {
            description: i18n[lang].yAxis
          }
        }
      }
    }
  });
}

用户偏好检测与适应

自动检测用户的无障碍偏好并调整图表:

// 检测高对比度模式
const highContrastMediaQuery = window.matchMedia('(prefers-contrast: more)');
highContrastMediaQuery.addListener(handleContrastChange);
handleContrastChange(highContrastMediaQuery);

function handleContrastChange(e) {
  if (e.matches) {
    applyHighContrastTheme();
  } else {
    applyDefaultTheme();
  }
}

// 检测减少动画偏好
const reducedMotionMediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
reducedMotionMediaQuery.addListener(handleMotionPreference);
handleMotionPreference(reducedMotionMediaQuery);

function handleMotionPreference(e) {
  myChart.setOption({
    animation: !e.matches,
    animationDuration: e.matches ? 0 : 1000
  });
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步