/* global Fluid, CONFIG */ window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; Fluid.utils = { listenScroll: function(callback) { var dbc = new Debouncer(callback); window.addEventListener('scroll', dbc, false); dbc.handleEvent(); return dbc; }, unlistenScroll: function(callback) { window.removeEventListener('scroll', callback); }, listenDOMLoaded(callback) { if (document.readyState !== 'loading') { callback(); } else { document.addEventListener('DOMContentLoaded', function () { callback(); }); } }, scrollToElement: function(target, offset) { var of = jQuery(target).offset(); if (of) { jQuery('html,body').animate({ scrollTop: of.top + (offset || 0), easing : 'swing' }); } }, elementVisible: function(element, offsetFactor) { offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0; var rect = element.getBoundingClientRect(); const viewportHeight = window.innerHeight || document.documentElement.clientHeight; return ( (rect.top >= 0 && rect.top <= viewportHeight * (1 + offsetFactor) + rect.height / 2) || (rect.bottom >= 0 && rect.bottom <= viewportHeight * (1 + offsetFactor) + rect.height / 2) ); }, waitElementVisible: function(selectorOrElement, callback, offsetFactor) { var runningOnBrowser = typeof window !== 'undefined'; var isBot = (runningOnBrowser && !('onscroll' in window)) || (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent)); if (!runningOnBrowser || isBot) { return; } offsetFactor = offsetFactor && offsetFactor >= 0 ? offsetFactor : 0; function waitInViewport(element) { Fluid.utils.listenDOMLoaded(function() { if (Fluid.utils.elementVisible(element, offsetFactor)) { callback(); return; } if ('IntersectionObserver' in window) { var io = new IntersectionObserver(function(entries, ob) { if (entries[0].isIntersecting) { callback(); ob.disconnect(); } }, { threshold : [0], rootMargin: (window.innerHeight || document.documentElement.clientHeight) * offsetFactor + 'px' }); io.observe(element); } else { var wrapper = Fluid.utils.listenScroll(function() { if (Fluid.utils.elementVisible(element, offsetFactor)) { Fluid.utils.unlistenScroll(wrapper); callback(); } }); } }); } if (typeof selectorOrElement === 'string') { this.waitElementLoaded(selectorOrElement, function(element) { waitInViewport(element); }); } else { waitInViewport(selectorOrElement); } }, waitElementLoaded: function(selector, callback) { var runningOnBrowser = typeof window !== 'undefined'; var isBot = (runningOnBrowser && !('onscroll' in window)) || (typeof navigator !== 'undefined' && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent)); if (!runningOnBrowser || isBot) { return; } if ('MutationObserver' in window) { var mo = new MutationObserver(function(records, ob) { var ele = document.querySelector(selector); if (ele) { callback(ele); ob.disconnect(); } }); mo.observe(document, { childList: true, subtree: true }); } else { Fluid.utils.listenDOMLoaded(function() { var waitLoop = function() { var ele = document.querySelector(selector); if (ele) { callback(ele); } else { setTimeout(waitLoop, 100); } }; waitLoop(); }); } }, createScript: function(url, onload) { var s = document.createElement('script'); s.setAttribute('src', url); s.setAttribute('type', 'text/javascript'); s.setAttribute('charset', 'UTF-8'); s.async = false; if (typeof onload === 'function') { if (window.attachEvent) { s.onreadystatechange = function() { var e = s.readyState; if (e === 'loaded' || e === 'complete') { s.onreadystatechange = null; onload(); } }; } else { s.onload = onload; } } var ss = document.getElementsByTagName('script'); var e = ss.length > 0 ? ss[ss.length - 1] : document.head || document.documentElement; e.parentNode.insertBefore(s, e.nextSibling); }, createCssLink: function(url) { var l = document.createElement('link'); l.setAttribute('rel', 'stylesheet'); l.setAttribute('type', 'text/css'); l.setAttribute('href', url); var e = document.getElementsByTagName('link')[0] || document.getElementsByTagName('head')[0] || document.head || document.documentElement; e.parentNode.insertBefore(l, e); }, loadComments: function(selector, loadFunc) { var ele = document.querySelector('#comments[lazyload]'); if (ele) { var callback = function() { loadFunc(); ele.removeAttribute('lazyload'); }; Fluid.utils.waitElementVisible(selector, callback, CONFIG.lazyload.offset_factor); } else { loadFunc(); } }, getBackgroundLightness(selectorOrElement) { var ele = selectorOrElement; if (typeof selectorOrElement === 'string') { ele = document.querySelector(selectorOrElement); } var view = ele.ownerDocument.defaultView; if (!view) { view = window; } var rgbArr = view.getComputedStyle(ele).backgroundColor.replace(/rgba*\(/, '').replace(')', '').split(/,\s*/); if (rgbArr.length < 3) { return 0; } var colorCast = (0.213 * rgbArr[0]) + (0.715 * rgbArr[1]) + (0.072 * rgbArr[2]); return colorCast === 0 || colorCast > 255 / 2 ? 1 : -1; }, retry(handler, interval, times) { if (times <= 0) { return; } var next = function() { if (--times >= 0 && !handler()) { setTimeout(next, interval); } }; setTimeout(next, interval); } }; /** * Handles debouncing of events via requestAnimationFrame * @see http://www.html5rocks.com/en/tutorials/speed/animations/ * @param {Function} callback The callback to handle whichever event */ function Debouncer(callback) { this.callback = callback; this.ticking = false; } Debouncer.prototype = { constructor: Debouncer, /** * dispatches the event to the supplied callback * @private */ update: function() { this.callback && this.callback(); this.ticking = false; }, /** * ensures events don't get stacked * @private */ requestTick: function() { if (!this.ticking) { requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this))); this.ticking = true; } }, /** * Attach this as the event listeners */ handleEvent: function() { this.requestTick(); } };