具有多个项目的可访问拖放
在本文中,我想向您展示如何扩展HTML5拖放功能-这样它可以为有视力和屏幕阅读器的用户处理多个元素,并支持键盘交互。
我假设您已经对拖放API有所了解,但如果没有,请看一下此介绍性文章 (忽略对Modernizr的引用,这是您不需要的)。
拖动多个元素的基本方法实际上很简单-我们只需要记住一个以上元素的拖动数据即可。 我们可以使用dataTransfer
对象来做到这一点,或者我们可以只使用一个单独的数组(这就是我们要做的)。 那么,为什么要写整篇文章呢?
为什么-因为尽管数据很简单,但是界面却更加复杂。
我们需要做一些额外的工作来实现预选择机制 ,并且需要使它通过键盘工作 (因为本机拖放不支持此功能)。
注意:本文将不会涉及触摸事件,也不会为较旧的浏览器提供一个polyfill。 此外,我们采用的方法只能在单个页面中使用,它不支持在窗口之间拖动。 尽管拖放API确实支持此功能,但是没有直接的方法可以使其通过键盘访问。
基本拖放
因此,让我们从定义基本拖放事件的功能示例开始,允许用鼠标在两个容器之间拖动单个元素:
请参见CodePen上的SitePoint笔基本拖放 ( @SitePoint )。
那里有些东西可能与您所看到的其他演示有所不同。
第一种是它为拖动的元素维护item
引用的方式,而不是通过dataTransfer
对象传递元素的ID
(尽管我们必须传递某些内容,否则整个操作在Firefox中将失败;它可能是任何东西,因此空字符串会做):
var item = null;document.addEventListener('dragstart', function(e)
{item = e.target;e.dataTransfer.setData('text', '');}, false);
通过避免元素具有ID的需要,这简化了演示,并且可以轻松地扩展到可能不容易知道元素ID的服务器端应用程序(如CMS )。 这种方法还将成为多项选择的基础,其中item
引用将成为items
数组。
接下来的重要事情是事件属性effectAllowed
和dropEffect
的省略。 这些参数可以采用"copy"
或"move"
,并应控制允许哪些操作以及浏览器将显示哪个光标。 但是,浏览器的实现不一致,因此包括它们在内没有太多意义。
最后,请注意基础HTML如何不包含任何draggable
属性:
<ol data-draggable="target"><li data-draggable="item">Item 0</li><li data-draggable="item">Item 1</li><li data-draggable="item">Item 2</li><li data-draggable="item">Item 3</li>
</ol>
尽管大多数示例在静态HTML中使用这些属性,但我认为这与分离原理矛盾–因为它允许拖动元素,但是如果没有随附的JavaScript,您实际上无法将其拖放到任何地方。 因此,我改为使用静态数据属性来标识可拖动元素,然后使用脚本将draggable
属性应用于通过特征检测的浏览器。
对于特征检测失败的情况,此方法还提供了排除任何破坏的实现的机会。 使用window.opera
测试将Opera 12或更早版本排除在外,因为它的实现存在很多问题,并且不值得再花时间了。
无障碍拖放
可访问性是设计的基本原则,从这些角度考虑,总是容易实现。 因此,在继续进行此演示之前,我们必须对键盘和屏幕阅读器的可访问性要求有一个清晰的认识。
《 ARIA Authoring Practices》有一个关于拖放的小节 ,概述了我们需要的属性,并提供了交互作用方式的指南。 总结一下:
- 可拖动元素以
aria-grabbed="false"
标识,并且必须可以通过键盘进行导航。 - 提供一种机制供用户选择要拖动的元素,推荐的击键为Space 。 选择要拖动的元素时,其
aria-grabbed
属性设置为"true"
。 - 为用户提供一种机制,指示他们已经完成选择,推荐的击键是
Control+M
- 然后,使用
aria-dropeffect
标识目标元素,该目标元素的值指示允许哪些操作,例如"move"
或"copy"
。 在此过程中,目标元素还必须可以通过键盘进行导航。 - 当用户到达目标元素时,请为他们提供执行放下动作的机制,推荐的击键也是
Control+M
- 用户可以随时按
Escape
键取消整个操作。 - 操作完成或中止后,通过将所有
aria-dropeffect
属性设置为"none"
(或将其删除),并将所有aria-grabbed
属性设置为"false"
来清理接口。
现在,必须根据规范使用两个ARIA属性,即aria-grabbed
和aria-dropeffect
。 但是,事件和交互仅仅是建议,我们不必刻意跟踪它们。 但是,我们应该(并将)尽可能地遵循这些建议,因为它们是我们与规范性引用最接近的内容。
在大多数情况下,我认为这是有道理的。 但是我确实对Control+M
按键的两种用法都表示怀疑。 我不认为完全没有选择结束键是必要的,而且对于视力不佳的键盘用户来说,键本身也不是很直观的。
因此,我认为最好的方法是补充这些建议:
- 我们将实现选择结束按键,但这不是必需的,也不会阻止进一步的选择,它只是将焦点移至第一个放置目标的快捷方式。
- 我们将对两个键都使用
Control+M
,但也允许使用Enter
键触发放置动作。
该准则还讨论了使用修饰符实现多重选择。 建议使用Shift+Space
进行连续选择(即,选择两个端点之间的所有项目),并使用Control+Space
进行非连续选择(即,选择任意项目)。 这些显然是最适合使用的修饰符… 但是 ,Mac上Control+Space
等效项将是Command+Space
,但这已经绑定到系统操作上,无法被JavaScript抑制。 也许我们可以使用Control ,但Mac用户不会期望如此,因为Control键通常仅用于通过一键鼠标触发右键单击。 剩下的唯一选择是Shift ,但这是专门用于连续选择的!
因此,为了简单起见并避免该问题,我们将不实施连续选择。 我们现在可以做的最简单的事情是支持所有三个用于非连续选择的修饰符-Command + Space , Control + Space或Shift + Space在每个平台上都被以相同的方式对待-然后,无论特定用户可以触发什么,以及他们认为合理的任何东西都可以使用。
多项选择
既然我们对键盘可访问性有什么要求有了明确的认识,就可以开始为鼠标和键盘实现选择事件。 选择一个项目将不可避免地意味着添加一个class
或属性来指示选择,因此在两种情况下,我们都使用aria-grabbed
。 这将为我们提供一个方便且在语义上合适的样式钩子,并将有可能支持跨模式 -即支持鼠标和键盘交互的任何混合的能力,而不是假设仅使用一个或另一个。
因此,让我们首先更新应用了draggable
属性的初始代码,以便它还应用aria-grabbed
和tabindex
默认值:
for(var items = document.querySelectorAll('[data-draggable="item"]'), len = items.length, i = 0; i < len; i ++)
{items[i].setAttribute('draggable', 'true');items[i].setAttribute('aria-grabbed', 'false');items[i].setAttribute('tabindex', '0');
}
然后,我们可以从鼠标事件开始实现选择功能。
鼠标选择
在此演示中,您可以使用鼠标进行单个选择,或使用修饰符进行多个选择(仅此而已)。 但是请注意,如何只能在单个容器中进行多个选择,而不能在多个容器中进行选择:
请参阅CodePen上的SitePoint ( @SitePoint ) 选择笔式鼠标 。
有选择地将选择限制为单个“所有者”容器,并在脚本中使用selections.owner
参考来实现。 这种行为的目的是为了简化用户的交互,所以它始终不清楚的地方的选择是从哪里来的 ,并在那里他们可以被移动到 (即其他地方)。
如果您查看JavaScript代码,还将看到我们如何添加一些选择功能addSelection
, removeSelection
和clearSelections
添加和删除aria-grabbed
表示项目选择,以及管理selections
对象的数据。
调用这些功能的鼠标交互使用两个鼠标事件。 单选或全局重置(不带修饰符)由mousedown
事件触发,但取消选择或多重选择(带修饰符)则推迟到mouseup
事件。
使用两个事件对于创建直观的交互至关重要,该交互可以满足用户对修改后的和未修改的点击应如何表现的期望。 大部分都非常简单,但是存在一个潜在的矛盾:
- 修改后单击已选择的项目应取消选择它。
- 在任何已选择的项目上进行修改或未修改的拖动都应移动整个选择而不更改它。
不能仅使用mousedown
和dragstart
事件来实现这两种方法,因为它们将彼此直接矛盾。 但是,如果我们将mouseup
事件用于修改的选择和取消选择,那么我们可以满足两个期望。
我们还必须处理一种特殊情况,即用户尝试对未选中的项目进行修改后的拖动 。 由于修改后的选择被推迟到mouseup
,所以该操作将启动在不会包含在放置中的项目上的拖动。 但是我们可以通过使用dragstart
事件自动选择目标项目来解决此问题。
键盘选择
键盘选择是使用我们之前看过的模型来实现的-您可以使用Tab键浏览到项目,然后使用Space进行选择,并带有用于多个选择的修饰符:
请参见CodePen上的SitePoint ( @SitePoint ) 选择笔键盘 。
此示例中的JavaScript使用与鼠标相同的选择功能,这些选择功能是通过单个keydown
事件(由keyCode
区分)来调用的(注意:最新代码位于JavaScript面板的底部) 。
该keydown
还可以处理任何目标元素上的Escape键,以实现全局重置键击。 对于鼠标,您可以通过单击容器外部的任意位置来重置选择,因此按Escape键等效于键盘。
因此,现在我们有了完整的选择事件集。 至关重要的是,由于我们将aria-grabbed
用于两组事件, 因此鼠标或键盘交互的结果没有功能上的区别 -用户可以用键盘选择然后用鼠标拖动,反之亦然-以及状态任意组合的接口正确无误。
拖动选择
做出一个或多个选择后,用户需要知道他们可以将项目拖到哪里(即哪些容器是有效的放置目标)。 再一次,我们可以为鼠标和键盘交互使用相同的属性-在这种情况下,我们首先将aria-dropeffect
应用于所有目标容器:
for(var targets = document.querySelectorAll('[data-draggable="target"]'), len = targets.length, i = 0; i < len; i ++)
{targets[i].setAttribute('aria-dropeffect', 'none');
}
当目标可用时,其aria-dropeffect
从"none"
变为"move"
。 这使屏幕阅读器知道元素现在是有效的目标,并且还可以用作视觉样式钩子:
[data-draggable="target"]
{border-color:#888;background:#ddd;color:#555;
}
[data-draggable="target"][aria-dropeffect="move"]
{border-color:#68b;background:#fff;
}
(请注意,尽管唯一的视觉差异是颜色,但对比度的变化足以使其不仅仅依靠颜色来传达信息。)
键盘拖动
对于键盘交互, 一旦做出任何选择,就会立即应用"move"
状态。 键盘交互没有这样的“拖动”状态-用户在选择Tab到目标容器时会选择目标,因此只要存在选择,目标容器就必须可用:
见笔键盘拖动由SitePoint( @SitePoint上) CodePen 。
您将看到JavaScript代码如何具有两个新功能addDropeffect
和clearDropeffect
在目标容器上管理aria-dropeffect
和tabindex
。
但是,当目标可用时,我们还会从其中的所有项目中删除 tabindex
,以通过减少选项卡顺序中的内容量来提高键盘用户的可用性。 由于无法选择所有者容器之外的项目,因此它们根本不需要(也可以说不应 )按Tab键排序。 此类项目的aria-grabbed
属性也已暂时删除,因此屏幕阅读器不会继续将其宣布为可拖动项目。
这些功能由与选择时相同的keydown
事件调用-每当选择任何项时都添加dropeffect,并在全部重置后再次将其清除。 该事件还包括选择结束按键(对于PC是Control + M ,对于Mac是Command + M ),这只是将焦点放在第一个可用目标容器上的快捷方式。
我们还需要对Escape击键进行额外的焦点管理,因为击键可能在焦点位于目标容器上时使用,并且具有从该容器中删除tabindex
的作用。 这将导致焦点位置重新设置到页面顶部,因此我们必须显式管理焦点,以防止发生这种情况。 最简单的解决方案是将焦点重新设置在最后选择的项目上。
您可能会注意到对addDropeffect
和clearDropeffect
的调用有些过分地被clearDropeffect
-例如,即使我们已经有选择, 每个选择事件也会调用addDropeffect
。 我们需要这样做以支持跨模式-即我们不能假定以前的选择也是使用键盘进行的。 但是类似地,我们不能假定鼠标选择跟随另一个鼠标选择,因此我们需要更新鼠标事件以及键盘事件,以恢复使用任何一种模式时的适用状态。
鼠标拖动
对于鼠标拖动,在实际开始拖动之前,我们不会添加aria-dropeffect
,此时所有可用的目标容器都将突出显示:
请参见CodePen上的SitePoint ( @SitePoint ) 拖动笔式鼠标 。
鼠标交互的复杂之处在于需要创建“悬停”状态。 对于键盘,在目标样式中添加:focus
规则很简单,但是我们不能使用:hover
进行鼠标交互,因为在本机拖动期间永远不会发生悬停状态 。
但是,我们可以做的是使用本机拖动事件来监视鼠标位置,然后实现脚本化的悬停状态。 我们需要的事件称为dragenter
和dragleave
,它们在概念上类似于mouseover
和mouseout
,但有一个重要的区别—拖动事件没有relatedTarget
属性。 所以,当你拖动鼠标到目标元素(例如),在dragenter
事件不能告诉你鼠标远离移动哪个元素。
但是我们可以通过维护手动引用来解决此问题-由于dragleave
事件始终在 dragenter
事件之前 ,因此,逻辑上,该dragenter
的target
必须与dragleave
的related-target dragleave
。 因此,如果我们在前者中记录该引用,则可以在后者中使用它:
var related = null;document.addEventListener('dragenter', function(e)
{related = e.target;}, false);document.addEventListener('dragleave', function(e)
{//the related var is this event's relatedTarget element}, false);
为了使它有用,我们还需要此getContainer
函数,该函数从任何内部元素返回目标容器引用:
function getContainer(element)
{do{if(element.nodeType == 1 && element.getAttribute('aria-dropeffect')){return element;}}while(element = element.parentNode);return null;
}
我们需dragleave
是因为dragleave
事件会冒泡-就像mouseout
一样,当鼠标移到同一容器内的任何元素上时,它们会触发。 我们需要过滤这些事件,以便它们不重置悬停状态,因此使用getContainer
标准化对其父容器的所有引用,然后我们仅响应该引用已更改的事件:
document.addEventListener('dragleave', function(e)
{var droptarget = getContainer(related);if(droptarget != selections.droptarget){//... update target highlightingselections.droptarget = droptarget;}
}, false);
删除选择
最后的交互是将项目放入目标容器中。 在此过程的这一点上,我们将有一个items
数组,其中引用了所有要拖动的项目,因此移动该选择只是将这些项目附加到放置目标容器的一种情况:
for(var len = items.length, i = 0; i < len; i ++)
{droptarget.appendChild(items[i]);
}
我们根本不需要引用dataTransfer
对象,因为我们没有使用它。
老鼠粪便
对于鼠标交互,我们已经有一个由dragleave
事件创建的droptarget
引用(或者,如果没有当前目标,它将为null
)。 因此,我们只需要一个dragend
事件来处理释放鼠标时发生的情况:
请参阅CodePen上的SitePoint ( @SitePoint )的钢笔鼠标 粪便 。
这里重要的是没有drop
事件。
drop
事件名义上意味着该元素已被放置到一个有效的目标中,而dragend
事件仅意味着该鼠标已被释放(即放置目标是否有效)。 但是实际上,在单个文档的上下文中,这两个事件实际上没有区别 -每次都触发两个事件,“有效的放置目标”仅是“无论我们说什么”!
但是,我们必须处理浏览器事件错误- 如果仍然按住Command键,Mac / Webkit不会触发drop事件 。 因此,释放鼠标将使物件仍然被抓住,目标仍然处于活动状态,因此为避免该问题,我们dragend
所有内容使用dragend
事件。
在该事件中,我们有一个条件来处理放置动作(当目标有效时),并有一个条件用于处理重置(无论哪种情况)。 这种条件逻辑的拆分确保了在没有有效放置目标的情况下,我们不会重置抓取状态-因此用户可以再次尝试而不必重新选择项目。
键盘掉落
对于键盘交互,放下选择意味着导航到目标容器,然后按放下击键( Enter或Modifier + M )。 我们没有活动的droptarget
引用,但是我们不需要一个引用-拖放目标是接收按键的元素:
请参见在CodePen上将画笔键盘按SitePoint( @SitePoint ) 放置 。
因此,放置动作只是目标容器上的一个keydown
事件,它检查目标元素和击键,并做出相应的响应-移动选择,然后清理界面。 在这种情况下,我们只处理有效的放置操作-键盘导航不会发生无效的放置操作,因为首先只有有效的目标才能接收到击键(并且我们已经通过Escape键实现了用户中止)。
但是,我们确实必须像处理中止一样来管理焦点-因为当前具有焦点的元素将要失去其tabindex
,所以我们必须将焦点移到显式位置,以防止其被重置。 同样,最简单直观的解决方案是集中最后选择的项目。
就是这样!
当然,还有许多可能的其他增强功能,其中包括:
- 添加对触摸和/或指针事件的支持。
- 为较旧的浏览器创建一个polyfill。
- 删除所选内容时对其进行排序。
- 使用Modifier + Drop选择“复制”或“移动”。
- 使用Shift + Select进行连续选择和/或使用Shift + Arrow进行键盘范围选择。
- 使用自定义拖动重影来指示要拖动多少个项目。
- 当用鼠标拖动项目时,或使用选择结束键时,视觉上使选择变暗。
我将在以后的文章中讨论其中一些可能性。
同时,您可以从我们的GitHub存储库中获取文件的副本:
- 拖放演示
From: https://www.sitepoint.com/accessible-drag-drop/
具有多个项目的可访问拖放相关推荐
- phpStudy项目目录无法访问(报错500、400)
2019独角兽企业重金招聘Python工程师标准>>> 比如用t开头.n开头等作为项目目录名,访问时会显示如下: 比如项目目录为test 或者 通过查看nginx日志error.lo ...
- Docker:恢复对开源项目的无限制访问
喜欢就关注我们吧! 继宣布针对免费用户的拉速限制声明之后,Docker 现如今又透露了进一步的策略更新,旨在恢复对开源项目的无限制访问. Docker 方面此表示,为了支持开源社区,他们为开源项目制定 ...
- 如何让tomcat服务器运行在80端口,并且无需输入项目名即可访问项目()
这个问题最开始遇到的时候是半年前,自己买了个服务器玩,但是域名解析的时候出了问题,我查了查资料才知道腾讯云是默认解析到80端口,而且还改不了. 首先是修改tomcat运行端口号,默认是8080,但是我 ...
- 浅谈-tomcat中的项目之间的访问
1问题现象:windows项目上有一个tomcat容器,eclipse编译器中创建了一个tomcat服务,将两个项目部署到这个服务上,然后启动该服务,在本地tomcat容器中webapps文件夹中有一 ...
- 使用Idea部署SSM项目后,访问路径为url:8080/项目名_war_exploded的解决方案
使用Idea部署SSM项目后,访问路径为url:8080/项目名_war_exploded的解决方案 参考文章: (1)使用Idea部署SSM项目后,访问路径为url:8080/项目名_war_exp ...
- 4天4夜渡劫成功,解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问
经历4天4夜解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问 一.问题重现 二.排查问题 三.解决问题 四.关于Mysql这两个参数的作用以及解释 五.总结 标题是不是惊讶到你了,但 ...
- 从零开始搭建python flask+vue 小型web项目以及flask_sqlalchemy访问数据库
重零开始搭建python flask+vue 小型web项目以及flask_sqlalchemy访问数据库 前言 作者是一个前端开发者,之前从未接触过python,也没接触过后端开发,所有这篇文章中有 ...
- Vue项目配置本地访问地址和IP访问地址
Vue项目配置本地访问地址和IP访问地址 1.在config/index.js配置: dev: {host: '0.0.0.0', } 2.在build/webpack.dev.config.js更改 ...
- android编程权威指南 的PhotoGallery项目Flickr 不能访问的替代解决方法
android编程权威指南 的PhotoGallery项目Flickr 不能访问的替代解决方法 参考: <<android编程权威指南(第2版)>>的PhotoGallery项 ...
最新文章
- 仅用几行Python代码就能帮小姐姐复制U盘文件,实用干货
- 大哥你怕是没听过:头上没毛,代码不牢!
- 探讨ASP.NET 2.0中的Web控件改进技术(3)
- 02.Python基础
- 2022年全球及中国金属摩托车车轮市场竞争格局与供需前景调研报告
- shell学习过程中的错误集锦
- 华为宣布方舟编译器将于8月31日
- R语言作图之ggplot2初识(1)
- pyqt5 tablewidget 设置行高_Python+PyQt5基础开发(10)
- ad建集成库_AD16创建集成库的步骤
- window10激活
- 英特尔酷睿处理器后缀
- Python编程 | 统计新浪微博热门话题
- Uri.parse()的各种用法
- AI的10个开源工具/框架
- 德国海曼HTPA 32x32d热成像传感器代替MLX90640之传感器数据读取和计算
- 给apple老师建议
- 2022-2028全球氢化镁行业调研及趋势分析报告
- vue服务端渲染的优势
- 【身边人回忆乔布斯】之“苹果史学家”迈克尔·莫里茨
热门文章
- 【蓝旭】第五周预习博客
- 微信小程序把view居中_初识微信小程序
- IPUtils工具类
- 自己写的手机游戏脚本
- 数据圈最全的数据产品文章全集
- Micro(二)[环境搭建]
- python 关于元组的一些写法
- SAI绘制月下狼嚎图
- 已解决sqlalchemy.exc.ProgrammingError: (pymssql._pymssql.ProgrammingError) (102, b“Incorrect syntax nea
- 【行业标准】YBT092-2019-合金铸铁磨球(高中低铬铸造钢球)