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

export const isVisible = (element: HTMLElement) => {
  if (process.env.NODE_ENV === 'test') return true
  const computed = getComputedStyle(element)
  // element.offsetParent won't work on fix positioned
  // WARNING: potential issue here, going to need some expert advices on this issue
  return computed.position === 'fixed' ? false : element.offsetParent !== null
}

/* 
  getComputedStyle 获取到dom结构的样式对象集合

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

解析

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

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

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

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

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

obtainAllFocusableElements 

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

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

export const isFocusable = (element: HTMLElement): boolean => {
  if (
    element.tabIndex > 0 ||
    (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
  ) {
    return true
  }
  // HTMLButtonElement has disabled
  if ((element as HTMLButtonElement).disabled) {
    // HTMLButtonElement 表示按钮元素类型
    return false
  }

  switch (element.nodeName) {
    case 'A': {
      // casting current element to Specific HTMLElement in order to be more type precise
      return (
        !!(element as HTMLAnchorElement).href &&
        (element as HTMLAnchorElement).rel !== 'ignore'
      )
      /* 
        !!  (element as HTMLAnchorElement).href 
          !! 双重取反
              !!a 表示 a != ""&&a != undefined && a != null
            
        
        a.rel  用于指定当前文档与被链接文档的关系
      HTMLAnchorElement 表示锚点链接类型

      */
    }
    case 'INPUT': {
      return !(
        (element as HTMLInputElement).type === 'hidden' ||
        (element as HTMLInputElement).type === 'file'
      )
      /* 
        HTMLInputElement 输入元素类型
        as  typescript 中的类型断言 
          所谓类型断言指的是我知道 某个值的详细信息
      
      */
    }
    case 'BUTTON':
    case 'SELECT':
    case 'TEXTAREA': {
      return true
    }
    default: {
      return false
    }
  }
}

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

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

element.focus?.()

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

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

document.activeElement 

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

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

triggerEvent 自定义事件

export const triggerEvent = function (
  elm: HTMLElement,//当前dom元素
  name: string,//事件的名称
  ...opts: Array<boolean>//更多参数 opts,为一个数组, true 表示阻止事件冒泡 true 表示阻止默认行为
): HTMLElement {
  let eventName: string

  if (name.includes('mouse') || name.includes('click')) {
    // includes 表示是否包含 返回true或者false mouse 或者 click 都是鼠标事件 MouseEvents
    eventName = 'MouseEvents'
  } else if (name.includes('key')) {
    // key表示键盘事件
    eventName = 'KeyboardEvent'
  } else {
    // 既不是鼠标事件也不是键盘事件
    eventName = 'HTMLEvents'
  }
  const evt = document.createEvent(eventName)
  // 创建事件
  evt.initEvent(name, ...opts)
  // 初始化事件 
  // opts 是数组 boolean 值  第一个表示是否阻止冒泡  是否阻止默认行为
  // document.createEvent   dispatchEvent
  elm.dispatchEvent(evt)//触发事件

  /* 
    在其他地方可用document.addEventListener("事件名称") 来监听这些自定义事件
  */

  return elm
}

....opts:Array<boolean>

表示入参 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 获取到指定的兄弟元素


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

思路

  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 可以将伪数组转化为数组

完整代码

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])`

/**
 * Determine if the testing element is visible on screen no matter if its on the viewport or not
 */
export const isVisible = (element: HTMLElement) => {
  if (process.env.NODE_ENV === 'test') return true
  const computed = getComputedStyle(element)
  // element.offsetParent won't work on fix positioned
  // WARNING: potential issue here, going to need some expert advices on this issue
  return computed.position === 'fixed' ? false : element.offsetParent !== null
}

/* 
  getComputedStyle 获取到dom结构的样式对象集合

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








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

/**
 * @desc Determine if target element is focusable
 * @param element {HTMLElement}
 * @returns {Boolean} true if it is focusable
 */
export const isFocusable = (element: HTMLElement): boolean => {
  if (
    element.tabIndex > 0 ||
    (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
  ) {
    return true
  }
  // HTMLButtonElement has disabled
  if ((element as HTMLButtonElement).disabled) {
    // HTMLButtonElement 表示按钮元素类型
    return false
  }

  switch (element.nodeName) {
    case 'A': {
      // casting current element to Specific HTMLElement in order to be more type precise
      return (
        !!(element as HTMLAnchorElement).href &&
        (element as HTMLAnchorElement).rel !== 'ignore'
      )
      /* 
        !!  (element as HTMLAnchorElement).href 
          !! 双重取反
              !!a 表示 a != ""&&a != undefined && a != null
            
        
        a.rel  用于指定当前文档与被链接文档的关系
      HTMLAnchorElement 表示锚点链接类型

      
      
      */



    }
    case 'INPUT': {
      return !(
        (element as HTMLInputElement).type === 'hidden' ||
        (element as HTMLInputElement).type === 'file'
      )
      /* 
        HTMLInputElement 输入元素类型
        as  typescript 中的类型断言 
          所谓类型断言指的是我知道 某个值的详细信息
      
      */
    }
    case 'BUTTON':
    case 'SELECT':
    case 'TEXTAREA': {
      return true
    }
    default: {
      return false
    }
  }
}

/**
 * @desc Set Attempt to set focus on the current node.
 * @param element
 *          The node to attempt to focus on.
 * @returns
 *  true if element is focused.
 */
export const attemptFocus = (element: HTMLElement): boolean => {
  if (!isFocusable(element)) {
    // 判断是否可以获取焦点 入宫不可获取焦点就返回false
    return false
  }
  // Remove the old try catch block since there will be no error to be thrown
  element.focus?.()
  // ?. 表示 问号之前的成立才执行问号之后的代码
  return document.activeElement === element
  // document.activeElement 当前页面中获取焦点的元素
}

/**
 * Trigger an event
 * mouseenter, mouseleave, mouseover, keyup, change, click, etc.
 * @param  {HTMLElement} elm
 * @param  {String} name
 * @param  {*} opts
 */
export const   = function (
  elm: HTMLElement,//当前dom元素
  name: string,//事件的名称
  ...opts: Array<boolean>//更多参数 opts,为一个数组, true 表示阻止事件冒泡 true 表示阻止默认行为
): HTMLElement {
  let eventName: string

  if (name.includes('mouse') || name.includes('click')) {
    // includes 表示是否包含 返回true或者false mouse 或者 click 都是鼠标事件 MouseEvents
    eventName = 'MouseEvents'
  } else if (name.includes('key')) {
    // key表示键盘事件
    eventName = 'KeyboardEvent'
  } else {
    // 既不是鼠标事件也不是键盘事件
    eventName = 'HTMLEvents'
  }
  const evt = document.createEvent(eventName)
  // 创建事件
  evt.initEvent(name, ...opts)
  // 初始化事件 
  // opts 是数组 boolean 值  第一个表示是否阻止冒泡  是否阻止默认行为
  // document.createEvent   dispatchEvent
  elm.dispatchEvent(evt)//触发事件

  /* 
    在其他地方可用document.addEventListener("事件名称") 来监听这些自定义事件
  */

  return elm
}

export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns')

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

export const focusNode = (el: HTMLElement) => {
  if (!el) return
  el.focus()
  !isLeaf(el) && el.click()
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值