Indicating Scroll Position on a Page With CSS

Scrolling is something we all know and do on the web to the extent that it’s an expectation or perhaps even a habit, like brushing our teeth. That’s probably why we don’t put too much thought into designing the scrolling experience — it’s a well-known basic function. In fact, the popular “there is no fold” saying comes from the idea that people know how to scroll and there is no arbitrary line that people don’t go under.

Scroll-based features tend to involve some bespoke concoction of CSS and JavaScript. That’s because there simply aren’t that many native features available to do it. But what if we could accomplish something that only uses CSS? 

Take this ingenious horizontal scrollbar with CSS, for instance. I want to do something similar, but to indicate scrolled sections rather than capture continuous scrolling. In other words, rather than increasing the length of the indicator during scroll, I only want to increase the length when a certain section of the page has been reached.

Like this:

Here’s my plan: Each section carries an indicator that’s undetectable until it reaches the top of the screen. That’s where it becomes visible by changing color and sticks to the top of the viewport.

The exact opposite should happen in reverse: the indicator will follow along when scrolling back up the screen, camouflaging itself back to being undetected to the naked eye.

There are two key parts to this. The first is for the indicator to change color when it’s near the top of the screen. The second is for the indicator to stay put at the top of the screen and come down only when its section is scrolled down to.

The second one is easy to do: we use position: sticky; on our elements. When a page is scrolled, a sticky element sticks to a given position on the screen within its parent container.

That brings us to changing colors. Since the background of an HTML document is white by default, I’m keeping white as the base color for the demo. This means the indicator should look white when it’s over the base color and turn to some other color when it’s over the indicator bar at the top of the screen.

The dashed indicator is currently invisible, but becomes visible when it sticks to the top and blends with the background color of the indicator container.

This is where CSS blend modes come into play. They give us so many options to create a variety of color amalgams. I’m going to go with the overlay value. This one is quite dynamic in nature. I won’t explain the blend in depth (because the CSS-Tricks Alamanac already does a good job of that) but taking this demo into account, I’ll say this: when the background color is white the resulting foreground color is white; and when the background is some other color, the resulting color is darker or lighter, depending on the color it’s mixed with.

The indicator stops in the demo are black. But, because of the blend, we see them as white because they are on a white background. And when they are over the indicator container element, which is a lovely shade of violet, we see a dark violet indicator stop, because we’re mixing the indicator stop’s black with the indicator container’s violet.

Starting with the HTML:

<div id="passageWrapper">
  <strong>Sections Scrolled ↴</strong>
  <!-- Indicator container -->
  <div id="passage"></div>
</div>


<!-- Indicator stop -->
<div class=passageStops></div>


<!-- First Section -->
<div class="sections">
  <!-- Content -->
</div>


<!-- Another indicator stop -->
<div class="passageStops"></div>


<!-- Second Section -->
<div class="sections">
  <!-- Content -->
</div>


<!-- Another indicator stop -->
<div class="passageStops"></div>


<!-- Third Section -->
<div class="sections">
  <!-- Content -->
</div>

Pretty straightforward, right? There’s a sticky container at the very top that holds the indicators when they reach the top.  From there, we have three sections of content, each one topped with an indicator that will stick to the top with the indicator and blend with it.

Here’s the CSS:

.passageStops {
  background-color: black; /* Each indicator stop is black */
  mix-blend-mode: overlay; /* This makes it appear white on a white background */
  width: 33.3%; /* Three sections total, so each section is one-third */
  top: calc(1em + 3px);
}


#passage, 
.passageStops{
  height: 10px;
}


#passageWrapper,
.passageStops {
  position: sticky; /* The container and stops should stick to the top */
  z-index: 1; /* Make sure the indicator and stops stay at the forefront */
}


#passage {
  background: violet; /* Will blend with black to make a darker violet indicator */
  margin: 0 0 20px 0;
}


#passageWrapper{
  background-color: white; /* Make sure we're working with white to hide indicator stops */
  height: 40px;
  top: 0px;
}


/* Each stop will shift one-third the width of the indicator container to cover the whole thing when the last section is reached. */
.passageStops:nth-child(4){ margin-left: 33.3%; }
.passageStops:nth-child(6){ margin-left: 66.6%; }


/* More styling, blah blah. */

The indicators (.passageStops) are black. But the overlay blend mode makes them appear white when it blends with the white background under it. Since there are three sections, each indicator is of one-third width.

The indicators have position: sticky; with a top distance value. This means the indicators will stick once they reach the calculated position from the top of the screen. When that happens, the black indicators that appeared white blend with the violet indicator container, which makes them appear to be a dark violet, representing the new scroll position on the page.

