123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- /**
- * Attaches the scroll event handler
- *
- * @return {void}
- */
- function attach() {
- var container = this.options.container;
-
- if (container instanceof HTMLElement) {
- var style = window.getComputedStyle(container);
-
- if (style.position === 'static') {
- container.style.position = 'relative';
- }
- }
-
- container.addEventListener('scroll', this._scroll);
- window.addEventListener('resize', this._scroll);
- this._scroll();
- this.attached = true;
- }
-
- /**
- * Checks an element's position in respect to the viewport
- * and determines wether it's inside the viewport.
- *
- * @param {node} element The DOM node you want to check
- * @return {boolean} A boolean value that indicates wether is on or off the viewport.
- */
- function inViewport(el) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { tolerance: 0 };
-
- if (!el) {
- throw new Error('You should specify the element you want to test');
- }
-
- if (typeof el === 'string') {
- el = document.querySelector(el);
- }
-
- var elRect = el.getBoundingClientRect();
-
- return (
- // Check bottom boundary
- elRect.bottom - options.tolerance > 0 &&
-
- // Check right boundary
- elRect.right - options.tolerance > 0 &&
-
- // Check left boundary
- elRect.left + options.tolerance < (window.innerWidth || document.documentElement.clientWidth) &&
-
- // Check top boundary
- elRect.top + options.tolerance < (window.innerHeight || document.documentElement.clientHeight)
- );
- }
-
- /**
- * Checks an element's position in respect to a HTMLElement
- * and determines wether it's within its boundaries.
- *
- * @param {node} element The DOM node you want to check
- * @return {boolean} A boolean value that indicates wether is on or off the container.
- */
- function inContainer(el) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { tolerance: 0, container: '' };
-
- if (!el) {
- throw new Error('You should specify the element you want to test');
- }
-
- if (typeof el === 'string') {
- el = document.querySelector(el);
- }
- if (typeof options === 'string') {
- options = {
- tolerance: 0,
- container: document.querySelector(options)
- };
- }
- if (typeof options.container === 'string') {
- options.container = document.querySelector(options.container);
- }
- if (options instanceof HTMLElement) {
- options = {
- tolerance: 0,
- container: options
- };
- }
- if (!options.container) {
- throw new Error('You should specify a container element');
- }
-
- var containerRect = options.container.getBoundingClientRect();
-
- return (
- // // Check bottom boundary
- el.offsetTop + el.clientHeight - options.tolerance > options.container.scrollTop &&
-
- // Check right boundary
- el.offsetLeft + el.clientWidth - options.tolerance > options.container.scrollLeft &&
-
- // Check left boundary
- el.offsetLeft + options.tolerance < containerRect.width + options.container.scrollLeft &&
-
- // // Check top boundary
- el.offsetTop + options.tolerance < containerRect.height + options.container.scrollTop
- );
- }
-
- // TODO: Refactor this so it can be easily tested
- /* istanbul ignore next */
- function eventHandler() {
- var trackedElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { tolerance: 0 };
-
- var selectors = Object.keys(trackedElements);
- var testVisibility = void 0;
-
- if (!selectors.length) return;
-
- if (options.container === window) {
- testVisibility = inViewport;
- } else {
- testVisibility = inContainer;
- }
-
- selectors.forEach(function (selector) {
- trackedElements[selector].nodes.forEach(function (item) {
- if (testVisibility(item.node, options)) {
- item.wasVisible = item.isVisible;
- item.isVisible = true;
- } else {
- item.wasVisible = item.isVisible;
- item.isVisible = false;
- }
- if (item.isVisible === true && item.wasVisible === false) {
- if (!trackedElements[selector].enter) return;
-
- Object.keys(trackedElements[selector].enter).forEach(function (callback) {
- if (typeof trackedElements[selector].enter[callback] === 'function') {
- trackedElements[selector].enter[callback](item.node, 'enter');
- }
- });
- }
- if (item.isVisible === false && item.wasVisible === true) {
- if (!trackedElements[selector].leave) return;
-
- Object.keys(trackedElements[selector].leave).forEach(function (callback) {
- if (typeof trackedElements[selector].leave[callback] === 'function') {
- trackedElements[selector].leave[callback](item.node, 'leave');
- }
- });
- }
- });
- });
- }
-
- /**
- * Debounces the scroll event to avoid performance issues
- *
- * @return {void}
- */
- function debouncedScroll() {
- var _this = this;
-
- var timeout = void 0;
-
- return function () {
- clearTimeout(timeout);
-
- timeout = setTimeout(function () {
- eventHandler(_this.trackedElements, _this.options);
- }, _this.options.debounce);
- };
- }
-
- /**
- * Removes the scroll event handler
- *
- * @return {void}
- */
- function destroy() {
- this.options.container.removeEventListener('scroll', this._scroll);
- window.removeEventListener('resize', this._scroll);
- this.attached = false;
- }
-
- /**
- * Stops tracking elements matching a CSS selector. If a selector has no
- * callbacks it gets removed.
- *
- * @param {string} event The event you want to stop tracking (enter or leave)
- * @param {string} selector The CSS selector you want to stop tracking
- * @return {void}
- */
- function off(event, selector, handler) {
- var enterCallbacks = Object.keys(this.trackedElements[selector].enter || {});
- var leaveCallbacks = Object.keys(this.trackedElements[selector].leave || {});
-
- if ({}.hasOwnProperty.call(this.trackedElements, selector)) {
- if (handler) {
- if (this.trackedElements[selector][event]) {
- var callbackName = typeof handler === 'function' ? handler.name : handler;
- delete this.trackedElements[selector][event][callbackName];
- }
- } else {
- delete this.trackedElements[selector][event];
- }
- }
-
- if (!enterCallbacks.length && !leaveCallbacks.length) {
- delete this.trackedElements[selector];
- }
- }
-
- /**
- * Starts tracking elements matching a CSS selector
- *
- * @param {string} event The event you want to track (enter or leave)
- * @param {string} selector The element you want to track
- * @param {function} callback The callback function to handle the event
- * @return {void}
- */
- function on(event, selector, callback) {
- var allowed = ['enter', 'leave'];
-
- if (!event) throw new Error('No event given. Choose either enter or leave');
- if (!selector) throw new Error('No selector to track');
- if (allowed.indexOf(event) < 0) throw new Error(event + ' event is not supported');
-
- if (!{}.hasOwnProperty.call(this.trackedElements, selector)) {
- this.trackedElements[selector] = {};
- }
-
- this.trackedElements[selector].nodes = [];
-
- for (var i = 0, elems = document.querySelectorAll(selector); i < elems.length; i++) {
- var item = {
- isVisible: false,
- wasVisible: false,
- node: elems[i]
- };
-
- this.trackedElements[selector].nodes.push(item);
- }
-
- if (typeof callback === 'function') {
- if (!this.trackedElements[selector][event]) {
- this.trackedElements[selector][event] = {};
- }
-
- this.trackedElements[selector][event][callback.name || 'anonymous'] = callback;
- }
- }
-
- /**
- * Observes DOM mutations and runs a callback function when
- * detecting one.
- *
- * @param {node} obj The DOM node you want to observe
- * @param {function} callback The callback function you want to call
- * @return {void}
- */
- function observeDOM(obj, callback) {
- var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
-
- /* istanbul ignore else */
- if (MutationObserver) {
- var obs = new MutationObserver(callback);
-
- obs.observe(obj, {
- childList: true,
- subtree: true
- });
- } else {
- obj.addEventListener('DOMNodeInserted', callback, false);
- obj.addEventListener('DOMNodeRemoved', callback, false);
- }
- }
-
- /**
- * Detects wether DOM nodes enter or leave the viewport
- *
- * @constructor
- * @param {object} options The configuration object
- */
- function OnScreen() {
- var _this = this;
-
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { tolerance: 0, debounce: 100, container: window };
-
- this.options = {};
- this.trackedElements = {};
-
- Object.defineProperties(this.options, {
- container: {
- configurable: false,
- enumerable: false,
- get: function get() {
- var container = void 0;
-
- if (typeof options.container === 'string') {
- container = document.querySelector(options.container);
- } else if (options.container instanceof HTMLElement) {
- container = options.container;
- }
-
- return container || window;
- },
- set: function set(value) {
- options.container = value;
- }
- },
- debounce: {
- get: function get() {
- return parseInt(options.debounce, 10) || 100;
- },
- set: function set(value) {
- options.debounce = value;
- }
- },
- tolerance: {
- get: function get() {
- return parseInt(options.tolerance, 10) || 0;
- },
- set: function set(value) {
- options.tolerance = value;
- }
- }
- });
-
- Object.defineProperty(this, '_scroll', {
- enumerable: false,
- configurable: false,
- writable: false,
- value: this._debouncedScroll.call(this)
- });
-
- observeDOM(document.querySelector('body'), function () {
- Object.keys(_this.trackedElements).forEach(function (element) {
- _this.on('enter', element);
- _this.on('leave', element);
- });
- });
-
- this.attach();
- }
-
- Object.defineProperties(OnScreen.prototype, {
- _debouncedScroll: {
- configurable: false,
- writable: false,
- enumerable: false,
- value: debouncedScroll
- },
- attach: {
- configurable: false,
- writable: false,
- enumerable: false,
- value: attach
- },
- destroy: {
- configurable: false,
- writable: false,
- enumerable: false,
- value: destroy
- },
- off: {
- configurable: false,
- writable: false,
- enumerable: false,
- value: off
- },
- on: {
- configurable: false,
- writable: false,
- enumerable: false,
- value: on
- }
- });
-
- OnScreen.check = inViewport;
-
- export default OnScreen;
- //# sourceMappingURL=on-screen.es6.js.map
|