index.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. 'use strict'
  2. var attrs: string[] = ['top', 'left', 'right', 'bottom']
  3. var inited: boolean
  4. var elementComputedStyle = {}
  5. var support: string
  6. function getSupport() {
  7. if(!('CSS' in window) || typeof CSS.supports != 'function') {
  8. support = ''
  9. } else if(CSS.supports('top: env(safe-area-inset-top)')) {
  10. support = 'env'
  11. } else if(CSS.supports('top: constant(safe-area-inset-top)')) {
  12. support = 'constant'
  13. } else {
  14. support = ''
  15. }
  16. return support
  17. }
  18. function init() {
  19. support = typeof support === 'string' ? support : getSupport()
  20. if(!support) {
  21. attrs.forEach((attr: string) => {
  22. elementComputedStyle[attr] = 0
  23. })
  24. return
  25. }
  26. function setStyle(el: HTMLElement, style) {
  27. var elStyle: CSSStyleDeclaration = el.style
  28. Object.keys(style).forEach(key => {
  29. var val: string = style[key]
  30. elStyle[key] = val
  31. })
  32. }
  33. var cbs: Function[] = []
  34. function parentReady(callback?: Function) {
  35. if(callback) {
  36. cbs.push(callback)
  37. } else {
  38. cbs.forEach(cb => {
  39. cb()
  40. })
  41. }
  42. }
  43. var passiveEvents: any = false
  44. try {
  45. var opts = Object.defineProperty({}, 'passive', {
  46. get: function() {
  47. passiveEvents = { passive: true }
  48. }
  49. })
  50. window.addEventListener('test', null, opts)
  51. } catch(e) {
  52. }
  53. function addChild(parent: HTMLElement, attr: string) {
  54. var a1: HTMLElement = document.createElement('div')
  55. var a2: HTMLElement = document.createElement('div')
  56. var a1Children: HTMLElement = document.createElement('div')
  57. var a2Children: HTMLElement = document.createElement('div')
  58. var W: number = 100
  59. var MAX: number = 10000
  60. var aStyle = {
  61. position: 'absolute',
  62. width: W + 'px',
  63. height: '200px',
  64. boxSizing: 'border-box',
  65. overflow: 'hidden',
  66. paddingBottom: `${support}(safe-area-inset-${attr})`
  67. }
  68. setStyle(a1, aStyle)
  69. setStyle(a2, aStyle)
  70. setStyle(a1Children, {
  71. transition: '0s',
  72. animation: 'none',
  73. width: '400px',
  74. height: '400px'
  75. })
  76. setStyle(a2Children, {
  77. transition: '0s',
  78. animation: 'none',
  79. width: '250%',
  80. height: '250%'
  81. })
  82. a1.appendChild(a1Children)
  83. a2.appendChild(a2Children)
  84. parent.appendChild(a1)
  85. parent.appendChild(a2)
  86. parentReady(() => {
  87. a1.scrollTop = a2.scrollTop = MAX
  88. var a1LastScrollTop: number = a1.scrollTop
  89. var a2LastScrollTop: number = a2.scrollTop
  90. function onScroll() {
  91. if(this.scrollTop === (this === a1 ? a1LastScrollTop : a2LastScrollTop)) {
  92. return
  93. }
  94. a1.scrollTop = a2.scrollTop = MAX
  95. a1LastScrollTop = a1.scrollTop
  96. a2LastScrollTop = a2.scrollTop
  97. attrChange(attr)
  98. }
  99. a1.addEventListener('scroll', onScroll, passiveEvents)
  100. a2.addEventListener('scroll', onScroll, passiveEvents)
  101. })
  102. var computedStyle: CSSStyleDeclaration = getComputedStyle(a1)
  103. Object.defineProperty(elementComputedStyle, attr, {
  104. configurable: true,
  105. get() {
  106. return parseFloat(computedStyle.paddingBottom)
  107. }
  108. })
  109. }
  110. var parentDiv: HTMLElement = document.createElement('div')
  111. setStyle(parentDiv, {
  112. position: 'absolute',
  113. left: '0',
  114. top: '0',
  115. width: '0',
  116. height: '0',
  117. zIndex: '-1',
  118. overflow: 'hidden',
  119. visibility: 'hidden',
  120. })
  121. attrs.forEach(key => {
  122. addChild(parentDiv, key)
  123. })
  124. document.body.appendChild(parentDiv)
  125. parentReady()
  126. inited = true
  127. }
  128. function getAttr(attr: string): number {
  129. if(!inited) {
  130. init()
  131. }
  132. return elementComputedStyle[attr]
  133. }
  134. var changeAttrs: string[] = []
  135. function attrChange(attr: string) {
  136. if(!changeAttrs.length) {
  137. setTimeout(() => {
  138. var style = {}
  139. changeAttrs.forEach(attr => {
  140. style[attr] = elementComputedStyle[attr]
  141. })
  142. changeAttrs.length = 0
  143. callbacks.forEach(callback => {
  144. callback(style)
  145. })
  146. }, 0)
  147. }
  148. changeAttrs.push(attr)
  149. }
  150. var callbacks: Function[] = []
  151. function onChange(callback: Function) {
  152. if(!getSupport()) {
  153. return
  154. }
  155. if(!inited) {
  156. init()
  157. }
  158. if(typeof callback === 'function') {
  159. callbacks.push(callback)
  160. }
  161. }
  162. function offChange(callback: Function) {
  163. var index = callbacks.indexOf(callback)
  164. if(index >= 0){
  165. callbacks.splice(index, 1)
  166. }
  167. }
  168. var safeAreaInsets = {
  169. get support(): boolean {
  170. return (typeof support === 'string' ? support : getSupport()).length != 0
  171. },
  172. get top(): number {
  173. return getAttr('top')
  174. },
  175. get left(): number {
  176. return getAttr('left')
  177. },
  178. get right(): number {
  179. return getAttr('right')
  180. },
  181. get bottom(): number {
  182. return getAttr('bottom')
  183. },
  184. onChange,
  185. offChange
  186. }
  187. export = safeAreaInsets