The reverse is also true. When an indicator loses its sticky position, it will move from the violet background of the indicator bar to the white background of the page, hiding it once again… like it was never there!

Here’s the demo again:

That’s it. You can perhaps further experiment with this by having a non-white background with another blend mode, or a gradient for the indicator bar or stops.

The post Indicating Scroll Position on a Page With CSS appeared first on CSS-Tricks.

Animating SVG Text on a Path

Animating SVG text on a path on scroll has been explained really well in this great video tutorial by the keyframers. The basic idea is to couple the startOffset value of a textPath element with the scroll position, allowing the text to move along its path while scrolling.

We wanted to take this a step further and integrate it in a real website example with some more features.

In our experiment, we made the animation smoother and used SVG filters, while also using different paths. Additionally, we worked with the Intersection Observer API for animating only the texts that are in the viewport. The intensity of the SVG filters depends on the scroll speed.

If you want to learn more about SVG filters and how to use them to create interesting effects, have a look at our dedicated series written by Sara Soueidan:

Please be aware that animating SVG filters in Firefox has dreadful performance. This has been like that for years, unfortunately. There are a number of bugs filed regarding animating SVG filters and even rendering SVG filters:

Sadly, the outlook on solving these issues in Firefox don’t look too good as these have all been marked with priority P3, which means:

“This isn’t a bad idea, and maybe we’ll want to implement it at some point in the future, but it’s not near-term roadmap material. Some core Bugzilla developer may work on it.”

So it’s a good idea to keep in mind that if you are working with SVG filters and plan to animate them, it’s probably best if you leave Firefox out of the equation. This is exactly what we did in our example, so you won’t see the fancy filter magic if you open the demo in Firefox. However, if you do want to try it out, you can do so with smaller areas, i.e. smaller texts. Although it won’t be as smooth as in Chrome, it will work better than with larger texts.

We hope you enjoy our examples and find them useful!

References & Credits

Animating SVG Text on a Path was written by Mary Lou and published on Codrops.

3D Folding Layout Technique for HTML Elements

Today we’re going to take a look at a cool, small technique to bend and fold HTML elements. This technique is not new by any means, it was explored in some previous works and one great example is Romain’s portfolio. It can be used to create interesting and diverse layouts, but please keep in mind that this is very experimental.

To start the article I’m going to come clean: this effect is all smoke and mirrors. HTML elements can’t actually bend, sorry if that breaks your heart.

This illusion is created by lining up multiple elements together to give the impression that it is a single piece. Then, rotating the elements on the edges making it look like the single piece is bending. Let’s see how that looks in code.

Creating the great fold illusion!

To begin, we’ll add a container with perspective so that we see the rotations happening in 3D. We’ll also create children “folds” with fixed dimensions and overflow hidden. The top and bottom folds are going to be placed absolutely on their respective sides of the middle fold.

Giving the folds fixed dimensions is not necessary; you can even give each fold different sizes if you are up to the challenge! But having fixed dimensions simplifies a lot of the alignment math.

The overflow:hidden is necessary, and it’s what makes the effect work. Because that’s what makes it seem like it’s a single unit even when the folds have different rotations.

<div class="wrapper-3d">
	<div class="fold fold-top"></div>
	<div class="fold fold-center" id="center-fold"></div>
	<div class="fold fold-bottom"></div>	
</div>
.wrapper-3d {
  position: relative;
    /* Based on screen with so the perspective doesn't break on small sizes*/
  perspective: 20vw;
  transform-style: preserve-3d;
}

.fold {
  overflow: hidden;
  width: 100vw;
  height: 80vh;
}

.fold-after {
  background: #dadada;
  position: absolute;
  transform-origin: top center;
  right: 0;
  left: 0;
  top: 100%;
}

.fold-before {
  background: #dadada;
  position: absolute;
  transform-origin: bottom center;
  left: 0;
  right: 0;
  bottom: 100%;
}

Note: In this case, were using the bottom and top attributes to position our extra folds. If you wanted to add more than two you would need to stack transforms. You could for example use a SCSS function that generates the code for all the folds to be in place.

Now let’s add a little bit of content inside the folds and see how that looks like. We’ll insert them inside a new .fold-content division. Each fold needs to have the same copy of the content for it to be seamless.

For now, the content is going to be a bunch of squares and headers. But you can add any HTML elements.

