1/** 2 * Scroll reveal 3 * 4 * When the scroll element is in viewport the css values of gridTemplateColumns are updated according to 5 * vertical scroll values, to reveal the element in the middle. 6 * 7 * The header, 'opacityElement', also fades in and out with scroll. 8 */ 9 10const revealElement = $('.scroll-reveal__hero'); 11const opacityElement = $('.opacity-0-to-100'); 12 13const initialColumns = ['1fr', '0fr', '1fr']; 14const minGridSize = 0; 15let maxGridSize = 1.8; 16let easingRate = 1.8; 17 18const largeScreenSize = 1224; 19const mediumScreenSize = 1024; 20const smallScreenSize = 768; 21 22let currentScroll = window.scrollY; 23let currentGridSize = 0; 24let currentOpacity = 0; 25let animationFrameID = null; 26 27/** 28 * Fully open reveal element 29 */ 30function openGrid() { 31 revealElement.css('grid-template-columns', '1fr ' + maxGridSize + 'r 1fr'); 32 opacityElement.css('opacity', '1'); 33} 34 35/** 36 * Open/close reveal element by amount based on scroll 37 * 38 * @param {number} scrollDiff 39 */ 40function updateGrid(scrollDiff) { 41 42 let transitionRate = 0.015; 43 let scrollUpOffset = -100; 44 45 const windowWidth = $(window).width(); 46 if (windowWidth < smallScreenSize) { 47 maxGridSize = 35; 48 easingRate = 30; 49 transitionRate = 0.5; 50 scrollUpOffset = 20; 51 } else if (windowWidth < mediumScreenSize) { 52 maxGridSize = 4.5; 53 easingRate = 3; 54 transitionRate = 0.05; 55 } else if (windowWidth < largeScreenSize) { 56 maxGridSize = 3; 57 easingRate = 3; 58 transitionRate = 0.03; 59 } else { 60 maxGridSize = 1.8; 61 } 62 63 const scrollDirection = scrollDiff > 0 ? 'down' : 'up'; 64 let columns = initialColumns.slice(); // Clone initialColumns array 65 66 // Open when scrolling down. 67 if (scrollDirection === 'down') { 68 // Speed of transition, with easing 69 const transitionFactor = transitionRate - ((currentGridSize / easingRate) * 0.01); 70 const transitionAmount = Math.abs(scrollDiff) * transitionFactor; 71 72 // Open, while scrolling down. 73 const newGridSize = currentGridSize + transitionAmount 74 75 if (newGridSize <= maxGridSize) { 76 columns[1] = newGridSize + 'fr'; 77 currentGridSize = newGridSize; 78 } else { 79 columns[1] = maxGridSize + "fr"; 80 } 81 82 if (currentOpacity + transitionAmount <= 1) { 83 currentOpacity = currentOpacity + transitionAmount; 84 } 85 86 // Update css 87 revealElement.css('grid-template-columns', columns.join(' ')); 88 opacityElement.css('opacity', currentOpacity); 89 90 91 } else { 92 // Close when scrolling up. 93 // Start close when element is partly out of viewport 94 if (!isElementInViewport(revealElement, scrollUpOffset)) { 95 96 // Speed of transition, with easing 97 const transitionFactor = transitionRate - ((currentGridSize) / easingRate) * 0.01; 98 99 const transitionAmount = Math.abs(scrollDiff) * transitionFactor; 100 const newGridSize = currentGridSize - transitionAmount; 101 if (newGridSize > minGridSize) { 102 columns[1] = newGridSize + 'fr'; 103 currentGridSize = newGridSize; 104 } else { 105 columns[1] = '0fr'; 106 } 107 108 // Only begin fade out when reveal is almost 3/4 closed 109 if (newGridSize <= 0.5 && (currentOpacity - transitionAmount > 0)) { 110 currentOpacity = currentOpacity - transitionAmount; 111 } 112 113 // Update css 114 revealElement.css('grid-template-columns', columns.join(' ')); 115 opacityElement.css('opacity', currentOpacity); 116 } 117 } 118} 119 120/** 121 * Whether element is fully visible, according to offset 122 * 123 * @param {Element} element 124 * @param {number} offset 125 * @returns {boolean|boolean} 126 */ 127function isElementInViewport(element, offset = 0) { 128 let rect = element[0].getBoundingClientRect(); 129 return ( 130 rect.top >= 0 && 131 rect.bottom <= (window.innerHeight + offset || document.documentElement.clientHeight + offset) 132 ); 133} 134 135/** 136 * Handle scroll event using requestAnimationFrame for smoother animations 137 */ 138function handleScroll() { 139 // If there's already a scheduled animation frame, exit 140 if (animationFrameID) return; 141 142 // Schedule animation frame 143 animationFrameID = requestAnimationFrame(function () { 144 const newScroll = window.scrollY; 145 const scrollDiff = newScroll - currentScroll; 146 if (isElementInViewport(revealElement, 180)) { 147 updateGrid(scrollDiff); 148 currentScroll = newScroll; 149 } 150 // Reset animationFrameID to null for the next scroll event 151 animationFrameID = null; 152 }); 153 154} 155 156Drupal.behaviors.scrollReveal = { 157 attach: function (context, settings) { 158 if (once('scrollReveal', 'html').length) { 159 // Open fully if visible on page load 160 if (isElementInViewport(revealElement, -100)) { 161 openGrid(); 162 } 163 window.addEventListener('scroll', handleScroll); 164 } 165 } 166}; 167 168 169
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.