1// Utility function 2function Util () {}; 3 4/* 5 class manipulation functions 6*/ 7Util.hasClass = function(el, className) { 8 if (el.classList) return el.classList.contains(className); 9 else return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')); 10}; 11 12Util.addClass = function(el, className) { 13 var classList = className.split(' '); 14 if (el.classList) el.classList.add(classList[0]); 15 else if (!Util.hasClass(el, classList[0])) el.className += " " + classList[0]; 16 if (classList.length > 1) Util.addClass(el, classList.slice(1).join(' ')); 17}; 18 19Util.removeClass = function(el, className) { 20 var classList = className.split(' '); 21 if (el.classList) el.classList.remove(classList[0]); 22 else if(Util.hasClass(el, classList[0])) { 23 var reg = new RegExp('(\\s|^)' + classList[0] + '(\\s|$)'); 24 el.className=el.className.replace(reg, ' '); 25 } 26 if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(' ')); 27}; 28// Sticky header reference 29var stickyHeader = document.querySelector('.sticky-header'); // Adjust selector 30var stickyHeaderHeight = stickyHeader ? stickyHeader.offsetHeight : 0; 31 32Util.toggleClass = function(el, className, bool) { 33 if(bool) Util.addClass(el, className); 34 else Util.removeClass(el, className); 35}; 36 37Util.setAttributes = function(el, attrs) { 38 for(var key in attrs) { 39 el.setAttribute(key, attrs[key]); 40 } 41}; 42 43/* 44 DOM manipulation 45*/ 46Util.getChildrenByClassName = function(el, className) { 47 var children = el.children, 48 childrenByClass = []; 49 for (var i = 0; i < el.children.length; i++) { 50 if (Util.hasClass(el.children[i], className)) childrenByClass.push(el.children[i]); 51 } 52 return childrenByClass; 53}; 54 55Util.is = function(elem, selector) { 56 if(selector.nodeType){ 57 return elem === selector; 58 } 59 60 var qa = (typeof(selector) === 'string' ? document.querySelectorAll(selector) : selector), 61 length = qa.length, 62 returnArr = []; 63 64 while(length--){ 65 if(qa[length] === elem){ 66 return true; 67 } 68 } 69 70 return false; 71}; 72 73/* 74 Animate height of an element 75*/ 76Util.setHeight = function(start, to, element, duration, cb) { 77 var change = to - start, 78 currentTime = null; 79 80 var animateHeight = function(timestamp){ 81 if (!currentTime) currentTime = timestamp; 82 var progress = timestamp - currentTime; 83 var val = parseInt((progress/duration)*change + start); 84 element.style.height = val+"px"; 85 if(progress < duration) { 86 window.requestAnimationFrame(animateHeight); 87 } else { 88 cb(); 89 } 90 }; 91 92 //set the height of the element before starting animation -> fix bug on Safari 93 element.style.height = start+"px"; 94 window.requestAnimationFrame(animateHeight); 95}; 96 97/* 98 Smooth Scroll 99*/ 100 101Util.scrollTo = function(final, duration, cb, scrollEl) { 102 var element = scrollEl || window; 103 var start = element.scrollTop || document.documentElement.scrollTop, 104 currentTime = null; 105 106 if(!scrollEl) start = window.scrollY || document.documentElement.scrollTop; 107 108 var animateScroll = function(timestamp){ 109 if (!currentTime) currentTime = timestamp; 110 var progress = timestamp - currentTime; 111 if(progress > duration) progress = duration; 112 var val = Math.easeInOutQuad(progress, start, final-start, duration); 113 element.scrollTo(0, val); 114 if(progress < duration) { 115 window.requestAnimationFrame(animateScroll); 116 } else { 117 cb && cb(); 118 } 119 }; 120 121 window.requestAnimationFrame(animateScroll); 122}; 123 124/* 125 Focus utility classes 126*/ 127 128//Move focus to an element 129Util.moveFocus = function (element) { 130 if( !element ) element = document.getElementsByTagName("body")[0]; 131 element.focus(); 132 if (document.activeElement !== element) { 133 element.setAttribute('tabindex','-1'); 134 element.focus(); 135 } 136}; 137 138/* 139 Misc 140*/ 141 142Util.getIndexInArray = function(array, el) { 143 return Array.prototype.indexOf.call(array, el); 144}; 145 146Util.cssSupports = function(property, value) { 147 if('CSS' in window) { 148 return CSS.supports(property, value); 149 } else { 150 var jsProperty = property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase();}); 151 return jsProperty in document.body.style; 152 } 153}; 154 155// merge a set of user options into plugin defaults 156// https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/ 157Util.extend = function() { 158 // Variables 159 var extended = {}; 160 var deep = false;
161 var i = 0; 162 var length = arguments.length; 163 164 // Check if a deep merge 165 if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 166 deep = arguments[0]; 167 i++; 168 } 169 170 // Merge the object into the extended object 171 var merge = function (obj) { 172 for ( var prop in obj ) { 173 if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 174 // If deep merge and property is an object, merge properties 175 if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 176 extended[prop] = extend( true, extended[prop], obj[prop] ); 177 } else { 178 extended[prop] = obj[prop]; 179 } 180 } 181 } 182 }; 183 184 // Loop through each object and conduct a merge 185 for ( ; i < length; i++ ) { 186 var obj = arguments[i]; 187 merge(obj); 188 } 189 190 return extended; 191}; 192 193// Check if Reduced Motion is enabled 194Util.osHasReducedMotion = function() { 195 if(!window.matchMedia) return false; 196 var matchMediaObj = window.matchMedia('(prefers-reduced-motion: reduce)'); 197 if(matchMediaObj) return matchMediaObj.matches; 198 return false; // return false if not supported 199}; 200 201/* 202 Polyfills 203*/ 204//Closest() method 205if (!Element.prototype.matches) { 206 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 207} 208 209if (!Element.prototype.closest) { 210 Element.prototype.closest = function(s) { 211 var el = this; 212 if (!document.documentElement.contains(el)) return null; 213 do { 214 if (el.matches(s)) return el; 215 el = el.parentElement || el.parentNode; 216 } while (el !== null && el.nodeType === 1); 217 return null; 218 }; 219} 220 221//Custom Event() constructor 222if ( typeof window.CustomEvent !== "function" ) { 223 224 function CustomEvent ( event, params ) { 225 params = params || { bubbles: false, cancelable: false, detail: undefined }; 226 var evt = document.createEvent( 'CustomEvent' ); 227 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); 228 return evt; 229 } 230 231 CustomEvent.prototype = window.Event.prototype; 232 233 window.CustomEvent = CustomEvent; 234} 235 236/* 237 Animation curves 238*/ 239Math.easeInOutQuad = function (t, b, c, d) { 240 t /= d/2; 241 if (t < 1) return c/2*t*t + b; 242 t--; 243 return -c/2 * (t*(t-2) - 1) + b; 244}; 245 246 247/* JS Utility Classes */ 248(function() { 249 // make focus ring visible only for keyboard navigation (i.e., tab key) 250 var focusTab = document.getElementsByClassName('js-tab-focus'); 251 function detectClick() { 252 if(focusTab.length > 0) { 253 resetFocusTabs(false); 254 window.addEventListener('keydown', detectTab); 255 } 256 window.removeEventListener('mousedown', detectClick); 257 }; 258 259 function detectTab(event) { 260 if(event.keyCode !== 9) return; 261 resetFocusTabs(true); 262 window.removeEventListener('keydown', detectTab); 263 window.addEventListener('mousedown', detectClick); 264 }; 265 266 function resetFocusTabs(bool) { 267 var outlineStyle = bool ? '' : 'none'; 268 for(var i = 0; i < focusTab.length; i++) { 269 focusTab[i].style.setProperty('outline', outlineStyle); 270 } 271 }; 272 window.addEventListener('mousedown', detectClick); 273}()); 274// Tutorial - https://codyhouse.co/tutorials/how-stacking-cards 275(function() { 276 var StackCards = function(element) { 277 this.element = element; 278 this.items = this.element.getElementsByClassName('js-stack-cards__item'); 279 this.scrollingFn = false; 280 this.scrolling = false; 281 initStackCardsEffect(this); 282 initStackCardsResize(this); 283 }; 284 285 function initStackCardsEffect(element) { // use Intersection Observer to trigger animation 286 setStackCards(element); // store cards CSS properties 287 var observer = new IntersectionObserver(stackCardsCallback.bind(element), { threshold: [0, 1] }); 288 observer.observe(element.element); 289 }; 290 291 function initStackCardsResize(element) { // detect resize to reset gallery 292 element.element.addEventListener('resize-stack-cards', function(){ 293 setStackCards(element); 294 animateStackCards.bind(element); 295 }); 296 }; 297 298 function stackCardsCallback(entries) { // Intersection Observer callback 299 if(entries[0].isIntersecting) { 300 if(this.scrollingFn) return; // listener for scroll event already added 301 stackCardsInitEvent(this); 302 } else { 303 if(!this.scrollingFn) return; // listener for scroll event already removed 304 window.removeEventListener('scroll', this.scrollingFn); 305 this.scrollingFn = false;
306 } 307 }; 308 309 function stackCardsInitEvent(element) { 310 element.scrollingFn = stackCardsScrolling.bind(element); 311 window.addEventListener('scroll', element.scrollingFn); 312 }; 313 314 function stackCardsScrolling() { 315 if(this.scrolling) return; 316 this.scrolling = true; 317 window.requestAnimationFrame(animateStackCards.bind(this)); 318 }; 319 320 function setStackCards(element) { 321 // store wrapper properties 322 element.marginY = getComputedStyle(element.element).getPropertyValue('--stack-cards-gap'); 323 getIntegerFromProperty(element); // convert element.marginY to integer (px value) 324 element.elementHeight = element.element.offsetHeight; 325 326 // store card properties 327 var cardStyle = getComputedStyle(element.items[0]); 328 element.cardTop = Math.floor(parseFloat(cardStyle.getPropertyValue('top'))) - stickyHeaderHeight; 329 element.cardHeight = Math.floor(parseFloat(cardStyle.getPropertyValue('height'))); 330 element.windowHeight = window.innerHeight; 331 // store window property 332 element.windowHeight = window.innerHeight; 333 334 // reset margin + translate values 335 if(isNaN(element.marginY)) { 336 element.element.style.paddingBottom = '50px'; 337 } else { 338 element.element.style.paddingBottom = (element.marginY*(element.items.length - 1))+'px'; 339 } 340 341 for(var i = 0; i < element.items.length; i++) { 342 if(isNaN(element.marginY)) { 343 element.items[i].style.transform = 'none;'; 344 } else { 345 element.items[i].style.transform = 'translateY('+element.marginY*i+'px)'; 346 } 347 } 348 }; 349 350 function getIntegerFromProperty(element) { 351 var node = document.createElement('div'); 352 node.setAttribute('style', 'opacity:0; visbility: hidden;position: absolute; height:'+element.marginY); 353 element.element.appendChild(node); 354 element.marginY = parseInt(getComputedStyle(node).getPropertyValue('height')); 355 element.element.removeChild(node); 356 }; 357 358 function animateStackCards() { 359 if(isNaN(this.marginY)) { // --stack-cards-gap not defined - do not trigger the effect 360 this.scrolling = false; 361 return; 362 } 363 364 var top = this.element.getBoundingClientRect().top; 365 366 if(this.cardTop - top + this.element.windowHeight - this.elementHeight - this.cardHeight + this.marginY + this.marginY*this.items.length + stickyHeaderHeight > 0) { 367 this.scrolling = false; 368 return; 369 } 370 371 for(var i = 0; i < this.items.length; i++) { // use only scale 372 var scrolling = this.cardTop - top - i*(this.cardHeight+this.marginY); 373 if(scrolling > 0) { 374 var scaling = i == this.items.length - 1 ? 1 : (this.cardHeight - scrolling*0.05)/this.cardHeight; 375 this.items[i].style.transform = 'translateY('+this.marginY*i+'px) scale('+scaling+')'; 376 } else { 377 this.items[i].style.transform = 'translateY('+this.marginY*i+'px)'; 378 } 379 } 380 381 this.scrolling = false; 382 }; 383 384 // initialize StackCards object 385 var stackCards = document.getElementsByClassName('js-stack-cards'), 386 intersectionObserverSupported = ('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype), 387 reducedMotion = Util.osHasReducedMotion(); 388 389 if(stackCards.length > 0 && intersectionObserverSupported && !reducedMotion) { 390 var stackCardsArray = []; 391 for(var i = 0; i < stackCards.length; i++) { 392 (function(i){ 393 stackCardsArray.push(new StackCards(stackCards[i])); 394 })(i); 395 } 396 397 var resizingId = false, 398 customEvent = new CustomEvent('resize-stack-cards'); 399 400 window.addEventListener('resize', function() { 401 clearTimeout(resizingId); 402 resizingId = setTimeout(doneResizing, 500); 403 }); 404 405 function doneResizing() { 406 for( var i = 0; i < stackCardsArray.length; i++) { 407 (function(i){stackCardsArray[i].element.dispatchEvent(customEvent)})(i); 408 }; 409 }; 410 } 411}()); 412
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.