PageSourceSearch

https://www.williamsnd.com/wp-content/plugins/wp-proud-core/module…/angular-inview/angular-inview.js?ver=7.1.2

js williamsnd.com collected 2026-10-02 07:25:02 UTC 12,495 bytes, 382 lines download raw bytes

vendor: 12,495 bytes, lines 1-382
1// # Angular-Inview
2// - Author: [Nicola Peduzzi](https://github.com/thenikso)
3// - Repository: https://github.com/thenikso/angular-inview
4// - Install with: `npm install angular-inview@beta`
5// - Version: **2.2.0**
6(function() {
7'use strict';
8
9// An [angular.js](https://angularjs.org) directive to evaluate an expression if
10// a DOM element is or not in the current visible browser viewport.
11// Use it in your AngularJS app by including the javascript and requireing it:
12//
13// `angular.module('myApp', ['angular-inview'])`
14var angularInviewModule = angular.module('angular-inview', [])
15
16// ## in-view directive
17//
18// ### Usage
19// ```html
20// <any in-view="{expression}" [in-view-options="{object}"]></any>
21// ```
22.directive('inView', ['$parse', inViewDirective])
23
24// ## in-view-container directive
25.directive('inViewContainer', inViewContainerDirective);
26
27// ## Implementation
28function inViewDirective ($parse) {
29  return {
30    // Evaluate the expression passet to the attribute `in-view` when the DOM
31    // element is visible in the viewport.
32    restrict: 'A',
33    require: '?^^inViewContainer',
34    link: function inViewDirectiveLink (scope, element, attrs, container) {
35      // in-view-options attribute can be specified with an object expression
36      // containing:
37      //   - `offset`: An array of values to offset the element position.
38      //     Offsets are expressed as arrays of 4 numbers [top, right, bottom, left].
39      //     Like CSS, you can also specify only 2 numbers [top/bottom, left/right].
40      //     Instead of numbers, some array elements can be a string with a percentage.
41      //     Positive numbers are offsets outside the element rectangle and
42      //     negative numbers are offsets to the inside.
43      //   - `viewportOffset`: Like the element offset but appied to the viewport.
44      //   - `generateDirection`: Indicate if the `direction` information should
45      //     be included in `$inviewInfo` (default false).
46      //   - `generateParts`: Indicate if the `parts` information should
47      //     be included in `$inviewInfo` (default false).
48      //   - `throttle`: Specify a number of milliseconds by which to limit the
49      //     number of incoming events.
50      var options = {};
51      if (attrs.inViewOptions) {
52        options = scope.$eval(attrs.inViewOptions);
53      }
54      if (options.offset) {
55        options.offset = normalizeOffset(options.offset);
56      }
57      if (options.viewportOffset) {
58        options.viewportOffset = normalizeOffset(options.viewportOffset);
59      }
60
61      // Build reactive chain from an initial event
62      var viewportEventSignal = signalSingle({ type: 'initial' })
63
64      // Merged with the window events
65      .merge(signalFromEvent(window, 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup keydown'))
66
67      // Merge with container's events signal
68      if (container) {
69        viewportEventSignal = viewportEventSignal.merge(container.eventsSignal);
70      }
71
72      // Throttle if option specified
73      if (options.throttle) {
74        viewportEventSignal = viewportEventSignal.throttle(options.throttle);
75      }
76
77      // Map to viewport intersection and in-view informations
78      var inviewInfoSignal = viewportEventSignal
79
80      // Inview information structure contains:
81      //   - `inView`: a boolean value indicating if the element is
82      //     visible in the viewport;
83      //   - `changed`: a boolean value indicating if the inview status
84      //     changed after the last event;
85      //   - `event`: the event that initiated the in-view check;
86      .map(function(event) {
87        var viewportRect;
88        if (container) {
89          viewportRect = container.getViewportRect();
90          // TODO merge with actual window!
91        } else {
92          viewportRect = getViewportRect();
93        }
94        viewportRect = offsetRect(viewportRect, options.viewportOffset);
95        var elementRect = offsetRect(element[0].getBoundingClientRect(), options.offset);
96        var isVisible = !!(element[0].offsetWidth || element[0].offsetHeight || element[0].getClientRects().length);
97        var info = {
98          inView: isVisible && intersectRect(elementRect, viewportRect),
99          event: event,
100          element: element,
101          elementRect: elementRect,
102          viewportRect: viewportRect
103        };
104        // Add inview parts
105        if (options.generateParts && info.inView) {
106          info.parts = {};
107          info.parts.top = elementRect.top >= viewportRect.top;
108          info.parts.left = elementRect.left >= viewportRect.left;
109          info.parts.bottom = elementRect.bottom <= viewportRect.bottom;
110          info.parts.right = elementRect.right <= viewportRect.right;
111        }
112        return info;
113      })
114
115      // Add the changed information to the inview structure.
116      .scan({}, function (lastInfo, newInfo) {
117        // Add inview direction info
118        if (options.generateDirection && newInfo.inView && lastInfo.elementRect) {
119          newInfo.direction = {
120            horizontal: newInfo.elementRect.left - lastInfo.elementRect.left,
121            vertical: newInfo.elementRect.top - lastInfo.elementRect.top
122          };
123        }
124        // Calculate changed flag
125        newInfo.changed =
126          newInfo.inView !== lastInfo.inView ||
127          !angular.equals(newInfo.parts, lastInfo.parts) ||
128          !angular.equals(newInfo.direction, lastInfo.direction);
129        return newInfo;
130      })
131
132      // Filters only informations that should be forwarded to the callback
133      .filter(function (info) {
134        // Don't forward if no relevant infomation changed
135        if (!info.changed) {
136          return false;
137        }
138        // Don't forward if not initially in-view
139        if (info.event.type === 'initial' && !info.inView) {
140          return false;
141        }
142        return true;
143      });
144
145      // Execute in-view callback
146      var inViewExpression = $parse(attrs.inView);
147      var dispose = inviewInfoSignal.subscribe(function (info) {
148        scope.$applyAsync(function () {
149          inViewExpression(scope, {
150            '$inview': info.inView,
151            '$inviewInfo': info
152          });
153        });
154      });
155
156      // Dispose of reactive chain
157      scope.$on('$destroy', dispose);
158    }
159  }
160}
161
162function inViewContainerDirective () {
163  return {
164    restrict: 'A',
165    controller: ['$element', function ($element) {
166      this.element = $element;
167      this.eventsSignal = signalFromEvent($element, 'scroll');
168      this.getViewportRect = function () {
169        return $element[0].getBoundingClientRect();
170      };
171    }]
172  }
173}
174
175// ## Utilities
176
177function getViewportRect () {
178  var result = {
179    top: 0,
180    left: 0,
181    width: window.innerWidth,
182    right: window.innerWidth,
183    height: window.innerHeight,
184    bottom: window.innerHeight
185  };
186  if (result.height) {
187    return result;
188  }
189  var mode = document.compatMode;
190  if (mode === 'CSS1Compat') {
191    result.width = result.right = document.documentElement.clientWidth;
192    result.height = result.bottom = document.documentElement.clientHeight;
193  } else {
194    result.width = result.right = document.body.clientWidth;
195    result.height = result.bottom = document.body.clientHeight;
196  }
197  return result;
198}
199
200function intersectRect (r1, r2) {
201  return !(r2.left > r1.right ||
202           r2.right < r1.left ||
203           r2.top > r1.bottom ||
204           r2.bottom < r1.top);
205}
206
207function normalizeOffset (offset) {
208  if (!angular.isArray(offset)) {
209    return [offset, offset, offset, offset];
210  }
211  if (offset.length == 2) {
212    return offset.concat(offset);
213  }
214  else if (offset.length == 3) {
215    return offset.concat([offset[1]]);
216  }
217  return offset;
218}
219
220function offsetRect (rect, offset) {
221  if (!offset) {
222    return rect;
223  }
224  var offsetObject = {
225    top: isPercent(offset[0]) ? (parseFloat(offset[0]) * rect.height) : offset[0],
226    right: isPercent(offset[1]) ? (parseFloat(offset[1]) * rect.width) : offset[1],
227    bottom: isPercent(offset[2]) ? (parseFloat(offset[2]) * rect.height) : offset[2],
228    left: isPercent(offset[3]) ? (parseFloat(offset[3]) * rect.width) : offset[3]
229  };
230  // Note: ClientRect object does not allow its properties to be written to therefore a new object has to be created.
231  return {
232    top: rect.top - offsetObject.top,
233    left: rect.left - offsetObject.left,
234    bottom: rect.bottom + offsetObject.bottom,
235    right: rect.right + offsetObject.right,
236    height: rect.height + offsetObject.top + offsetObject.bottom,
237    width: rect.width + offsetObject.left + offsetObject.right
238  };
239}
240
241function isPercent (n) {
242  return angular.isString(n) && n.indexOf('%') > 0;
243}
244
245// ## QuickSignal FRP
246// A quick and dirty implementation of Rx to have a streamlined code in the
247// directives.
248
249// ### QuickSignal
250//
251// - `didSubscribeFunc`: a function receiving a `subscriber` as described below
252//
253// Usage:
254//     var mySignal = new QuickSignal(function(subscriber) { ... })
255function QuickSignal (didSubscribeFunc) {
256  this.didSubscribeFunc = didSubscribeFunc;
257}
258
259// Subscribe to a signal and consume the steam of data.
260//
261// Returns a function that can be called to stop the signal stream of data and
262// perform cleanup.
263//
264// A `subscriber` is a function that will be called when a new value arrives.
265// a `subscriber.$dispose` property can be set to a function to be called uppon
266// disposal. When setting the `$dispose` function, the previously set function
267// should be chained.
268QuickSignal.prototype.subscribe = function (subscriber) {
269  this.didSubscribeFunc(subscriber);
270  var dispose = function () {
271    if (subscriber.$dispose) {
272      subscriber.$dispose();
273      subscriber.$dispose = null;
274    }
275  }
276  return dispose;
277}
278
279QuickSignal.prototype.map = function (f) {
280  var s = this;
281  return new QuickSignal(function (subscriber) {
282    subscriber.$dispose = s.subscribe(function (nextValue) {
283      subscriber(f(nextValue));
284    });
285  });
286};
287
288QuickSignal.prototype.filter = function (f) {
289  var s = this;
290  return new QuickSignal(function (subscriber) {
291    subscriber.$dispose = s.subscribe(function (nextValue) {
292      if (f(nextValue)) {
293        subscriber(nextValue);
294      }
295    });
296  });
297};
298
299QuickSignal.prototype.scan = function (initial, scanFunc) {
300  var s = this;
301  return new QuickSignal(function (subscriber) {
302    var last = initial;
303    subscriber.$dispose = s.subscribe(function (nextValue) {
304      last = scanFunc(last, nextValue);
305      subscriber(last);
306    });
307  });
308}
309
310QuickSignal.prototype.merge = function (signal) {
311  return signalMerge(this, signal);
312};
313
314QuickSignal.prototype.throttle = function (threshhold) {
315  var s = this, last, deferTimer;
316  return new QuickSignal(function (subscriber) {
317    var chainDisposable = s.subscribe(function () {
318      var now = +new Date,
319          args = arguments;
320      if (last && now < last + threshhold) {
321        clearTimeout(deferTimer);
322        deferTimer = setTimeout(function () {
323          last = now;
324          subscriber.apply(null, args);
325        }, threshhold);
326      } else {
327        last = now;
328        subscriber.apply(null, args);
329      }
330    });
331    subscriber.$dispose = function () {
332      clearTimeout(deferTimer);
333      if (chainDisposable) chainDisposable();
334    };
335  });
336};
337
338function signalMerge () {
339  var signals = arguments;
340  return new QuickSignal(function (subscriber) {
341    var disposables = [];
342    for (var i = signals.length - 1; i >= 0; i--) {
343      disposables.push(signals[i].subscribe(function () {
344        subscriber.apply(null, arguments);
345      }));
346    }
347    subscriber.$dispose = function () {
348      for (var i = disposables.length - 1; i >= 0; i--) {
349        if (disposables[i]) disposables[i]();
350      }
351    }
352  });
353}
354
355// Returns a signal from DOM events of a target.
356function signalFromEvent (target, event) {
357  return new QuickSignal(function (subscriber) {
358    var handler = function (e) {
359      subscriber(e);
360    };
361    var el = angular.element(target);
362    el.on(event, handler);
363    subscriber.$dispose = function () {
364      el.off(event, handler);
365    };
366  });
367}
368
369function signalSingle (value) {
370  return new QuickSignal(function (subscriber) {
371    setTimeout(function() { subscriber(value); });
372  });
373}
374
375// Module loaders exports
376if (typeof define === 'function' && define.amd) {
377  define(['angular'], angularInviewModule);
378} else if (typeof module !== 'undefined' && module && module.exports) {
379  module.exports = angularInviewModule;
380}
381
382})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.