• VUE3 element-plus源码解析之- 001 dom aria.ts 文件解析


    FOCUSABLE_ELEMENT_SELECTORS 

    const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`
    

    isVisible

    1. export const isVisible = (element: HTMLElement) => {
    2. if (process.env.NODE_ENV === 'test') return true
    3. const computed = getComputedStyle(element)
    4. // element.offsetParent won't work on fix positioned
    5. // WARNING: potential issue here, going to need some expert advices on this issue
    6. return computed.position === 'fixed' ? false : element.offsetParent !== null
    7. }
    8. /*
    9. getComputedStyle 获取到dom结构的样式对象集合
    10. offsetParent
    11. 元素自身有fixed定位,父元素不存在定位,则offsetParent的结果未null
    12. 元素自身无fixed定位,且父元素不存在定位,offsetParent为 body元素
    13. 元素自身无fixed定位,且父元素存在定位,offsetParent为离自身最近且经过定位的父元素
    14. body 元素的offsetParent是null
    15. */

    解析

    getComputedStyle 获取指定dom元素的样式属性结合

    返回的是一个对象 通过属性名称即可访问 

    如  getComputedStyle(document.getElementById("dom"))['width] 即可访问到dom元素的宽度

    offsetParent 距离当前元素最近的进行过定位的父元素

        素自身有fixed定位,父元素不存在定位,则offsetParent的结果未null
        元素自身无fixed定位,且父元素不存在定位,offsetParent为 body元素
        元素自身无fixed定位,且父元素存在定位,offsetParent为离自身最近且经过定位的父元素
        body 元素的offsetParent是null

    obtainAllFocusableElements 

    1. export const obtainAllFocusableElements = (
    2. element: HTMLElement
    3. ): HTMLElement[] => {
    4. return Array.from(
    5. element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENT_SELECTORS)
    6. ).filter((item: HTMLElement) => isFocusable(item) && isVisible(item))
    7. }
    8. /*
    9. obtainAllFocusableElements
    10. 入参
    11. HTMLElement 单个dom元素
    12. 返回值
    13. HTMLElement [] dom元素集合的 数组
    14. Array.from 将伪数组转换为真实的数组
    15. element.querySelectorAll 查找当前元素下的所有 满足条件的子元素 FOCUSABLE_ELEMENT_SELECTORS(子元素类名集合)
    16. filter 过滤
    17. isFocusable 和 isVisible 调用处理都返回true的
    18. */

     obtainAllFocusableElements
      入参   
        HTMLElement 单个dom元素
      返回值
        HTMLElement []  dom元素集合的 数组
      Array.from 将伪数组转换为真实的数组
        element.querySelectorAll 查找当前元素下的所有 满足条件的子元素 FOCUSABLE_ELEMENT_SELECTORS(子元素类名集合)
      filter 过滤
      isFocusable 和 isVisible 调用处理都返回true的

    1. export const isFocusable = (element: HTMLElement): boolean => {
    2. if (
    3. element.tabIndex > 0 ||
    4. (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
    5. ) {
    6. return true
    7. }
    8. // HTMLButtonElement has disabled
    9. if ((element as HTMLButtonElement).disabled) {
    10. // HTMLButtonElement 表示按钮元素类型
    11. return false
    12. }
    13. switch (element.nodeName) {
    14. case 'A': {
    15. // casting current element to Specific HTMLElement in order to be more type precise
    16. return (
    17. !!(element as HTMLAnchorElement).href &&
    18. (element as HTMLAnchorElement).rel !== 'ignore'
    19. )
    20. /*
    21. !! (element as HTMLAnchorElement).href
    22. !! 双重取反
    23. !!a 表示 a != ""&&a != undefined && a != null
    24. a.rel 用于指定当前文档与被链接文档的关系
    25. HTMLAnchorElement 表示锚点链接类型
    26. */
    27. }
    28. case 'INPUT': {
    29. return !(
    30. (element as HTMLInputElement).type === 'hidden' ||
    31. (element as HTMLInputElement).type === 'file'
    32. )
    33. /*
    34. HTMLInputElement 输入元素类型
    35. as typescript 中的类型断言
    36. 所谓类型断言指的是我知道 某个值的详细信息
    37. */
    38. }
    39. case 'BUTTON':
    40. case 'SELECT':
    41. case 'TEXTAREA': {
    42. return true
    43. }
    44. default: {
    45. return false
    46. }
    47. }
    48. }

    tabIndex

    tabIndex 是全局属性,表示元素是否可以通过键盘导航 tab 选中

    如果 tabIndex 为 负值 小于 0 则不可以通过 tab选中

    tabIndex 设置为0 时 通常是为不可聚焦元素添加可聚焦属性

    tabIndex 为正数 大于 0 时 可以调整 被访问的优先级

    (element as HTMLButtonElement).disabled

    as 是断言 此处的作用是 认定了 element的类型为 HTMLButtonElement元素

    此处表达的是 如果 button元素有disabled属性为true 即 终止代码 不可获取焦点

    !! 双重取反

    !!a  等价于  a != ""  && a != null && a != undefined

     a.rel

    a.rel 用于指定当前文档于被链接文档的关系

    attemptFocus

    1. export const attemptFocus = (element: HTMLElement): boolean => {
    2. if (!isFocusable(element)) {
    3. // 判断是否可以获取焦点 入宫不可获取焦点就返回false
    4. return false
    5. }
    6. // Remove the old try catch block since there will be no error to be thrown
    7. element.focus?.()
    8. // ?. 表示 问号之前的成立才执行问号之后的代码
    9. return document.activeElement === element
    10. // document.activeElement 当前页面中获取焦点的元素
    11. }

    element.focus?.()

    element.focus?.() 表示 入宫 element的focus属性存在就调用

    相当于三元表达式 element.focus?element.focus():null

    document.activeElement 

    document.activeElement 表示当前页面中获取焦点的元素

    document.activeElement = element 表示把当前的 element 设置为获取 焦点的元素

    triggerEvent 自定义事件

    1. export const triggerEvent = function (
    2. elm: HTMLElement,//当前dom元素
    3. name: string,//事件的名称
    4. ...opts: Array<boolean>//更多参数 opts,为一个数组, true 表示阻止事件冒泡 true 表示阻止默认行为
    5. ): HTMLElement {
    6. let eventName: string
    7. if (name.includes('mouse') || name.includes('click')) {
    8. // includes 表示是否包含 返回true或者false mouse 或者 click 都是鼠标事件 MouseEvents
    9. eventName = 'MouseEvents'
    10. } else if (name.includes('key')) {
    11. // key表示键盘事件
    12. eventName = 'KeyboardEvent'
    13. } else {
    14. // 既不是鼠标事件也不是键盘事件
    15. eventName = 'HTMLEvents'
    16. }
    17. const evt = document.createEvent(eventName)
    18. // 创建事件
    19. evt.initEvent(name, ...opts)
    20. // 初始化事件
    21. // opts 是数组 boolean 值 第一个表示是否阻止冒泡 是否阻止默认行为
    22. // document.createEvent dispatchEvent
    23. elm.dispatchEvent(evt)//触发事件
    24. /*
    25. 在其他地方可用document.addEventListener("事件名称") 来监听这些自定义事件
    26. */
    27. return elm
    28. }

    ....opts:Array

    表示入参 opts 是一个类型为boolean的数组

    并将其解构出来

    document.createEvent(eventName)

    document.createEvent 创建自定义事件 

    并传入事件类型 eventName 

    eventName类型

    MouseEvents 

    鼠标事件  

    KeyboardEvent

    键盘事件

    HTMLEvents

    html事件 非 鼠标和键盘事件

    自定义事件

    let evt = document.createEvent(eventName)  创建自定义事件 并传入事件类型

    evt.initEvent(name,...opts) 初始化事件 并传入参数 自定义事件名称 和 是否阻止冒泡 是否阻止默认事件行为 阻止参数

    ele.dispatchEvent(evt)  触发自定义事件

    可通过 document.addEventListener(name,()=>{}) 监听自定义事件的触发

    getSibling 获取到指定的兄弟元素

    1. export const getSibling = (
    2. el: HTMLElement,//当前元素
    3. distance: number,//距离当前元素的位置
    4. elClass: string//元素的类名
    5. ) => {
    6. const { parentNode } = el//拿到当前元素的父元素
    7. if (!parentNode) return null//如果当前元素没有父元素就是 最顶级的元素 没有兄弟元素 就返回null
    8. const siblings = parentNode.querySelectorAll(elClass)//获取到所有兄弟元素的集合
    9. const index = Array.prototype.indexOf.call(siblings, el)// 获取到当前元素在所有兄弟元素集合中的位置 索引
    10. return siblings[index + distance] || null// 返回当前元素 距离兄弟元素指定位置的元素
    11. }

    思路

    1. 获取到当前元素的父元素
    2. 如果父元素不存在,就返回null
    3. 根据类名查找到所有的元素 
    4. 拿到当前元素的位置索引
    5. 通过 当前元素的索引和传入的distance相加即得到指定坐标的元素

    Array.prototype.indexOf.call(siblings, el)

    查找当前元素 el 在 siblings 的位置

    由于sibilings是伪数组 所以需要调用 Array.prototype.indexof.call

    等价于 Array.from(siblings).indexOf(el) Array.from 可以将伪数组转化为数组

    完整代码

    1. const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`
    2. /**
    3. * Determine if the testing element is visible on screen no matter if its on the viewport or not
    4. */
    5. export const isVisible = (element: HTMLElement) => {
    6. if (process.env.NODE_ENV === 'test') return true
    7. const computed = getComputedStyle(element)
    8. // element.offsetParent won't work on fix positioned
    9. // WARNING: potential issue here, going to need some expert advices on this issue
    10. return computed.position === 'fixed' ? false : element.offsetParent !== null
    11. }
    12. /*
    13. getComputedStyle 获取到dom结构的样式对象集合
    14. offsetParent
    15. 元素自身有fixed定位,父元素不存在定位,则offsetParent的结果未null
    16. 元素自身无fixed定位,且父元素不存在定位,offsetParent为 body元素
    17. 元素自身无fixed定位,且父元素存在定位,offsetParent为离自身最近且经过定位的父元素
    18. body 元素的offsetParent是null
    19. */
    20. export const obtainAllFocusableElements = (
    21. element: HTMLElement
    22. ): HTMLElement[] => {
    23. return Array.from(
    24. element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENT_SELECTORS)
    25. ).filter((item: HTMLElement) => isFocusable(item) && isVisible(item))
    26. }
    27. /*
    28. obtainAllFocusableElements
    29. 入参
    30. HTMLElement 单个dom元素
    31. 返回值
    32. HTMLElement [] dom元素集合的 数组
    33. Array.from 将伪数组转换为真实的数组
    34. element.querySelectorAll 查找当前元素下的所有 满足条件的子元素 FOCUSABLE_ELEMENT_SELECTORS(子元素类名集合)
    35. filter 过滤
    36. isFocusable 和 isVisible 调用处理都返回true的
    37. */
    38. /**
    39. * @desc Determine if target element is focusable
    40. * @param element {HTMLElement}
    41. * @returns {Boolean} true if it is focusable
    42. */
    43. export const isFocusable = (element: HTMLElement): boolean => {
    44. if (
    45. element.tabIndex > 0 ||
    46. (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
    47. ) {
    48. return true
    49. }
    50. // HTMLButtonElement has disabled
    51. if ((element as HTMLButtonElement).disabled) {
    52. // HTMLButtonElement 表示按钮元素类型
    53. return false
    54. }
    55. switch (element.nodeName) {
    56. case 'A': {
    57. // casting current element to Specific HTMLElement in order to be more type precise
    58. return (
    59. !!(element as HTMLAnchorElement).href &&
    60. (element as HTMLAnchorElement).rel !== 'ignore'
    61. )
    62. /*
    63. !! (element as HTMLAnchorElement).href
    64. !! 双重取反
    65. !!a 表示 a != ""&&a != undefined && a != null
    66. a.rel 用于指定当前文档与被链接文档的关系
    67. HTMLAnchorElement 表示锚点链接类型
    68. */
    69. }
    70. case 'INPUT': {
    71. return !(
    72. (element as HTMLInputElement).type === 'hidden' ||
    73. (element as HTMLInputElement).type === 'file'
    74. )
    75. /*
    76. HTMLInputElement 输入元素类型
    77. as typescript 中的类型断言
    78. 所谓类型断言指的是我知道 某个值的详细信息
    79. */
    80. }
    81. case 'BUTTON':
    82. case 'SELECT':
    83. case 'TEXTAREA': {
    84. return true
    85. }
    86. default: {
    87. return false
    88. }
    89. }
    90. }
    91. /**
    92. * @desc Set Attempt to set focus on the current node.
    93. * @param element
    94. * The node to attempt to focus on.
    95. * @returns
    96. * true if element is focused.
    97. */
    98. export const attemptFocus = (element: HTMLElement): boolean => {
    99. if (!isFocusable(element)) {
    100. // 判断是否可以获取焦点 入宫不可获取焦点就返回false
    101. return false
    102. }
    103. // Remove the old try catch block since there will be no error to be thrown
    104. element.focus?.()
    105. // ?. 表示 问号之前的成立才执行问号之后的代码
    106. return document.activeElement === element
    107. // document.activeElement 当前页面中获取焦点的元素
    108. }
    109. /**
    110. * Trigger an event
    111. * mouseenter, mouseleave, mouseover, keyup, change, click, etc.
    112. * @param {HTMLElement} elm
    113. * @param {String} name
    114. * @param {*} opts
    115. */
    116. export const = function (
    117. elm: HTMLElement,//当前dom元素
    118. name: string,//事件的名称
    119. ...opts: Array<boolean>//更多参数 opts,为一个数组, true 表示阻止事件冒泡 true 表示阻止默认行为
    120. ): HTMLElement {
    121. let eventName: string
    122. if (name.includes('mouse') || name.includes('click')) {
    123. // includes 表示是否包含 返回true或者false mouse 或者 click 都是鼠标事件 MouseEvents
    124. eventName = 'MouseEvents'
    125. } else if (name.includes('key')) {
    126. // key表示键盘事件
    127. eventName = 'KeyboardEvent'
    128. } else {
    129. // 既不是鼠标事件也不是键盘事件
    130. eventName = 'HTMLEvents'
    131. }
    132. const evt = document.createEvent(eventName)
    133. // 创建事件
    134. evt.initEvent(name, ...opts)
    135. // 初始化事件
    136. // opts 是数组 boolean 值 第一个表示是否阻止冒泡 是否阻止默认行为
    137. // document.createEvent dispatchEvent
    138. elm.dispatchEvent(evt)//触发事件
    139. /*
    140. 在其他地方可用document.addEventListener("事件名称") 来监听这些自定义事件
    141. */
    142. return elm
    143. }
    144. export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns')
    145. export const getSibling = (
    146. el: HTMLElement,//当前元素
    147. distance: number,//距离当前元素的位置
    148. elClass: string//元素的类名
    149. ) => {
    150. const { parentNode } = el//拿到当前元素的父元素
    151. if (!parentNode) return null//如果当前元素没有父元素就是 最顶级的元素 没有兄弟元素 就返回null
    152. const siblings = parentNode.querySelectorAll(elClass)//获取到所有兄弟元素的集合
    153. const index = Array.prototype.indexOf.call(siblings, el)// 获取到当前元素在所有兄弟元素集合中的位置 索引
    154. return siblings[index + distance] || null// 返回当前元素 距离兄弟元素指定位置的元素
    155. }
    156. export const focusNode = (el: HTMLElement) => {
    157. if (!el) return
    158. el.focus()
    159. !isLeaf(el) && el.click()
    160. }

  • 相关阅读:
    脚本-抽取配置文件
    Kubernetes - pod详解
    Python 完美诠释"高内聚"概念的 IO 流 API 体系结构
    Java基于SpringBoot+Vue+nodejs的企业公司人事管理系统 Element
    通讯网关软件022——利用CommGate X2MQTT实现MQTT访问MSSQL服务器
    mysql进阶: mysql中的锁(全局锁/表锁/行锁/间隙锁/临键锁/共享锁/排他锁)
    网站优化搜索引擎与关键词
    【TOOL】ceres学习笔记(二) —— 自定义函数练习
    不科学,RocketMQ生产者在一个应用服务竟然不能向多个NameServer发送消息
    20_ue4实现键盘控制物体自转(蓝图通讯2)
  • 原文地址:https://blog.csdn.net/qq_42389674/article/details/126550825