<div class="wrapper-3d">
	<div class="fold fold-top">
		<div class="fold-content">
			<div class="square green"></div>
			<h1>This is my content</h1>
			<div class="square blue"></div>
			<h1>This is my content</h1>
			<div class="square red"></div>
		</div>
	</div>
	<div class="fold fold-center" id="center-fold">
		<div class="fold-content" id="center-content">
			<div class="square green"></div>
			<h1>This is my content</h1>
			<div class="square blue"></div>
			<h1>This is my content</h1>
			<div class="square red"></div>
		</div>
	</div>
	<div class="fold fold-bottom">
		<div class="fold-content">
			<div class="square green"></div>
			<h1>This is my content</h1>
			<div class="square blue"></div>
			<h1>This is my content</h1>
			<div class="square red"></div>
		</div>
	</div>
</div>
.square {
	width: 100%;
	padding-bottom: 75%;
}

.green {
	background-color: lightgreen;
}

.blue {
	background-color: lightblue;
}

.red {
	background-color: lightcoral;
}

Right now the content is out of place because each fold has its content at the top. Well, that’s how HTML works. We want it to be a single unit and be all aligned. So we’ll add an extra .fold-align between the content and the fold.

Each fold is going to have its unique alignment. We’ll position their content to start at the top of the middle fold.

<div class="wrapper-3d">
    <div class="fold fold-top">
        <div class="fold-align">
            <div class="fold-content">
                <!-- Content -->
            </div>
        </div>
    </div>
    <div class="fold fold-center" id="center-fold">
        <div class="fold-align">
            <div class="fold-content" id="center-content">
                <!-- Content -->
            </div>
        </div>
    </div>
    <div class="fold fold-bottom">
        <div class="fold-align">
            <div class="fold-content">
                <!-- Content -->
            </div>
        </div>
    </div>
</div>
.fold-align {
	width: 100%;
	height: 100%;
}

.fold-bottom .fold-align {
	transform: translateY(-100%);
}

.fold-top .fold-align {
	transform: translateY(100%);
}

Now we only need to rotate the top and bottom folds from the respective origin and we’re done with creating the effect!

.fold-bottom {
	transform-origin: top center;
	transform: rotateX(120deg);
}

.fold-top {
	transform-origin: bottom center;
	transform: rotateX(-120deg);
}

Scrolling the folds

Because our folds have overflow: hidden there isn’t a default way to scroll through them. Not to mention that they also need to scroll in sync. So, we need to manage that ourselves!

To make our scroll simple to manage, we’ll take advantage of the regular scroll wheel.

First, we’ll set the body’s height to how big we want the scroll to be. And then we’ll sync our elements to the scroll created by the browser. The height of the body is going to be the screen’s height plus the content overflowing the center fold. This will guarantee that we are only able to scroll if the content overflows its fold height.

let centerContent = document.getElementById('center-content');
let centerFold = document.getElementById('center-fold');

let overflowHeight =  centerContent.clientHeight - centerFold.clientHeight

document.body.style.height = overflowHeight + window.innerHeight + "px";

After we create the scroll, we’ll update the position of the folds’ content to make them scroll with the page.

let foldsContent = Array.from(document.getElementsByClassName('fold-content'))
let tick = () => {
    let scroll = -(
        document.documentElement.scrollTop || document.body.scrollTop
    );
    foldsContent.forEach((content) => {
        content.style.transform = `translateY(${scroll}px)`;
    })
    requestAnimationFrame(tick);
}
tick();

And that’s it! To make it more enjoyable, we’ll remove the background color of the folds. And add a some lerp to make the scrolling experience smoother!

Conclusion

Over this short tutorial, we went over the basic illusion of folding HTML elements. But there’s so much more we can do with this! Each of the demos uses different variations (and styles) of the basic technique we just learned!

With one variation you can use non-fixed size elements. With another variation, you can animate them while sticking some folds to the sides of the screen.

Each demo variation has its benefits and caveats. I encourage you to dig into the code and see how the small changes between demos allow for different results!

Also, it’s good to note that in some browsers this technique has some tiny line gaps between folds. We minimized this by scaling up the parent and down-scaling the child elements. It’s not a perfect solution but it reduced it slightly and did the trick for most cases! If you know how to remove them for good let us know!

If you have any questions or want to share something lets us know in the comments or reach out to me on Twitter @anemolito!

3D Folding Layout Technique for HTML Elements was written by Daniel Velasquez and published on Codrops.

Moving Rainbow Underlines

I absolutely love the design of the Sandwich site. Among many beautiful features are these headlines with rainbow underlines that move as you scroll. It's not scroll-jacking — it's just a minor design feature that uses scroll position to enact a little movement.

