ResizeObserverSPI.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import {Map} from './shims/es6-collections.js';
  2. import ResizeObservation from './ResizeObservation.js';
  3. import ResizeObserverEntry from './ResizeObserverEntry.js';
  4. import getWindowOf from './utils/getWindowOf.js';
  5. export default class ResizeObserverSPI {
  6. /**
  7. * Collection of resize observations that have detected changes in dimensions
  8. * of elements.
  9. *
  10. * @private {Array<ResizeObservation>}
  11. */
  12. activeObservations_ = [];
  13. /**
  14. * Reference to the callback function.
  15. *
  16. * @private {ResizeObserverCallback}
  17. */
  18. callback_;
  19. /**
  20. * Public ResizeObserver instance which will be passed to the callback
  21. * function and used as a value of it's "this" binding.
  22. *
  23. * @private {ResizeObserver}
  24. */
  25. callbackCtx_;
  26. /**
  27. * Reference to the associated ResizeObserverController.
  28. *
  29. * @private {ResizeObserverController}
  30. */
  31. controller_;
  32. /**
  33. * Registry of the ResizeObservation instances.
  34. *
  35. * @private {Map<Element, ResizeObservation>}
  36. */
  37. observations_ = new Map();
  38. /**
  39. * Creates a new instance of ResizeObserver.
  40. *
  41. * @param {ResizeObserverCallback} callback - Callback function that is invoked
  42. * when one of the observed elements changes it's content dimensions.
  43. * @param {ResizeObserverController} controller - Controller instance which
  44. * is responsible for the updates of observer.
  45. * @param {ResizeObserver} callbackCtx - Reference to the public
  46. * ResizeObserver instance which will be passed to callback function.
  47. */
  48. constructor(callback, controller, callbackCtx) {
  49. if (typeof callback !== 'function') {
  50. throw new TypeError('The callback provided as parameter 1 is not a function.');
  51. }
  52. this.callback_ = callback;
  53. this.controller_ = controller;
  54. this.callbackCtx_ = callbackCtx;
  55. }
  56. /**
  57. * Starts observing provided element.
  58. *
  59. * @param {Element} target - Element to be observed.
  60. * @returns {void}
  61. */
  62. observe(target) {
  63. if (!arguments.length) {
  64. throw new TypeError('1 argument required, but only 0 present.');
  65. }
  66. // Do nothing if current environment doesn't have the Element interface.
  67. if (typeof Element === 'undefined' || !(Element instanceof Object)) {
  68. return;
  69. }
  70. if (!(target instanceof getWindowOf(target).Element)) {
  71. throw new TypeError('parameter 1 is not of type "Element".');
  72. }
  73. const observations = this.observations_;
  74. // Do nothing if element is already being observed.
  75. if (observations.has(target)) {
  76. return;
  77. }
  78. observations.set(target, new ResizeObservation(target));
  79. this.controller_.addObserver(this);
  80. // Force the update of observations.
  81. this.controller_.refresh();
  82. }
  83. /**
  84. * Stops observing provided element.
  85. *
  86. * @param {Element} target - Element to stop observing.
  87. * @returns {void}
  88. */
  89. unobserve(target) {
  90. if (!arguments.length) {
  91. throw new TypeError('1 argument required, but only 0 present.');
  92. }
  93. // Do nothing if current environment doesn't have the Element interface.
  94. if (typeof Element === 'undefined' || !(Element instanceof Object)) {
  95. return;
  96. }
  97. if (!(target instanceof getWindowOf(target).Element)) {
  98. throw new TypeError('parameter 1 is not of type "Element".');
  99. }
  100. const observations = this.observations_;
  101. // Do nothing if element is not being observed.
  102. if (!observations.has(target)) {
  103. return;
  104. }
  105. observations.delete(target);
  106. if (!observations.size) {
  107. this.controller_.removeObserver(this);
  108. }
  109. }
  110. /**
  111. * Stops observing all elements.
  112. *
  113. * @returns {void}
  114. */
  115. disconnect() {
  116. this.clearActive();
  117. this.observations_.clear();
  118. this.controller_.removeObserver(this);
  119. }
  120. /**
  121. * Collects observation instances the associated element of which has changed
  122. * it's content rectangle.
  123. *
  124. * @returns {void}
  125. */
  126. gatherActive() {
  127. this.clearActive();
  128. this.observations_.forEach(observation => {
  129. if (observation.isActive()) {
  130. this.activeObservations_.push(observation);
  131. }
  132. });
  133. }
  134. /**
  135. * Invokes initial callback function with a list of ResizeObserverEntry
  136. * instances collected from active resize observations.
  137. *
  138. * @returns {void}
  139. */
  140. broadcastActive() {
  141. // Do nothing if observer doesn't have active observations.
  142. if (!this.hasActive()) {
  143. return;
  144. }
  145. const ctx = this.callbackCtx_;
  146. // Create ResizeObserverEntry instance for every active observation.
  147. const entries = this.activeObservations_.map(observation => {
  148. return new ResizeObserverEntry(
  149. observation.target,
  150. observation.broadcastRect()
  151. );
  152. });
  153. this.callback_.call(ctx, entries, ctx);
  154. this.clearActive();
  155. }
  156. /**
  157. * Clears the collection of active observations.
  158. *
  159. * @returns {void}
  160. */
  161. clearActive() {
  162. this.activeObservations_.splice(0);
  163. }
  164. /**
  165. * Tells whether observer has active observations.
  166. *
  167. * @returns {boolean}
  168. */
  169. hasActive() {
  170. return this.activeObservations_.length > 0;
  171. }
  172. }