116. 前端JavaScript(四)防抖与消息处理
本主题接着《前端JavaScript(三)》讲述,推荐按序号阅读
核心库core/ drupal.debounce:
文件: /core/misc/debounce.es6.js
为全局对象Drupal添加了一个方法:
Drupal.debounce(func, wait, immediate)
该方法称为防抖函数,用于在给定毫秒时间段内最多执行一次给定的函数,参数如下:
func:给定的要执行的函数
wait:时间范围,整数,单位毫秒,在该范围内参数一最多被执行一次
immediate:前文讲到在给定时间段范围内最多执行一次,那么这一次是在这个范围的开始执行还是结束时执行呢?这就是该参数的意义,是一个布尔值,true表示在开始执行,反之在结尾执行,默认为false
返回值是一个匿名函数,包装了传入的函数,称为传入函数的防抖变体
示例如下:
function f() {
function fn() {
console.log(Math.random());
}
let yunke = Drupal.debounce(fn, 5000, true);
yunke();
yunke();
yunke();
}
该示例中,在执行函数f时,里面虽然三次调用yunke();但函数fn仅被执行一次(不管多少次调用,在5秒内仅执行一次)
在频繁重复调用防抖变体时,如果参数immediate被设置为true,那么可以肯定在等待时间内回调一定会被执行一次,且仅一次(在本轮事件循环中执行),如果设置为false,那么只能讲在等待时间内最多被执行一次,可能不被执行,因为是以异步方式执行,每次调用都会推后异步回调的执行时间,示例如下:
function f() {
function fn() {
console.log(Math.random());
}
let startTime = new Date();
let loop = true;
let yunke = Drupal.debounce(fn, 500, false);
while (loop) {
let d = (new Date()) - startTime;
if (d > 5000) {
loop = false;
}
yunke();
}
}
在该列中,执行f()时,回调fn被设置为500毫秒内最多仅执行一次,在while循环中频繁重复调用防抖变体yunke(),循环5秒,执行结果并不是fn被调用了10次,而是5.5秒后被执行一次。
该函数原理上很精炼,用到了闭包、异步编程等;用处有很多,尤其是高频事件触发回调时,比如鼠标移动、窗体大小调整等,能够防止剧烈回调执行,这也是其称为防抖函数的原因。
注意:高频调用Drupal.debounce(fn, 200)();是起不到防抖作用的,仅仅起到延迟执行的效果,因为每次调用返回的都是不同的防抖变体。
核心库core/drupal.announce:
该库用于支持辅助技术(通常为残障辅助设备),在理解该库前需要先理解辅助技术相关知识:
有些人群访问web需要借助辅助设备,比如失明和弱视、耳聋和听力丧失、运动受限、言语障碍、光敏性或多种残疾组合的残疾人,以及身体功能退化的老人,他们使用屏幕阅读器、盲文系统等等,这些可以是硬件也可以是软件,这里统称为辅助技术Assistive Technologies,为了和这些辅助技术交互,W3C发起了一个Web无障碍计划Web Accessibility Initiative,缩写为WAI,官网地址为:
Web内容无障碍中文指南 (WCAG) 2.1官方地址:
https://www.w3.org/Translations/WCAG21-zh/
该计划制定了一个ARIA规范用于具体实现,ARIA是“Accessible Rich Internet Applications”的缩写,即可访问的富互联网应用程序规范,web开发者在开发无障碍访问网页时需要遵循该规范,规范官方地址为:
https://www.w3.org/TR/wai-aria-1.1/
该规范为html标签提供三种指示语义的属性以支持辅助技术:角色、属性、状态,解释如下:
role:
角色,表明一些html标签是做什么用的,如滑块、菜单栏、对话框、选项卡等,示例:
<p id="ajax_error_alert" role="alert"></p>
该例告诉辅助技术这个p元素的身份是一个包含警告信息的角色,辅助技术应据此向用户发出警告信息;规范指出:角色属性一旦设定就不能被改变,需更改应删除旧元素后建立新元素,角色全部可选值参见这里:
https://www.w3.org/TR/wai-aria-1.1/#role_definitions
aria-属性名:
属性,表明元素的特性,如是否可拖拽、是否必填等,不同属性名及其不同值含义不同:
示例一:
<ul aria-busy="true" >
该例中aria-busy属性表示当前区域是否正在更新:可选为true, 表示该区域正在更新,如加载或修改,默认为false, 表示与true相对的状态,静置中,或为error,表示该区域出现错误。如果某个区域内(如这里的ul)有多个地方需要修改,需要全部修改完毕再通知使用者的话,就可以先将aria-busy设为true, 等到全部内容更新完毕后再设成false,该属性可以避免辅助技术在区域内容更新完毕前不断即时提醒使用者。
示例二:
<div aria-live="assertive" aria-busy="false"></div>
该例中aria-live指示通知用户的行为,其值是字符串,可选有:off、polite、assertive、rude。默认为off, 表示不通知更新;polite表示只有用户闲时通知;assertive表示尽快通知用户;rude表示即时通知用户,必要的时候甚至中断用户,绝大多数的更新应该在用户闲暇的时候通知,即时提示对用户是一种干扰,如果希望内容完全更新后再提示,可以使用上面提到的aria-busy
aria-状态名:
状态,表示当前交互的状态,如是否选中、隐藏、禁用、展开、键盘按下等,示例如下:
<li role="checkbox" aria-checked="true">全部产品</li>
全部状态和属性请见:
https://www.w3.org/TR/wai-aria-1.1/#state_prop_def
元素的以上三种属性这里统称为无障碍属性,其值均应小写,有些元素默认具备一些无障碍属性,详见:
https://www.w3.org/TR/html-aria/#dfn-implicit-aria-semantics
在有默认无障碍属性的情况下无需明确指定,无障碍计划诞生于HTML4之后,HTML5之前,很多H5标签天然具备语义性,但在使用如div、span之类的无语义元素时,为了实现无障碍访问应当明确指定无障碍属性;
现代浏览器在打开一个网页时,除建立DOM树外,还会建立可访问树(Accessibility Tree),这颗树被映射给操作系统,后者提供给辅助技术,树中的节点称为可访问对象,它们由DOM对象和无障碍属性信息综合计算而来
库解释:
文件:core/misc/announce.es6.js
在文档就绪后,该库在页面末尾追加一个专门用于通知辅助技术(如屏幕阅读器)的元素:
<div id="drupal-live-announce" class="visually-hidden" aria-live="polite" aria-busy="false"></div>
该元素的内容发生变化时将会通知辅助技术,其他js可以通过以下方法向该元素添加内容:
Drupal.announce(text, priority);
参数:text是要通知的文本内容,字符串值,如需翻译应该是翻译之后的
参数:priority表示紧急程度,字符串值,polite和assertive二选一,如果传递其他参数将默认为polite,这也是默认值,表示在用户闲暇时进行通知,assertive表示尽管通知用户,有必要时中断用户进行通知
bug提示:该方法在实现上错误使用了防抖方法,修复如下:
let announce_debounce = debounce(announce, 200);
Drupal.announce = function (text, priority) {
announcements.push({
text,
priority,
});
return announce_debounce();
};
云客已向官方报告,在发布前,可自行修复
核心库core/drupal.message:
文件:/core/misc/message.es6.js
用于提供一个API方便其他JS在页面上显示消息,消息分三种类型:状态status、警告warning、错误error(注意消息不是日志,日志有更多类型),详见后端服务:\Drupal::messenger()。
该库使用示例如下:
function yunke() {
let message = new Drupal.Message();
let id = message.add('js动态添加的状态消息');
setTimeout(function () {
message.remove(id);
}, 5000);
//console.log(hasXor);
}
这将在页面默认消息块放置的地方显示一条状态消息,然后5秒后自动删除
默认消息显示区:
在系统开启消息块的情况下,会默认提供一个消息显示区,消息块由系统模块提供,块插件类:
\Drupal\system\Plugin\Block\SystemMessagesBlock
该块用到了状态消息类型的渲染元素,元素类型插件类如下:
\Drupal\Core\Render\Element\StatusMessages
消息块提供了一个具备“data-drupal-messages”属性的div元素来存放后端提供的消息,如果后端没有消息将不提供该元素,相反只要消息块被启用,将始终提供一个具备属性“data-drupal-messages-fallback”的div元素,以供前端显示消息,显示消息的标签结构如下:
<div data-drupal-messages>
<div class="messages__wrapper layout-container">
<div role="contentinfo" aria-label="状态消息" class="messages messages--status">
<h2 class="visually-hidden">状态消息</h2>
<ul class="messages__list">
<li class="messages__item">状态消息1</li>
<li class="messages__item">状态消息2</li>
</ul>
</div>
<div role="contentinfo" aria-label="警告信息" class="messages messages--warning">
<h2 class="visually-hidden">警告信息</h2>
警告消息1
</div>
<div role="contentinfo" aria-label="错误信息" class="messages messages--error">
<div role="alert">
<h2 class="visually-hidden">错误信息</h2>
<ul class="messages__list">
<li class="messages__item">错误消息1</li>
<li class="messages__item">错误消息2</li>
</ul>
</div>
</div>
</div>
</div>
默认主题中该结构在以下模板中输出:
core/themes/bartik/templates/status-messages.html.twig
core/themes/classy/templates/misc/status-messages.html.twig
在前端将寻找该元素,并用其第一个子元素作为默认消息显示区(也就是类为“messages__wrapper”的元素),如果后端没有提供消息,那么该元素是不存在的,将退而寻找具备属性“data-drupal-messages-fallback”的div元素(在消息块启用时该元素一定存在),然后为其建立一个具备“messages__wrapper”类的子元素,并采用该子元素做消息显示区。
自定义消息显示区:
仅在没有指定消息显示区的情况下才采用前一节所述的默认消息显示区,那么如何采用自定义显示区呢?
示例如下:
let element=document.getElementById('sidebar-first');
let message = new Drupal.Message(element);
如你所见,将指定的消息显示区作为构造函数的参数传入即可
消息元素:
由主题方法“Drupal.theme.message”建立返回,是一个div元素,有如下属性:
class:类,值有messages、messages--${type}
role:值为:alert或status,依据类型而定
data-drupal-message-id:消息id
data-drupal-message-type:消息类型
aria-label:无障碍属性,消息类型的名称,供屏幕阅读器设备使用
设计师可通过这些属性来控制消息元素的外观
该库接口方法解释如下:
add(message, options = {}):
向页面添加显示一条消息,参数message是一个字符串,表示要显示的消息,其中可以带html标签,参数options是消息的上下文参数,有四个可用的属性:
options.type:消息的类型,status、warning、error之一,如省略默认为status,传入其他值将抛出异常
options.id:这条消息的id,字符串值,如省略将随机生成一个,格式为“类型-15位随机数字”,如:“status-553531552765107”
options.priority:用于通知辅助技术时,表示消息的紧急程度,字符串值,polite和assertive二选一,如果传递其他参数将默认为polite,表示在用户闲暇时进行通知,assertive表示尽管通知用户,有必要时中断用户进行通知,通常我们不需要设置该参数,会依据消息类型自动判断,当warning、error时自动设置为assertive,一旦用户设置,将不会进行自动判断操作
options.announce:默认通知辅助技术的消息,如果全等于空字符串“''”将禁用通知,否则以该参数作为通知的消息(应为字符串值),如果该参数不存在,将以messsage参数作为通知消息
该方法返回消息id,其他方法可依据返回的id去操作消息元素
select(id):
依据消息id返回消息元素的dom对象,仅在消息显示区中查找
remove(id):
依据消息id移除消息元素,仅在消息显示区中查找
clear():
移除消息显示区中的全部消息
static announce(message, options):
将消息通知给辅助技术
messageInternalWrapper(messageWrapper):
传递一个元素,在其内头部建立一个消息显示区(具备类属性“messages__wrapper”的div元素 ),并返回显示区的dom对象