🍚 Sticky situation
Let's start with our base markup & styles
Markup
Let's use a figure tag for semantics as all of this is essentially a visual section that could be moved to another part of the document without affecting the flow. The text below is suited as a figcaption also. This markup uses many elements with the whole sticky concept in mind. It'll all make sense with our styles 😌
<figure
class="c-fancy-gallery"
>
<div class="c-fancy-gallery_sticky-area">{# This will define the "stickiness" boundaries #}
<div class="c-fancy-gallery_sticky">{# This is the element that will stick #}
{# Visuals grid goes here #}
</div>
</div>
<div class="c-fancy-gallery_spacer">{# This allows to occupy vertical space that'll define the scroll length of our animation #}</div>
<figcaption class="c-fancy-gallery_caption">
{# Split the sentence by words #}
{% for word in gallery.caption | split(' ') %}
<span class="c-fancy-gallery_word">
<span class="c-fancy-gallery_word-inner">{{word}}</span>
</span>
{% endfor %}
</figcaption>
</figure>
Styles
We start things off with our relative main container with a mask system.
.c-fancy-gallery {
position: relative;
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); // Can be removed for debug purposes
// Fix for safari iOS not registering the clip-path without this (probably bc of combination w/ sticky elements)
background: url('data:image/gif;base64,R0lGODlhAQABAAAAACw=');
}
We use a clip-path in order to mask content overflowing instead of a overflow: hidden otherwise we couldn't use position: sticky on a child element anymore
Then we define the sticky area to occupy the whole container plus 100vh above and 100vh below. That'll allow us to make the sticky block always fixed while scrolling along.
.c-fancy-gallery_sticky-area {
position: absolute;
top: -100vh;
left: 0;
height: calc(100% + 200vh);
width: 100%;
}
Now for our actual sticky element. This one needs to be above and fullscreen so we use our vw() and vh() helpers.
.c-fancy-gallery_sticky {
position: sticky;
top: 0;left: 0;
width: vw(100);
height: vh(100);
z-index: 10;
overflow: hidden;
}
The "spacer" is an empty block allowing us to control the scroll-length before the caption part. This snippet also handles the responsive sizing.
.c-fancy-gallery_spacer {
// Desktop-ish
@media (min-width: $from-small) {
height: 200vh;
}
// Mobile-ish
@media (max-width: $to-small) {
height: 100vh;
}
}
Finally, our caption section. It's pretty simple, we are also using a clip-path on each word as a mask for our upcoming animation.
.c-fancy-gallery_caption {
position: relative;
text-align: center;
font-size: var(--font-size-huge);
line-height: 0.85;
@include -padding-huge-bottom;
}
.c-fancy-gallery_word {
display: block;
clip-path: polygon(0% 0%, 100% 0%, 100% 115%, 0% 115%); // Custom mask area, gives more control than overflow
}
.c-fancy-gallery_word-inner {
display: block; // Need those as block to support transform properties added by GSAP later
}
Now that everything's set up, we can make sure everything works as intended by using background or outline properties.