UserscriptAPIDom

https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI

当前为 2021-09-10 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/431998/969308/UserscriptAPIDom.js

  1. /**
  2. * UserscriptAPIDom
  3. *
  4. * 依赖于 `UserscriptAPI`。
  5. * @version 1.1.0.20210910
  6. * @author Laster2800
  7. * @see {@link https://gitee.com/liangjiancang/userscript/tree/master/lib/UserscriptAPI UserscriptAPI}
  8. */
  9. class UserscriptAPIDom {
  10. /**
  11. * @param {UserscriptAPI} api `UserscriptAPI`
  12. */
  13. constructor(api) {
  14. this.api = api
  15. }
  16.  
  17. /**
  18. * 设定元素位置,默认设定为绝对居中
  19. *
  20. * 要求该元素此时可见且尺寸为确定值(一般要求为块状元素)。
  21. * @param {HTMLElement} target 目标元素
  22. * @param {Object} [options] 选项
  23. * @param {string} [options.position='fixed'] 定位方式
  24. * @param {string} [options.top='50%'] `style.top`
  25. * @param {string} [options.left='50%'] `style.left`
  26. */
  27. setPosition(target, options) {
  28. options = {
  29. position: 'fixed',
  30. top: '50%',
  31. left: '50%',
  32. ...options,
  33. }
  34. target.style.position = options.position
  35. const style = window.getComputedStyle(target)
  36. const top = (Number.parseFloat(style.height) + Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)) / 2
  37. const left = (Number.parseFloat(style.width) + Number.parseFloat(style.paddingLeft) + Number.parseFloat(style.paddingRight)) / 2
  38. target.style.top = `calc(${options.top} - ${top}px)`
  39. target.style.left = `calc(${options.left} - ${left}px)`
  40. }
  41.  
  42. /**
  43. * @typedef FadeTargetElement
  44. * @property {string} [fadeInDisplay] 渐显开始后的 `display` 样式。若没有设定:
  45. * * 若当前 `display` 与 `fadeOutDisplay` 不同,默认值为当前 `display`。
  46. * * 若当前 `display` 与 `fadeOutDisplay` 相同,默认值为 `block`。
  47. * @property {string} [fadeOutDisplay='none'] 渐隐开始后的 `display` 样式
  48. * @property {number} [fadeInTime] 渐显时间;缺省时,元素的 `transition-duration` 必须与 `api.options.fadeTime` 一致
  49. * @property {number} [fadeOutTime] 渐隐时间;缺省时,元素的 `transition-duration` 必须与 `api.options.fadeTime` 一致
  50. * @property {string} [fadeInFunction='ease-in-out'] 渐显效果,应为符合 `transition-timing-function` 的有效值
  51. * @property {string} [fadeOutFunction='ease-in-out'] 渐隐效果,应为符合 `transition-timing-function` 的有效值
  52. * @property {boolean} [fadeInNoInteractive] 渐显期间是否禁止交互
  53. * @property {boolean} [fadeOutNoInteractive] 渐隐期间是否禁止交互
  54. */
  55. /**
  56. * 处理 HTML 元素的渐显和渐隐
  57. * @param {boolean} inOut 渐显/渐隐
  58. * @param {HTMLElement & FadeTargetElement} target HTML 元素
  59. * @param {() => void} [callback] 渐显/渐隐完成的回调函数
  60. */
  61. fade(inOut, target, callback) {
  62. const api = this.api
  63. let transitionChanged = false
  64. const fadeId = Date.now() // 等同于当前时间戳,其意义在于保证对于同一元素,后执行的操作必将覆盖前的操作
  65. const fadeOutDisplay = target.fadeOutDisplay ?? 'none'
  66. target._fadeId = fadeId
  67. if (inOut) { // 渐显
  68. let displayChanged = false
  69. if (typeof target.fadeInTime == 'number' || target.fadeInFunction) {
  70. target.style.transition = `opacity ${target.fadeInTime ?? api.options.fadeTime}ms ${target.fadeInFunction ?? 'ease-in-out'}`
  71. transitionChanged = true
  72. }
  73. if (target.fadeInNoInteractive) {
  74. target.style.pointerEvents = 'none'
  75. }
  76. const originalDisplay = window.getComputedStyle(target).display
  77. let fadeInDisplay = target.fadeInDisplay
  78. if (!fadeInDisplay) {
  79. if (originalDisplay != fadeOutDisplay) {
  80. fadeInDisplay = originalDisplay
  81. } else {
  82. fadeInDisplay = 'block'
  83. }
  84. }
  85. if (originalDisplay != fadeInDisplay) {
  86. target.style.display = fadeInDisplay
  87. displayChanged = true
  88. }
  89. setTimeout(() => {
  90. let success = false
  91. if (target._fadeId <= fadeId) {
  92. target.style.opacity = '1'
  93. success = true
  94. }
  95. setTimeout(() => {
  96. callback?.(success)
  97. if (target._fadeId <= fadeId) {
  98. if (transitionChanged) {
  99. target.style.transition = ''
  100. }
  101. if (target.fadeInNoInteractive) {
  102. target.style.pointerEvents = ''
  103. }
  104. }
  105. }, target.fadeInTime ?? api.options.fadeTime)
  106. }, displayChanged ? 10 : 0) // 此处的 10ms 是为了保证修改 display 后在浏览器上真正生效;按 HTML5 定义,浏览器需保证 display 在修改后 4ms 内生效,但实际上大部分浏览器貌似做不到,等个 10ms 再修改 opacity
  107. } else { // 渐隐
  108. if (typeof target.fadeOutTime == 'number' || target.fadeOutFunction) {
  109. target.style.transition = `opacity ${target.fadeOutTime ?? api.options.fadeTime}ms ${target.fadeOutFunction ?? 'ease-in-out'}`
  110. transitionChanged = true
  111. }
  112. if (target.fadeOutNoInteractive) {
  113. target.style.pointerEvents = 'none'
  114. }
  115. target.style.opacity = '0'
  116. setTimeout(() => {
  117. let success = false
  118. if (target._fadeId <= fadeId) {
  119. target.style.display = fadeOutDisplay
  120. success = true
  121. }
  122. callback?.(success)
  123. if (success) {
  124. if (transitionChanged) {
  125. target.style.transition = ''
  126. }
  127. if (target.fadeOutNoInteractive) {
  128. target.style.pointerEvents = ''
  129. }
  130. }
  131. }, target.fadeOutTime ?? api.options.fadeTime)
  132. }
  133. }
  134.  
  135. /**
  136. * 判断 HTML 元素类名中是否含有 `class`
  137. * @param {HTMLElement} el 目标元素
  138. * @param {string | string[]} className `class`,支持同时判断多个
  139. * @param {boolean} [and] 同时判断多个 `class` 时,默认采取 `OR` 逻辑,是否采用 `AND` 逻辑
  140. * @returns {boolean} 是否含有 `class`
  141. */
  142. containsClass(el, className, and = false) {
  143. if (el.classList) {
  144. const trim = clz => clz.startsWith('.') ? clz.slice(1) : clz
  145. if (Array.isArray(className)) {
  146. if (and) {
  147. for (const c of className) {
  148. if (!el.classList.contains(trim(c))) {
  149. return false
  150. }
  151. }
  152. return true
  153. } else {
  154. for (const c of className) {
  155. if (el.classList.contains(trim(c))) {
  156. return true
  157. }
  158. }
  159. return false
  160. }
  161. } else {
  162. return el.classList.contains(trim(className))
  163. }
  164. }
  165. return false
  166. }
  167.  
  168. /**
  169. * 判断 HTML 元素是否为 `fixed` 定位,或其是否在 `fixed` 定位的元素下
  170. * @param {HTMLElement} el 目标元素
  171. * @param {HTMLElement} [endEl] 终止元素,当搜索到该元素时终止判断(不会判断该元素)
  172. * @returns {boolean} HTML 元素是否为 `fixed` 定位,或其是否在 `fixed` 定位的元素下
  173. */
  174. isFixed(el, endEl) {
  175. while (el && el != endEl) {
  176. if (window.getComputedStyle(el).position == 'fixed') {
  177. return true
  178. }
  179. el = el.offsetParent
  180. }
  181. return false
  182. }
  183. }
  184.  
  185. /* global UserscriptAPI */
  186. { UserscriptAPI.registerModule('dom', UserscriptAPIDom) }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址