To draw the rainbows themselves, we could use a linear gradient with hard-stops, the same kinda concept as drawing stripes in CSS. That's a big ol' chunk of CSS, which is fine, but I see they've opted for a background-image instead. Here's that as an SVG, which is 661 bytes (tiny tiny). We can make it look like an underline by setting the background-size to limit the height and position it along the bottom with background-position.

We'll do it on an inline element so the underline breaks where the words break:

h1 {
  span {
    background-image: url(spectrum.svg);
    background-repeat: repeat-x;
    background-size: 100vw 0.2em;
    background-position: left bottom 5px; 
  }
}

To animate it, we move the background-position-x. Not a particularly performant thing to animate, but we're not really animating it anyway — it's just moving based on scroll position. Rather than manually manipulate the background-position-x, we'll set it with a custom property, then manipulate the custom property with JavaScript.

background-position-x: var(--scrollPos);

Updating that variable while the page scrolls is easy peezy:

window.addEventListener("scroll", e => {
  let scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; 
  let newPos = scrollTop + "px";
  document.documentElement.style.setProperty('--scrollPos', newPos);
});

Here it is working!

See the Pen
Rainbow Underlines
by Chris Coyier (@chriscoyier)
on CodePen.

See that kinda janky line where I'm either using document.body or document.documentElement? That's a stupid cross-browser thing where the "scrolling element" is different in Safari versus everything else.

While doing this I learned that you can use document.scrollingElement instead to avoid the pain there. I'll leave a comment in the code about that, but leave the original line for posterity.

The post Moving Rainbow Underlines appeared first on CSS-Tricks.

Need to scroll to the top of the page?

Perhaps the easiest way to offer that to the user is a link that targets an ID on the <html> element. So like...

<html id="top">
  <body>
     <!-- the entire document -->
     <a href="#top">Jump to top of page</a>
  </body>
</html>

But we've got a few options here.

If you want it to smooth scroll up to the top, you can do that in CSS if you like:

html {
  scroll-behavior: smooth;
}

Note that placing a property on the HTML element like that is all-encompassing behavior you don't have much control over.

In this case, we aren't linking to a focusable element either, which means that focus won't change. That's probably important, so this would be better:

<html>
  <body>
     <a id="top"></a>
     <!-- the entire document -->
     <a href="#top">Jump to top of page</a>
  </body>
</html>

It's better because the focus will move to that anchor tag, which is good for people using the keyboard or assistive technology.

These require clicks though. You might need to trigger scrolling within JavaScript, in which case:

window.scrollTo(0, 0);

...is a sure bet to scroll the window (or any other element) back to the top. The scrolling behavior of that is determined by CSS as well, but if you aren't doing that, you can force the smoothness in JavaScript:

window.scroll({
  top: 0, 
  left: 0, 
  behavior: 'smooth'
});

For a more complete story about smooth scrolling, we have a page for that.

The post Need to scroll to the top of the page? appeared first on CSS-Tricks.

Smooth Scrolling Image Effects

Picking up on our last tutorial on how to add smooth scrolling plus image animations to a page, we’d like to explore some more ideas for animations. We’ve made a small set of effects that show how you can apply some interesting transforms to elements like images and text while scrolling the page smoothly.

Inspirations for some of the effects come from Jesper Landberg’s smooth scroll with skew effect demo, Manuel Rovira’s Dribbble shot Lusaxweb Home and Jo Mor’s website.

The animations are powered by TweenMax.

Attention: Note that the demos are experimental and that we use modern CSS properties that might not be supported in older browsers.

For the demos, we’ve created different (grid) layouts with images that have decorative elements and captions.

We’ve used background images that are wrapped in a division with its overflow set to hidden, so that we can animate the scale or translate of the inner images in some examples. There are many possibilities to explore, for example, rotating the images:

SmoothScrollingEffects_01

…or adding a blend mode to one of the moving elements:

SmoothScrollingEffects_02

As you can also see in Jesper Landberg’s smooth scroll with skew effect demo, you can use the acceleration to control the transform amount. So when you scroll faster, the elements distort more.

Here’s a little GIF to show a detail of one of the animations:

smoothscrolleffect.2019-07-23 11_04_22

Note that when using the scale transform, the animations in Firefox don’t perform so smoothly.

We hope you enjoy this little set and find it inspirational.

References and Credits

Smooth Scrolling Image Effects was written by Mary Lou and published on Codrops.

How to Add Smooth Scrolling with Inner Image Animations to a Web Page

Today we want to show you how to add smooth scrolling in HTML with some additional subtle animations on images. With “smooth scrolling” we don’t mean smoothly scrolling to an element, but rather a smoothly animated kind of scrolling behavior. There are many beautiful examples of such smooth scrolling behavior on some recent websites, like Elena Iv-skaya, or Ada Sokół and the stunning site of Rafal Bojar, and many many others. The latter also has a very nice image animation that is synced with the scrolling. This kind of “inner” image animation adds another interesting layer to the whole scroll movement.

