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.