utils.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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. // 如果天然能focus,但没有被加上tabindex属性,比如focus到了第三方组件内部的可focus元素,直接返回。
  28. if (
  29. ['A', 'AREA', 'BUTTON', 'INPUT', 'SELECT', 'IFRAME'].includes(meaningfulNode.tagName) &&
  30. !meaningfulNode.getAttribute('tabindex')
  31. ) {
  32. return
  33. }
  34. while (!meaningfulNode.getAttribute || !meaningfulNode.getAttribute('tabindex')) {
  35. meaningfulNode = meaningfulNode.parentNode
  36. if (!meaningfulNode) {
  37. return
  38. }
  39. }
  40. // mouseover事件冒泡到有data-aria-xxx-area attribute的区域时,不应该提取该区域的无障碍辅助信息。
  41. if (e.type === 'mouseover' && (
  42. meaningfulNode.getAttribute('data-aria-navigation-area') !== null ||
  43. meaningfulNode.getAttribute('data-aria-viewport-area') !== null ||
  44. meaningfulNode.getAttribute('data-aria-interaction-area') !== null
  45. )
  46. ) {
  47. return
  48. }
  49. let elemType = ''
  50. const ariaLabel = meaningfulNode.getAttribute('aria-label')
  51. if (ariaLabel !== null) {
  52. elemType = ariaLabel
  53. } else {
  54. elemType = mapTags(meaningfulNode.tagName)
  55. }
  56. let elemDisc = ''
  57. const ariaDescription = meaningfulNode.getAttribute('aria-description')
  58. if (ariaDescription !== null) {
  59. elemDisc = ariaDescription
  60. } else {
  61. elemDisc = meaningfulNode.innerText
  62. }
  63. return {
  64. elemType,
  65. elemDisc
  66. }
  67. }
  68. function isObject(p) {
  69. return Object.prototype.toString.call(p) === '[object Object]'
  70. }
  71. // 判断两个对象内容是否相同
  72. function isSameObject(object1, object2) {
  73. const keys1 = Object.keys(object1)
  74. const keys2 = Object.keys(object2)
  75. if (keys1.length !== keys2.length) {
  76. return false
  77. }
  78. for (let index = 0; index < keys1.length; index++) {
  79. const val1 = object1[keys1[index]]
  80. const val2 = object2[keys2[index]]
  81. const areObjects = isObject(val1) && isObject(val2)
  82. if (
  83. (areObjects && !isSameObject(val1, val2)) ||
  84. (!areObjects && (val1 !== val2))
  85. ) {
  86. return false
  87. }
  88. }
  89. return true
  90. }
  91. function getAndFocusNextNodeWithCustomAttribute(attriName) {
  92. const startNode = (document.activeElement || document.body)
  93. const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT)
  94. treeWalker.currentNode = startNode
  95. let targetNode = null
  96. // eslint-disable-next-line
  97. while(true) {
  98. const nextNode = treeWalker.nextNode()
  99. if (!nextNode) {
  100. console.log('往下没找到')
  101. break
  102. }
  103. if (nextNode.dataset[attriName] !== undefined) {
  104. console.log('往下找到了')
  105. targetNode = nextNode
  106. break
  107. }
  108. }
  109. if (!targetNode && (startNode !== document.body)) {
  110. treeWalker.currentNode = document.body
  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. }
  125. if (targetNode) {
  126. targetNode.focus()
  127. if (document.activeElement !== targetNode) {
  128. targetNode.setAttribute('tabindex', '0')
  129. targetNode.focus()
  130. }
  131. }
  132. return targetNode
  133. }
  134. function __focusNextFocusableNode(treeWalker) {
  135. // eslint-disable-next-line
  136. while(true) {
  137. const nextNode = treeWalker.nextNode()
  138. if (!nextNode) {
  139. return false
  140. }
  141. if (nextNode.focus) {
  142. nextNode.focus()
  143. if (document.activeElement === nextNode) {
  144. return true
  145. }
  146. }
  147. }
  148. }
  149. function iterateOnFocusableNode(startNode, focusedNodeHandler) {
  150. const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT)
  151. treeWalker.currentNode = startNode
  152. treeWalker.currentNode.focus()
  153. if (document.activeElement === treeWalker.currentNode) {
  154. // console.log('起始节点可以focus')
  155. } else {
  156. // console.log('起始节点不可以focus,focus到下一节点。')
  157. const ret = __focusNextFocusableNode(treeWalker)
  158. if (!ret) {
  159. return
  160. }
  161. }
  162. const iterator = () => {
  163. focusedNodeHandler(treeWalker.currentNode).then(() => {
  164. const result = __focusNextFocusableNode(treeWalker)
  165. if (result) {
  166. // console.log('遍历到下一个节点!')
  167. iterator()
  168. } else {
  169. // console.log('遍历结束!')
  170. }
  171. }).catch((e) => {
  172. // console.log('遍历中止!', e)
  173. })
  174. }
  175. iterator()
  176. }
  177. /**
  178. * 返回一个自带消抖效果的函数,用res表示。
  179. *
  180. * fn: 需要被消抖的函数
  181. * delay: 消抖时长
  182. * isImmediateCall: 是在第一次调用时立即执行fn,还是在最后一次调用后等delay时长再调用fn
  183. */
  184. function debounce(fn, delay, isImmediateCall = false) {
  185. let timer = null
  186. // 上次调用的时刻
  187. let lastCallTime = 0
  188. if (isImmediateCall) {
  189. return function (...args) {
  190. const context = this
  191. const currentTime = Date.now()
  192. if (currentTime - lastCallTime >= delay) {
  193. fn.apply(context, args)
  194. }
  195. lastCallTime = currentTime
  196. }
  197. } else {
  198. return function (...args) {
  199. if (timer) {
  200. clearTimeout(timer)
  201. }
  202. const context = this
  203. timer = setTimeout(() => {
  204. fn.apply(context, args)
  205. }, delay)
  206. }
  207. }
  208. }
  209. export default {
  210. mapTags,
  211. extractTextForMagnify,
  212. isSameObject,
  213. iterateOnFocusableNode,
  214. debounce,
  215. getAndFocusNextNodeWithCustomAttribute,
  216. }