Why is this kind of smooth scrolling something you’d like to add to a web page? If you have ever animated something on scroll, you might have experienced that browsers have difficulties in displaying the incoming content jank-free; especially images may show tiny abrupt jumps on scroll. It just feel easy on the eye. To avoid that, we can use the trick of animating the content itself by translating it up or down instead of using the “native” scroll.

Smooth Scrolling

Jesper Landberg created some really great Codepen demos showcasing how smooth scrolling can be applied to different scenarios. The Smooth scroll with skew effect demo shows how to add a skew effect to images while (smooth) scrolling. Here you can also see how smooth scrolling with translating the content works: a content wrapper is set to position fixed with the overflow set to hidden so that its child can be moved. The body will get the height of the content set to it, so that we preserve the scroll bar. When we scroll, the fixed wrapper will stay in place while we animate the inner content. This trick makes a simple yet effective smooth scrolling behavior possible.

In our example we’ll use the following structure:

<body class="loading">
	<main>
		<div data-scroll>
			<!-- ... --->
		</div>
	</main>
</body>

The main element will serve as fixed, or “sticky”, container while the [data-scroll] div will get translated.

Inner Image Animation

For the inner image animation we need an image and a parent container that has its overflow set to “hidden”. The idea is to move the image up or down while we scroll. We will work with a background image on a div so that we can control the overflow size better. Mainly, we need to make sure that the image div is bigger than its parent. This is our markup:

<div class="item">
	<div class="item__img-wrap"><div class="item__img"></div></div>
	<!-- ... --->
</div>

Let’s set the styles for these elements. We will use a padding instead of a height so that we can set the right aspect ratio for the inner div which will have the image as background. For this, we use an aspect ratio variable so that we simply need to set the image width and height and leave the calculation to our stylesheet. Read more about this and other brilliant techniques in Apect Ratio Boxes on CSS-Tricks.

We set an image variable for the background image in the item__img-wrap class so that we don’t have to write too many rules. This does not need to be done like that, of course, especially if you’d like support for older browsers that don’t know what variables are. Set it to the item__img directly as background-image instead, if that’s the case.

.item__img-wrap {
	--aspect-ratio: 1/1.5;
	overflow: hidden;
	width: 500px;
	max-width: 100%;
	padding-bottom: calc(100% / (var(--aspect-ratio))); 
	will-change: transform;
}

.item:first-child .item__img-wrap {
	--aspect-ratio: 8/10;
	--image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/1.jpg);
}

.item:nth-child(2) .item__img-wrap {
	width: 1000px;
	--aspect-ratio: 120/76;
	--image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/2.jpg);
}

...

The div with the background image is the one we want to move up or down on scroll, so we need to make sure that it’s taller than its parent. For that, we define an “overflow” variable that we’ll use in a calculation for the height and the top. We set this variable because we want to be able to easily change it in some modified classes. This allows us to set a different overflow to each image which changes the visual effect subtly.

.item__img {
	--overflow: 40px;
	height: calc(100% + (2 * var(--overflow)));
	top: calc( -1 * var(--overflow));
	width: 100%;
	position: absolute;
	background-image: var(--image);
	background-size: cover;
	background-position: 50% 0%;
	will-change: transform;
}

.item__img--t1 {
	--overflow: 60px;
}

.item__img--t2 {
	--overflow: 80px;
}

.item__img--t3 {
	--overflow: 120px;
}

Now, let’s do the JavaScript part. Let’s start with some helper methods and variables.

const MathUtils = {
    // map number x from range [a, b] to [c, d]
    map: (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c,
    // linear interpolation
    lerp: (a, b, n) => (1 - n) * a + n * b
};

const body = document.body;

We will need to get the window’s size, specifically it’s height, for later calculations.

let winsize;
const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight};
calcWinsize();

We will also need to recalculate this value on resize.

window.addEventListener('resize', calcWinsize);

Also, we need to keep track of how much we scroll the page.

let docScroll;
const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop;
window.addEventListener('scroll', getPageYScroll);

Now that we have these helper functions ready, let’s get to the main functionality.
Let’s create a class for the smooth scrolling functionality.

class SmoothScroll {
    constructor() {
        this.DOM = {main: document.querySelector('main')};
        this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]');
        this.items = [];
        [...this.DOM.main.querySelectorAll('.content > .item')].forEach(item => this.items.push(new Item(item)));
        
