utils.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. function mapTags(tag) {
  2. let ret = ''
  3. switch (tag) {
  4. case 'A':
  5. ret = 'Link'
  6. break
  7. case 'BUTTON':
  8. ret = 'Button'
  9. break
  10. case 'IMG':
  11. ret = 'Image'
  12. break
  13. case 'INPUT':
  14. ret = 'Textbox'
  15. break
  16. case 'TEXTAREA':
  17. ret = 'Textbox'
  18. break
  19. default:
  20. ret = 'Text'
  21. break
  22. }
  23. return ret
  24. }
  25. function extractTextForMagnify(e) {
  26. let meaningfulNode = e.path[0]
  27. if (e.type === 'mouseover') {
  28. while (!meaningfulNode.getAttribute || !meaningfulNode.getAttribute('tabindex')) {
  29. meaningfulNode = meaningfulNode.parentNode
  30. if (!meaningfulNode) {
  31. return
  32. }
  33. }
  34. if (meaningfulNode.getAttribute('tabindex') === '-1') {
  35. return
  36. }
  37. // mouseover事件冒泡到有data-aria-xxx-area attribute的区域包裹元素时,不应该提取该区域包裹元素的无障碍辅助信息。
  38. if (
  39. meaningfulNode.getAttribute('data-aria-navigation-area') !== null ||
  40. meaningfulNode.getAttribute('data-aria-viewport-area') !== null ||
  41. meaningfulNode.getAttribute('data-aria-interaction-area') !== null
  42. ) {
  43. return
  44. }
  45. } else if (e.type === 'focusin') {
  46. // 如果天然能focus,但没有被加上tabindex属性,比如focus到了第三方组件内部的可focus元素,直接返回。
  47. if (
  48. ['A', 'AREA', 'BUTTON', 'INPUT', 'SELECT', 'IFRAME'].includes(meaningfulNode.tagName) &&
  49. !meaningfulNode.getAttribute('tabindex')
  50. ) {
  51. return
  52. }
  53. while (!meaningfulNode.getAttribute || !meaningfulNode.getAttribute('tabindex')) {
  54. meaningfulNode = meaningfulNode.parentNode
  55. if (!meaningfulNode) {
  56. return
  57. }
  58. }
  59. // 约定:tabindex属性值为-1的元素只用于在点击有data-aria-xxx-area attribute的区域包裹元素的子孙元素时,避免focus到区域包裹元素。
  60. if (meaningfulNode.getAttribute('tabindex') === '-1') {
  61. return
  62. }
  63. }
  64. let elemType = ''
  65. const ariaLabel = meaningfulNode.getAttribute('aria-label')
  66. if (ariaLabel !== null) {
  67. elemType = ariaLabel
  68. } else {
  69. elemType = mapTags(meaningfulNode.tagName)
  70. }
  71. let elemDisc = ''
  72. const ariaDescription = meaningfulNode.getAttribute('aria-description')
  73. if (ariaDescription !== null) {
  74. elemDisc = ariaDescription
  75. } else {
  76. elemDisc = meaningfulNode.innerText
  77. }
  78. return {
  79. elemType,
  80. elemDisc
  81. }
  82. }
  83. function isObject(p) {
  84. return Object.prototype.toString.call(p) === '[object Object]'
  85. }
  86. // 判断两个对象内容是否相同
  87. function isSameObject(object1, object2) {
  88. const keys1 = Object.keys(object1)
  89. const keys2 = Object.keys(object2)
  90. if (keys1.length !== keys2.length) {
  91. return false
  92. }
  93. for (let index = 0; index < keys1.length; index++) {
  94. const val1 = object1[keys1[index]]
  95. const val2 = object2[keys2[index]]
  96. const areObjects = isObject(val1) && isObject(val2)
  97. if (
  98. (areObjects && !isSameObject(val1, val2)) ||
  99. (!areObjects && (val1 !== val2))
  100. ) {
  101. return false
  102. }
  103. }
  104. return true
  105. }
  106. function getAndFocusNextNodeWithCustomAttribute(attriName) {
  107. const startNode = (document.activeElement || document.body)
  108. const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT)
  109. treeWalker.currentNode = startNode
  110. let targetNode = null
  111. // eslint-disable-next-line
  112. while(true) {
  113. const nextNode = treeWalker.nextNode()
  114. if (!nextNode) {
  115. console.log('往下没找到')
  116. break
  117. }
  118. if (nextNode.dataset[attriName] !== undefined) {
  119. console.log('往下找到了')
  120. targetNode = nextNode
  121. break
  122. }
  123. }
  124. if (!targetNode && (startNode !== document.body)) {
  125. treeWalker.currentNode = document.body
  126. // eslint-disable-next-line
  127. while(true) {
  128. const nextNode = treeWalker.nextNode()
  129. if (!nextNode) {
  130. console.log('往上也没找到')
  131. break
  132. }
  133. if (nextNode.dataset[attriName] !== undefined) {
  134. console.log('往上找到了')
  135. targetNode = nextNode
  136. break
  137. }
  138. }
  139. }
  140. if (targetNode) {
  141. targetNode.focus()
  142. if (document.activeElement !== targetNode) {
  143. targetNode.setAttribute('tabindex', '0')
  144. targetNode.focus()
  145. }
  146. }
  147. return targetNode
  148. }
  149. function __focusNextFocusableNode(treeWalker) {
  150. // eslint-disable-next-line
  151. while(true) {
  152. const nextNode = treeWalker.nextNode()
  153. if (!nextNode) {
  154. return false
  155. }
  156. if (nextNode.focus) {
  157. nextNode.focus()
  158. if (document.activeElement === nextNode) {
  159. return true
  160. }
  161. }
  162. }
  163. }
  164. function iterateOnFocusableNode(startNode, focusedNodeHandler) {
  165. const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT)
  166. treeWalker.currentNode = startNode
  167. treeWalker.currentNode.focus()
  168. if (document.activeElement === treeWalker.currentNode) {
  169. // console.log('起始节点可以focus')
  170. } else {
  171. // console.log('起始节点不可以focus,focus到下一节点。')
  172. const ret = __focusNextFocusableNode(treeWalker)
  173. if (!ret) {
  174. return
  175. }
  176. }
  177. const iterator = () => {
  178. focusedNodeHandler(treeWalker.currentNode).then(() => {
  179. const result = __focusNextFocusableNode(treeWalker)
  180. if (result) {
  181. // console.log('遍历到下一个节点!')
  182. iterator()
  183. } else {
  184. // console.log('遍历结束!')
  185. }
  186. }).catch((e) => {
  187. // console.log('遍历中止!', e)
  188. })
  189. }
  190. iterator()
  191. }
  192. /**
  193. * 返回一个自带消抖效果的函数,用res表示。
  194. *
  195. * fn: 需要被消抖的函数
  196. * delay: 消抖时长
  197. * isImmediateCall: 是否在一组操作中的第一次调用时立即执行fn
  198. * isRememberLastCall:是否在一组中最后一次调用后等delay时长再执行fn
  199. */
  200. function debounce(fn, delay, isImmediateCall = false, isRememberLastCall = true) {
  201. console.assert(isImmediateCall || isRememberLastCall, 'isImmediateCall 和 isRememberLastCall 至少应有一个是true,否则没有意义!')
  202. let timer = null
  203. // 上次调用的时刻
  204. let lastCallTime = 0
  205. if (isImmediateCall && !isRememberLastCall) {
  206. return function (...args) {
  207. const currentTime = Date.now()
  208. if (currentTime - lastCallTime >= delay) {
  209. fn.apply(this, args)
  210. }
  211. lastCallTime = currentTime
  212. }
  213. } else if (!isImmediateCall && isRememberLastCall) {
  214. return function (...args) {
  215. if (timer) {
  216. clearTimeout(timer)
  217. }
  218. timer = setTimeout(() => {
  219. fn.apply(this, args)
  220. }, delay)
  221. }
  222. } else if (isImmediateCall && isRememberLastCall) {
  223. return function (...args) {
  224. const currentTime = Date.now()
  225. if (currentTime - lastCallTime >= delay) { // 一组操作中的第一次
  226. fn.apply(this, args)
  227. lastCallTime = currentTime
  228. return
  229. } else { // 一组中的后续调用
  230. if (timer) { // 在此之前存在中间调用
  231. lastCallTime = currentTime
  232. clearTimeout(timer)
  233. }
  234. timer = setTimeout(() => {
  235. fn.apply(this, args)
  236. lastCallTime = 0
  237. timer = null
  238. }, delay)
  239. }
  240. }
  241. } else {
  242. console.error('不应该执行到这里!')
  243. }
  244. }
  245. class DebounceScheduler {
  246. constructor(fn, delay, context, isImmediateCall = false) {
  247. this.job = fn
  248. this.delay = delay
  249. this.context = context
  250. this.timer = null
  251. this.lastCallTime = 0
  252. this.isImmediateCall = isImmediateCall
  253. }
  254. planToDo(...args) {
  255. if (!this.isImmediateCall) {
  256. if (this.timer) {
  257. clearTimeout(this.timer)
  258. this.timer = null
  259. }
  260. this.timer = setTimeout(() => {
  261. this.job.apply(this.context, args)
  262. }, this.delay)
  263. } else {
  264. const currentTime = Date.now()
  265. if (currentTime - this.lastCallTime >= this.delay) { // 一组操作中的第一次
  266. this.job.apply(this.context, args)
  267. this.lastCallTime = currentTime
  268. return
  269. } else { // 一组中的后续调用
  270. if (this.timer) { // 在此之前存在中间调用
  271. this.lastCallTime = currentTime
  272. clearTimeout(this.timer)
  273. this.timer = null
  274. }
  275. this.timer = setTimeout(() => {
  276. this.job.apply(this.context, args)
  277. this.lastCallTime = 0
  278. this.timer = null
  279. }, this.delay)
  280. }
  281. }
  282. }
  283. cancel() {
  284. if (this.timer) {
  285. clearTimeout(this.timer)
  286. this.timer = null
  287. }
  288. }
  289. }
  290. export default {
  291. mapTags,
  292. extractTextForMagnify,
  293. isSameObject,
  294. getAndFocusNextNodeWithCustomAttribute,
  295. iterateOnFocusableNode,
  296. debounce,
  297. DebounceScheduler,
  298. }