This page is built with the above effect, take a quick scroll to see the effect and if you're interested then read the content on this site or go to the official documentation here.
<section class="position-relative zindex-2 bg shadow-sm">
<!-- section with a normal flow -->
</section>
<section class="revealing-hero bg zindex-1 js-revealing-hero">
<!-- Revealing Hero content -->
</section>
The top(first) section gives way to the one under it as the user scrolls. Also note the usage of the z-index attribute. Containers must have descending z-indexes as in the above example. Lastly notice that the top section does not have the classes "revealing-hero" or "js-revealing-hero". Only sections that are underneath, those to be revealed, must have them.
Scroll down to see required custom css and custom JavaScript
vvvvvv
This is in addition to the CSS of the Dependencies
.revealing-hero {
position: sticky;
min-height: 100vh;
--reavealing-hero-overlay-opacity: 1;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
pointer-events: none;
background-color: var(--color-black);
opacity: var(--reavealing-hero-overlay-opacity);
}
}
var RevealingHero = function(element) {
this.element = element;
this.scrollingFn = false;
this.scrolling = false;
initRevealingHero(this);
};
function initRevealingHero(element) {
createTopElement(element); // create a new node - to be inserted before the sticky element
initRevealingHeroEffect(element); // change the ::after element opacity on scroll
};
function initRevealingHeroEffect(element) {
var observer = new IntersectionObserver(revealingHeroCallback.bind(element));
observer.observe(element.prevElement);
};
function revealingHeroCallback(entries) {
if(entries[0].isIntersecting) {
if(this.scrollingFn) return; // listener for scroll event already added
this.scrollingFn = revealingHeroScrolling.bind(this);
window.addEventListener('scroll', this.scrollingFn);
} else {
if(!this.scrollingFn) return; // listener for scroll event already removed
window.removeEventListener('scroll', this.scrollingFn);
this.scrollingFn = false;
}
};
function revealingHeroScrolling() {
if(this.scrolling) return;
this.scrolling = true;
window.requestAnimationFrame(animateRevealingHero.bind(this));
};
function animateRevealingHero() {
// change opacity value
};
There are multiple ways of installing CodyFrame but the following covers how to get needed css/js from the CodyFrame Github repository
Written with access to CodyFrame v4 and v3
1. Navigate to CodyFrame's documentation then select v4 and click on their Github link
2. Navigate to main/css and download all of the css files (individually or as a group) then add them to your project files and link them above the required CSS
3. Now go back to the documentation and select v3 then click on the Github repository link
4. Now navigate to main/assets/js and download the util.js file. Place file in project and link the file above required JavaScript
** You can use other CodyFrame components that are compatible with above downloaded resources
This plugin is not easy to use but allows for a very cool effect
I also found that you have to add padding to the top and bottom of your pages content as they will get cut off or be hard to read as the user scrolls. You may also want to implement a snap scrolling effect to make the pages feel more responsive.
If you plan on on having many heroes reveal at a time then you will need to write css for the z-indexes as bootstrap only has 0-3 and CodyFrame only has 1-4 as provided class options.
As for documentation goes the css provided works but the given js requires you to write the rest of the animateRevealingHero JavaScript function using the variables given in the required code. This makes it difficult to use if you are not aware of the plugins methods bellow is the JavaScript code from the demo which is complete and worked for me.
(function() {
var RevealingHero = function(element) {
this.element = element;
this.scrollingFn = false;
this.scrolling = false;
this.resetOpacity = false;
initRevealingHero(this);
};
function initRevealingHero(element) {
// set position of sticky element
setBottom(element);
// create a new node - to be inserted before the sticky element
createPrevElement(element);
// on resize -> reset element bottom position
element.element.addEventListener('update-reveal-hero', function(){
setBottom(element);
setPrevElementTop(element);
});
animateRevealingHero.bind(element)(); // set initial status
// change opacity of layer
var observer = new IntersectionObserver(revealingHeroCallback.bind(element));
observer.observe(element.prevElement);
};
function createPrevElement(element) {
var newElement = document.createElement("div");
newElement.setAttribute('aria-hidden', 'true');
element.element.parentElement.insertBefore(newElement, element.element);
element.prevElement = element.element.previousElementSibling;
element.prevElement.style.opacity = '0';
setPrevElementTop(element);
};
function setPrevElementTop(element) {
element.prevElementTop = element.prevElement.getBoundingClientRect().top + window.scrollY;
};
function revealingHeroCallback(entries, observer) {
if(entries[0].isIntersecting) {
if(this.scrollingFn) return; // listener for scroll event already added
revealingHeroInitEvent(this);
} else {
if(!this.scrollingFn) return; // listener for scroll event already removed
window.removeEventListener('scroll', this.scrollingFn);
updateOpacityValue(this, 0);
this.scrollingFn = false;
}
};
function revealingHeroInitEvent(element) {
element.scrollingFn = revealingHeroScrolling.bind(element);
window.addEventListener('scroll', element.scrollingFn);
};
function revealingHeroScrolling() {
if(this.scrolling) return;
this.scrolling = true;
window.requestAnimationFrame(animateRevealingHero.bind(this));
};
function animateRevealingHero() {
if(this.prevElementTop - window.scrollY < window.innerHeight) {
var opacity = (1 - (window.innerHeight + window.scrollY - this.prevElementTop)/window.innerHeight).toFixed(2);
if(opacity > 0 ) {
this.resetOpacity = false;
updateOpacityValue(this, opacity);
} else if(!this.resetOpacity) {
this.resetOpacity = true;
updateOpacityValue(this, 0);
}
}
this.scrolling = false;
};
function updateOpacityValue(element, value) {
element.element.style.setProperty('--reavealing-hero-overlay-opacity', value);
};
function setBottom(element) {
var translateValue = window.innerHeight - element.element.offsetHeight;
if(translateValue > 0) translateValue = 0;
element.element.style.bottom = ''+translateValue+'px';
};
//initialize the Revealing Hero objects
var revealingHero = document.getElementsByClassName('js-revealing-hero');
var stickySupported = Util.cssSupports('position', 'sticky') || Util.cssSupports('position', '-webkit-sticky');
if( revealingHero.length > 0 && stickySupported) {
var revealingHeroArray = [];
for( var i = 0; i < revealingHero.length; i++) {
(function(i){revealingHeroArray.push(new RevealingHero(revealingHero[i]));})(i);
}
var resizingId = false,
customEvent = new CustomEvent('update-reveal-hero');
window.addEventListener('resize', function() {
clearTimeout(resizingId);
resizingId = setTimeout(doneResizing, 100);
});
// wait for font to be loaded
document.fonts.onloadingdone = function (fontFaceSetEvent) {
doneResizing();
};
function doneResizing() {
for( var i = 0; i < revealingHeroArray.length; i++) {
(function(i){revealingHeroArray[i].element.dispatchEvent(customEvent)})(i);
};
};
}
}());