        ...
    }
}

new SmoothScroll();

So far we have a reference to the main element (the container that needs to become “sticky”) and the scrollable element (the one we will be translating to simulate the scroll).

Also, we create an array of our item’s instances. We will get to that in a moment.

Now, we want to update the translateY value as we scroll but we might as well want to update other properties like the scale or rotation. Let’s create an object that stores this configuration. For now let’s just set up the translationY.

constructor() {
    ...

    this.renderedStyles = {
        translationY: {
            previous: 0, 
            current: 0, 
            ease: 0.1,
            setValue: () => docScroll
        }
    };
}

We will be using interpolation to achieve the smooth scrolling effect. The “previous” and “current” values are the values to interpolate. The current translationY will be a value between these two values at a specific increment. The “ease” is the amount to interpolate. The following formula calculates our current translation value:

previous = MathUtils.lerp(previous, current, ease)

The setValue function sets the current value, which in this case will be the current scroll position.
Let’s go ahead and execute this initially on page load to set up the right translationY value.

constructor() {
    ...

    this.update();
}

update() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue();
    }   
    this.layout();
}

layout() {
    this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`;
}

We set both interpolation values to be the same, in this case the scroll value, so that the translation gets set immediately without the being animated. We just want the animation happening when we scroll the page. After that, we call the layout function which will apply the transformation to our element. Note that the value will be negative since the element moves upwards.

As for the layout changes, we need to:

  • set the position of the main element to fixed and the overflow to hidden so it sticks to the screen and doesn’t scroll.
  • set the height of the body in order to keep the scrollbar on the page. It will be the same as the scrollable element’s height.
constructor() {
    ...

    this.setSize();
    this.style();
}

setSize() {
    body.style.height = this.DOM.scrollable.scrollHeight + 'px';
}

style() {
    this.DOM.main.style.position = 'fixed';
    this.DOM.main.style.width = this.DOM.main.style.height = '100%';
    this.DOM.main.style.top = this.DOM.main.style.left = 0;
    this.DOM.main.style.overflow = 'hidden';
}

We also need to reset the body’s height on resize:

constructor() {
    ...

    this.initEvents();
}

initEvents() {
    window.addEventListener('resize', () => this.setSize());
}

Now we start our loop function that updates the values as we scroll.

constructor() {
    ...

    requestAnimationFrame(() => this.render());
}

render() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].setValue();
        this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease);
    }
    this.layout();
    
    // for every item
    for (const item of this.items) {
        // if the item is inside the viewport call it's render function
        // this will update the item's inner image translation, based on the document scroll value and the item's position on the viewport
        if ( item.isVisible ) {
            item.render();
        }
    }
    
    // loop..
    requestAnimationFrame(() => this.render());
}

The only new thing here is the call to the item’s render function which is called for every item that is inside the viewport. This will update the translation of the item’s inner image as we will see ahead.

Since we rely on the scrollable element’s height, we need to preload the images so they get rendered and we get to calculate the right value for the height. We are using the imagesLoaded to achieve this:

const preloadImages = () => {
    return new Promise((resolve, reject) => {
        imagesLoaded(document.querySelectorAll('.item__img'), {background: true}, resolve);
    });
};

After the images are loaded we remove our page loader, get the scroll position (this might not be zero if we scrolled the page before the last refresh) and initialize our SmoothScroll instance.

preloadImages().then(() => {
    document.body.classList.remove('loading');
    // Get the scroll position
    getPageYScroll();
    // Initialize the Smooth Scrolling
    new SmoothScroll(document.querySelector('main'));
});

So now that the SmoothScroll is covered let’s create an Item class to represent each of the page items (the images).

class Item {
    constructor(el) {
        this.DOM = {el: el};
        this.DOM.image = this.DOM.el.querySelector('.item__img');
        
        this.renderedStyles = {
            innerTranslationY: {
                previous: 0, 
                current: 0, 
                ease: 0.1,
                maxValue: parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10),
                setValue: () => {
                    const maxValue = this.renderedStyles.innerTranslationY.maxValue;
                    const minValue = -1 * maxValue;
                    return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, minValue, maxValue), maxValue), minValue)
                }
            }
        };
    }
    ...
}

The logic here is identical to the SmoothScroll class. We create a renderedStyles object that contains the properties we want to update. In this case we will be translating the item’s inner image (this.DOM.image) on the y-axis. The only extra here is that we are defining a maximum value for the translation (maxValue). This value we’ve previously set in our CSS variable –overflow. Also, we assume the minimum value for the translation will be -1*maxVal.

The setValue function works as follows:

  • When the item’s top value (relative to the viewport) equals the window’s height (item just came into the viewport) the translation is set to the minimum value.
  • When the item’s top value (relative to the viewport) equals “-item’s height” (item just exited the viewport) the translation is set to the maximum value.

So basically we are mapping the item’s top value (relative to the viewport) from the range [window’s height, -item’s height] to [minVal, maxVal].

Next thing to do is setting the initial values on load. We also calculate the item’s height and top since we’ll need those to apply the function described before.

constructor(el) {
    ...
    
    this.update();
}

update() {
    this.getSize();
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue();
    }
    this.layout();
}

layout() {
    this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`;
}

