dlueth/qoopido.nucleus

View on GitHub
src/dom/element/appear.js

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * @use /demand/weakmap
 *
 * @require ../element
 * @require ../../function/merge
 * @require ../../function/debounce
 */

(function(global, document, setInterval) {
    'use strict';

    if(document.compatMode !== 'CSS1Compat') {
        throw new Error('Browser needs to be in standards mode');
    }

    function definition(Weakmap, DomElement, functionMerge, functionDebounce) {
        var documentElement  = document.documentElement,
            window           = new DomElement(global),
            viewport         = {},
            storage          = new Weakmap(),
            elements         = [],
            EVENTS_RESIZE    = 'resize orientationchange',
            EVENT_APPEAR     = 'appear',
            EVENT_DISAPPEAR  = 'disappear';

        window.on(EVENTS_RESIZE, functionDebounce(updateViewport));
        updateViewport();

        setInterval(function() {
            var i = 0, element, properties, settings, state;

            for(; element = elements[i]; i++) {
                properties = storage.get(element);
                settings   = properties.settings;

                if(!settings.visibility || element.isVisible()) {
                    state = check.call(element);

                    if(state !== properties.state) {
                        switch(state) {
                            case 0:
                                element.emit(EVENT_DISAPPEAR, { priority: 1 });

                                break;
                            case 1:
                                element.emit(properties.state <= 0 ? EVENT_APPEAR : EVENT_DISAPPEAR, { priority: 2 });

                                break;
                            case 2:
                                element.emit(EVENT_APPEAR, { priority: 1 });

                                break;
                        }

                        properties.state = state;
                    }

                    if(!settings.recur && state === 2) {
                        elements.splice(i, 1);
                        i--;
                    }
                }
            }
        }, 1000 / 30);

        function updateViewport() {
            viewport.left   = 0;
            viewport.top    = 0;
            viewport.right  = global.innerWidth || documentElement.clientWidth;
            viewport.bottom = global.innerHeight || documentElement.clientHeight;
        }

        function updateBoundaries() {
            var properties = storage.get(this),
                settings   = properties.settings,
                boundaries = properties.boundaries,
                treshold   = settings.threshold,
                x          = (treshold !== undefined) ? treshold : documentElement.clientWidth * settings.auto,
                y          = (treshold !== undefined) ? treshold : documentElement.clientHeight * settings.auto;

            boundaries.left   = viewport.left - x;
            boundaries.top    = viewport.top - y;
            boundaries.right  = viewport.right + x;
            boundaries.bottom = viewport.bottom + y;
        }

        function isWithinBoundaries(element, boundaries) {
            var rect = element.getBoundingClientRect();

            if(rect.right <= boundaries.left || rect.bottom <= boundaries.top || rect.left >= boundaries.right || rect.top >= boundaries.bottom) {
                return false;
            }

            return true;
        }

        function check() {
            var self       = this,
                node       = self.node,
                properties = storage.get(self);

            if(isWithinBoundaries(node, viewport)) {
                return 2;
            }

            if(isWithinBoundaries(node, properties.boundaries)) {
                return 1;
            }

            return 0;
        }

        function DomElementAppear(element, settings) {
            var self = DomElement.call(this, element);

            settings = functionMerge({}, DomElementAppear.settings, settings);

            if(settings.threshold === 'auto') {
                delete settings.threshold;
            }

            storage.set(self, {
                settings:   settings,
                boundaries: {},
                state:      -1
            });

            window.on(EVENTS_RESIZE, functionDebounce(updateBoundaries.bind(self)));

            updateBoundaries.call(self);
            elements.push(self);

            return self;
        }

        DomElementAppear.settings = {
            threshold:  'auto',
            recur:      true,
            auto:       1,
            visibility: true
        };

        return DomElementAppear.extends(DomElement);
    }

    provide([ '/demand/weakmap', '../element', '../../function/merge', '../../function/debounce' ], definition);
}(this, document, setInterval));