getSize() {
    const rect = this.DOM.el.getBoundingClientRect();
    this.props = {
        height: rect.height,
        top: docScroll + rect.top 
    }
}

We need the same for when the window gets resized:

initEvents() {
    window.addEventListener('resize', () => this.resize());
}
resize() {
    this.update();
}

Now we need to define the render function called inside the SmoothScroll render loop function (requestAnimationFrame):

render() {
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].current = this.renderedStyles[key].setValue();
        this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease);
    }
    this.layout();
}

This, as mentioned before, is only executed for items that are inside of the viewport. We can achieve this by using the IntersectionObserver API:

constructor(el) {
    ...

    this.observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0);
    });
    this.observer.observe(this.DOM.el);

    ...
}

And that’s it!

We hope you enjoyed this tutorial and find it useful!

How to Add Smooth Scrolling with Inner Image Animations to a Web Page was written by Mary Lou and published on Codrops.

Scroll-Linked Animations

You scroll down to a certain point, now you want to style things in a certain way. A header becomes fixed. An animation triggers. A table of contents appears. To do anything based on scroll position, JavaScript is required right now. You watch the scroll position via a DOM event and alter an element's styling based on that position. Or, probably better if you can, use IntersectionObserver. We just blogged about all this.

Now there is a new (unofficial) spec trying to bring these possibilities to CSS. I love it when web standards get involved because it sees authors like us trying to pull off certain design effects and wants to (presumably) help make it easier and more performant. I also like how this spec lists editors from Mozilla and Google and Apple.

I wonder how they'll handle the infinite-loop stuff here. Like you scroll to a point, it triggers some animation, which moves some element such that it changes the scroll position, which stops the animation, which moves the scroll position again... etc. I also wonder why it's all specific to animation. "Scroll-position styling" seems like it would have the widest appeal and use level of usefulness.

Direct Link to ArticlePermalink

The post Scroll-Linked Animations appeared first on CSS-Tricks.

Smooth Scrolling for Screencasts

Let's say you wanted to scroll a web page from top to bottom programmatically. For example, you're recording a screencast and want a nice full-page scroll. You probably can't scroll it yourself because it'll be all uneven and jerky. Native JavaScript can do smooth scrolling. Here's a tiny snippet that might do the trick for you:

window.scrollTo({
  top: document.body.getBoundingClientRect().height,
  behavior: 'smooth'
});

But there is no way to control the speed or easing of that! It's likely to be way too fast for a screencast. I found a little trick though, originally published by (I think) Jedidiah Hurt.

The trick is to use CSS transforms instead of actual scrolling. This way, both speed and easing can be controlled. Here's the code that I cleaned up a little:

const scrollElement = (element, scrollPosition, duration) => {
  
  // useful while testing to re-run it a bunch.
  // element.removeAttribute("style"); 
  
  const style = element.style;
  style.transition = duration + 's';
  style.transitionTimingFunction = 'ease-in-out';
  style.transform = 'translate3d(0, ' + -scrollPosition + 'px, 0)';
}

scrollElement(
  document.body, 
  (
    document.body.getBoundingClientRect().height
    -
    document.documentElement.clientHeight
    +
    25
  ),
  5
);

The idea is to transform a negative top position for the height of the entire document, but subtract the height of what you can see so it doesn't scroll too far. There is a little magic number in there you may need to adjust to get it just right for you.

Here's a movie I recorded that way:

It's still not perrrrrrfectly smooth. I partially blame the FPS of the video, but even with my eyeballs watching it record it wasn't total butter. If I needed even higher quality, I'd probably restart my computer and have this page open as the only tab and application open, lolz.

See a Demo

Another possibility is a little good ol' fashioned jQuery .animate(), which can be extended with some custom easing. Here's a demo of that.

See the Pen
jQuery Smooth Scrolling with Easing
by Chris Coyier (@chriscoyier)
on CodePen.

The post Smooth Scrolling for Screencasts appeared first on CSS-Tricks.

Downsides of Smooth Scrolling

Smooth scrolling has gotten a lot easier. If you want it all the time on your page, and you are happy letting the browser deal with the duration for you, it's a single line of CSS:

html {
  scroll-behavior: smooth;
}

I tried this on version 17 of this site, and it was the second most-hated thing, aside from the beefy scrollbar. I haven't changed the scrollbar. I like it. I'm a big user of scrollbars and making it beefy is extra usable for me and the custom styling is just fun. But I did revert to no smooth scrolling.

As Šime Vidas pointed to in Web Platform News, Wikipedia also tried smooth scrolling:

The recent design for moved paragraphs in mobile diffs called for an animated scroll when clicking from one instance of the paragraph in question to the other. The purpose of this animation is to help the user stay oriented in terms of where the paragraph got moved to.

We initially thought this behavior would benefit Minerva in general (e.g. when using the table of contents to navigate to a page section it would be awesome to animate the scroll), but after trying it out decided to scope this change just to the mobile diffs view for now

I can see not being able to adjust timing being a downside, but that wasn't what made me ditch smooth scrolling. The thing that seemed to frustrate a ton of people was on-page search. It's one thing to click a link and get zoomed to some header (that feels sorta good) but it's another when you're trying to quickly pop through matches when you do a Find on the page. People found the scrolling between matches slow and frustrating. I agreed.

Surprisingly, even the JavaScript variant of smooth scrolling...

document.querySelector('.hello').scrollIntoView({ 
  behavior: 'smooth' 
});

...has no ability to adjust timing. Nor is there a reliable way to detect if the page is actively being searched in order to make UX changes, like turning off smooth scrolling.

Perhaps the largest downside of smooth scrolling is the potential to mismanage focus. Scrolling to an element in JavaScript is fine, so long as you almost move focus to where you are scrolling. Heather Migliorisi covers that in detail here.

The post Downsides of Smooth Scrolling appeared first on CSS-Tricks.

Styling Based on Scroll Position

Rik Schennink documents a system for being able to write CSS selectors that style a page when it has scrolled to a certain point. If you're like me, you're already on the lookout for document.addEventListener('scroll' ... and being terrified about performance. Rik gets to that right away by both debouncing the function as well as marking the event as passive.

The end result is a data-scroll attribute on the <html> element that can be used in the CSS. Meaning if you're scrolled to 640px down the page, you have <html data-scroll="640"> and could write a selector like:

html:not([data-scroll='0']) {
  body {
    padding-top: 3em;
  }
  header {
    position: fixed;
  }
}

See the Pen
Writing Dumb JS 🧟‍♂️ and Smart CSS 👩‍🔬
by Rik Schennink (@rikschennink)
on CodePen.

Unfortunately, we don't have greater than (>) less than (<) selectors in CSS for things like numbered attributes, so the CSS styling potential is fairly limited here. You might ultimately need to update the JavaScript function such that it applies other classes or data attributes based on your math. But you'll already be set up for good performance here.

"Apply styles when the user has scrolled away from the top" is a legit use case. It makes me think of a once function (like we have in jQuery) where any scroll event would only be triggered once and then not again. They scrolled! So, by definition, they aren't at the top anymore! But that doesn't deal with when they scroll back to the top.

I find it generally more useful to use IntersectionObserver for styling things based on scroll position. With it, you can do things like, "has this element been scrolled into view or beyond," which is generically useful and can be used for scrolled-away-from-top stuff too.

Here's an example that adds or removes a class if a user has scrolled past a hidden pixel positioned at 500px down the page.

See the Pen
Fixed Header with IntersectionObserver
by Chris Coyier (@chriscoyier)
on CodePen.

That's performant as well, avoiding any scroll event handlers at all.

And speaking of IntersectionObserver, check out "Trust is Good, Observation is Better—Intersection Observer v2".

The post Styling Based on Scroll Position appeared first on CSS-Tricks.

Dealing with overflow and position: sticky;

Any overflow value other than visible and no height is the enemy of child elements with position: sticky;. It's like that element is ready to stick when the parent scrolls, but it never does because the height is unconstrained. Adding a fixed height can solve the issue, but that's not always desirable.

Dannie Vinther digs into a way of dealing with that. The end result is avoiding that situation all together by removing the element that wants to be sticky from the element that needs an overflow. But as soon as you do that, the elements no longer scroll together since they aren't siblings. The use case here is a table with sticky headers on vertical scrolling and allowing for horizontal scrolling as well. Dannie uses a script to sync the scroll positions.

Direct Link to ArticlePermalink

The post Dealing with overflow and position: sticky; appeared first on CSS-Tricks.