How to Trigger New Behavior on Scroll With Scrollytelling
Using scroll position to trigger new behavior
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the Better Data Visualizations with Svelte course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Better Data Visualizations with Svelte, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:15] In this lesson, we're going to be updating our existing scrolling telling chart to trigger new behavior whenever a new step comes into view. So just as a reminder, we left off with this, you know, scrolling telling view of just text elements that appear on top of a chart.
[00:16 - 00:26] And you might notice at the very bottom of the viewport, like right now, as an element comes into view, it triggers an update to its opacity. It goes from 0.3 opacity to 1 opacity.
[00:27 - 00:38] It's basically how we keep track of which step is currently active. And as a reminder, you could very easily keep track of this in your console or as a text element, for example.
[00:39 - 01:06] So if I wanted to go ahead and add, you know, a reference and say, this is the reference step number, and I go ahead and render current step, I could do this, and then I would just need to make sure that current step is always visible, and that it's fixed at the bottom right corner of the canvas. So if I did this, sorry, reference step is the name of the class.
[01:07 - 01:17] See how 1, 2, 1, 0, etc. So it's very small there, so maybe you want to add a padding of 1 rem or something so you can actually see it.
[01:18 - 01:30] And then just to remind ourselves, we can go ahead and say current step. And now you can always see what step is currently in view, just as a reminder to ourselves, which will be helpful as we're triggering new behavior in this lesson.
[01:31 - 01:38] So without out of the way, what do we want to do in this lesson? I've identified three things that I think would be nice, three kind of features .
[01:39 - 01:48] First is to update the data on scroll, specifically the positions of each data point. We want to update those dynamically whenever a new step comes into view.
[01:49 - 01:59] Second, we'll transition smoothly between steps. So rather than have those abruptly go from point A to point B just basically disappear and reappear, we'll transition the elements.
[02:00 - 02:14] And third, we'll dynamically set hovered data, which remember is kind of our tooltip data, based on the current step. So we could, you know, dynamically basically highlight a data point of a certain individual, a certain circle in our chart based on the current step.
[02:15 - 02:21] Okay. So with that roadmap out of the way, let's go ahead and get started by updating data on scroll.
[02:22 - 02:34] So as a reminder, the way that we're currently rendering data is with this each block that you see online 54 right here. So we iterate through each data element after sorting it according to its grade .
[02:35 - 02:48] And the reason that we have it sorted is so that our tab indexing like this shows each circle one by one in order of the hours studied or in order of the final grade. And so that's kind of the reason that we have this sorting.
[02:49 - 03:02] So our immediate step in this lesson is going to be updating the data that we 're using to render the chart. So that rather than, you know, this data, we're going to update this data and render a different variable in our each block.
[03:03 - 03:06] Okay. Let's go ahead and do that starting off at line 37.
[03:07 - 03:21] What we're going to do is create a variable called initial data, which is going to equal what we just saw in our each block. So if you want, you could go ahead and copy that paste it as initial data.
[03:22 - 03:29] And then we're going to have a new variable, which we call rendered data. Now by default, rendered data is going to be equal to initial data.
[03:30 - 03:39] So this is pretty important. And now let's go ahead and replace rendered data and replace that string that we had online 56 in our each block with rendered data.
[03:40 - 03:47] At first, nothing changes. Nothing should change because this is just a variable that references what was already being referenced in our each block.
[03:48 - 03:55] So now we want to basically update rendered data if and when current step changes. So that's the first thing that we're going to do.
[03:56 - 04:05] And the way that we're going to do this is with a reactive block. So I'll prefix again with this dollar colon to basically say, listen for any changes within the following block.
[04:06 - 04:21] And here's where I will include an if else statement, kind of a reactive if block. Interestingly, my GitHub co-pilot is actually suggesting that we do something like this, which would be interesting, but it's not exactly what we want.
[04:22 - 04:29] So what I'm going to do is I'm going to say if current step equals zero. So that's the case whenever the chart is resting or in its first text step.
[04:30 - 04:35] What do we want? Well, let's go ahead and describe kind of what would be cool at each step.
[04:36 - 04:47] And what I've identified is kind of this progressive reveal of the data points. So at step zero, I want both the x and y values to just be zero.
[04:48 - 04:52] Okay. So that would basically make the circles appear at the exact center of the chart.
[04:53 - 04:58] That's what I've identified as a cool first step. So let's leave a comment for ourselves of what we want to do.
[04:59 - 05:09] It's going to be set both x, which is equal to grade and y, which is going to be equal to hours to zero. That's what we want to achieve.
[05:10 - 05:28] In order to do so, we're going to reset rendered data to equal initial data dot map. So this map operation is pretty important, but basically it's iterating through each of the elements in initial data and returning something based on whatever we put inside of the map.
[05:29 - 05:45] So given d, which is the current element, we want to return a new object. So that's this syntax that just like autopilot or co-pilot suggests, basically assumes the current values of D, but then applies a zero to grade and a zero to hours.
[05:46 - 05:51] Let's hit save and see what triggers by default. You notice that everything appears at the bottom left.
[05:52 - 05:55] So maybe we're doing something right. Maybe we're not.
[05:56 - 06:09] Let's go ahead and for now just proceed with step one and two because step zero is technically step one. And so we'll go ahead and say else if current step equals one.
[06:10 - 06:16] And then what do we want to achieve within this if block? Let's only set our y value, which is hours to zero.
[06:17 - 06:26] So here we'll just basically have the exact same bit of code, but rather than setting both equal to zero, we'll only set hours. So we'll remove this declaration here.
[06:27 - 06:54] And then finally else, if current step equals two, meaning we're at the final step, let's go ahead and set all data to default, where we basically reset rendered data to equal initial data. So let's see what happens now as I scroll between step zero, one and two, we see that everything is first at the bottom left, then it assumes its proper grade position, then it goes up to their actual positions.
[06:55 - 07:09] So what I was describing was wanting to start at the midpoint and then spread out, but currently things are in the bottom left. We're going to get to the midpoint later, but for now we know that we have this kind of triggering of new positions based on scroll.
[07:10 - 07:20] So we basically completed step one for now. And so given that, let's go ahead and start step two, which is going to be transition smoothly between steps.
[07:21 - 07:28] So right now, like I said, everything is very abrupt. And we want to basically transition the circles to their proper positions.
[07:29 - 07:53] So thankfully for this, because all we're moving for each circle is its CX and CY positions, we can go ahead and add these transitions using CSS. So within my lengthy style tag now in app.spelt, let's find our circle rule set , which we see on line 120 through 123, and we already have a transition property that targets the radius and opacity of each circle.
[07:54 - 08:04] Let's add the CX and CY so that those properties also animate. We'll say CX has a 500 millisecond E's transition, same with CY.
[08:05 - 08:24] Let's hit save and see what happens as I scroll from point A to point B. Now you see these nice transitions that look pretty smooth. Finally, if you want to have some fun with these transitions, you can play around with the cubic Bezier easing functions that basically give you maximum control over how your transitions look.
[08:25 - 08:35] So for example, if I wanted to update these transitions to use a more dynamic transition easing function, I found this cubic Bezier function is pretty nice. It looks pretty cool.
[08:36 - 08:51] Notice how this is slightly different than the existing ease where everything fades maybe slower into place at first and then faster near the endpoint. So just to kind of illustrate that once again, the difference between steps.
[08:52 - 08:59] So whatever you prefer in terms of your cubic Bezier ezings, but I think this looks pretty cool. So feel free to play around with those numbers.
[09:00 - 09:17] And obviously if you wanted to do some research, you could definitely Google this cubic Bezier ezings and you could find a whole bunch of helpers and cheat sheets that basically walk you through how to use CSS to make these really cool ezings. This in particular is very helpful.
[09:18 - 09:32] It's kind of a visual cheat sheet that shows you different types of ezings and the code you need to get them. So I recommend playing around with this, finding the good ezings, especially for something as dynamic as point transitions, thinking can bring your chart to the next level.
[09:33 - 09:49] So now let's go ahead and revisit what we wanted to approach address earlier, which is revisiting these circles positions. Rather than starting off in the bottom left corner at the zero zero position, we want to leverage the midpoint of the chart and render circles there.
[09:50 - 10:00] So toward that end, we're going to create two new variables, which are going to call x midpoint and y midpoint. And for now, I'll go ahead and comment these out to be explained the logic of how we're going to achieve this.
[10:01 - 10:06] So what we're going to want to use is our existing x scale domain. So what does x scale domain include?
[10:07 - 10:15] Well, you might remember, I'm going to console.log it and prefix this with a dollar label so we can actually see it. You'll see that it's a range from zero to 100.
[10:16 - 10:23] And that's because that is literally the domain that we passed here, zero to 100. And we want to return the middle value between zero and 100.
[10:24 - 10:41] The reason that we want the raw point rather than the scaled point within the range is because we're going to use this within rendered data, right, we're basically going to replace the zero with x midpoint or y midpoint. And then within each block that we're rendering rendered data within, we are scaling the values.
[10:42 - 10:51] So it's important that we get the raw value, not the scaled value, because we 're going to pass it into x scale. With that in mind, how do we get the midpoint of two values in an array like this?
[10:52 - 11:04] Well, this is some like basic math that honestly I forgot that some people might forget for zero to 100 is pretty easy. But if the numbers were like 20 and 70 or 50 or whatever else, how would you find the midpoint?
[11:05 - 11:18] The answer is we add these two numbers together and then we divide by two and that should return the midpoint. So let's go ahead and write that the x midpoint should be x scale of domain and find the first index.
[11:19 - 11:32] And then let's add x scale domain of the zero with index and then divide that value by two. So let's verify that this works by console dot logging x midpoint.
[11:33 - 11:36] See what we get. We get a number of 50, which we definitely know is correct.
[11:37 - 11:44] Let's verify that this works by adding two different numbers. Let's do 70 and 30.
[11:45 - 11:47] And what do we expect the midpoint to be? Well, this is still 50.
[11:48 - 11:51] So maybe it's a bad example. What's the midpoint between 70 and 20?
[11:52 - 11:58] The answer is 45. The minus 25 is 45.
[11:59 - 12:03] 20 plus 25 is 45. So there we have it.
[12:04 - 12:12] This is how we achieve the midpoint. Basically, if the range was 70 to 20 or 20 to 70, we'd want to do this exact same treatment.
[12:13 - 12:24] In our case, it's much easier with an x scale domain of one and zero and then we'll do the exact same thing for our y midpoint. We'll get the first and zero with index and divide the value by two.
[12:25 - 12:33] So now let's see what our x and y midpoint are. Let's go ahead and console dot log x midpoint and y midpoint.
[12:34 - 12:35] Hit save. Refresh me.
[12:36 - 12:47] You'll see we have a midpoint of 50 on the horizontal dimension and 30 on the vertical dimension. So now let's go ahead and include those in our if block down here and replace these zeros with them.
[12:48 - 13:11] So first we'll go up here and we'll replace grade zero with grade of x midpoint and we'll replace hours of zero with y midpoint. Let's do the exact same thing in our second step and current step equals one and say instead of hours equals zero, hours equals y midpoint.
[13:12 - 13:19] Let's hit save and see what happens. We first enter the chart viewport, all circles converge on the zero, zero position.
[13:20 - 13:31] Then as we scroll, they stay on the zero position horizontally, vertically, or vertically, but horizontally they spread out. Finally they transition into their final positions in the final step.
[13:32 - 13:45] This is exactly what we wanted to achieve. And now we've basically successfully triggered these new behaviors on scroll and they sit nicely at this middle position using x and y midpoint.
[13:46 - 13:56] The final thing that we want to do is dynamically set hovered data, which you 'll remember is this tooltip data that we're keeping track of in a variable that we call h overed data. We want to update that in a specific step.
[13:57 - 14:02] So thankfully everything yet again is just going to live within this dynamic block. This is pretty easy.
[14:03 - 14:08] So let's do it specifically on step three. Let's reset hovered data to equal.
[14:09 - 14:15] And then we could do anything here. We could look within rendered data and find the person with the name of whatever.
[14:16 - 14:23] Okay, that's one thing we could do. We could say it's the first person who scored higher than a 70.
[14:24 - 14:33] That could be another thing that we do. The important thing is here that because hovered data is expecting one element that we only return one element, which is why we're using find here rather than filter.
[14:34 - 14:47] But just to illustrate exactly how we do this, let's just add a random number and basically find the 13th or the 12th student in our data set. So we'll set hovered data equals rendered data 13th index.
[14:48 - 14:54] See what happens as we scroll to that, we then update hovered data. There is this issue where that will not disappear on other steps.
[14:55 - 15:02] So the way that we fix that is actually pretty simple. We just say hovered data equals null or undefined on every other step.
[15:03 - 15:06] So let's hit save. See what happens as we scroll.
[15:07 - 15:10] The video is highlighted. That person disappears as we scroll back up.
[15:11 - 15:18] So you can play around with whoever you want to render here, whatever you like most. But feel free to play around with that number.
[15:19 - 15:27] And now as you scroll into step three, you'll see that specific circle, that student is highlighted. So let's add some final cleanup because we're basically known with this lesson.
[15:28 - 15:38] You may notice that we can't hover over circles within our chart. And that is a problem because right now it is a guided exploration, but there's no ability for the user to explore themselves.
[15:39 - 15:59] This is because earlier we applied this z index on our steps element to be two, which basically put it on top of the sticky element and basically made it impossible to use our cursor on that sticky element. So we want to go back into our CSS, find our steps element and add a simple property of pointer events, none.
[16:00 - 16:04] Let's hit save and now we can hover over individual students. So that was easy.
[16:05 - 16:19] And then the second thing is that we probably don't want to hover over student or hover over. We don't want to see the tooltip whenever we hover over circles in like step zero, for example, here doesn't make sense to show a tooltip.
[16:20 - 16:48] So let's just adjust the existing mouse over and focus events that we already have right here to only trigger whether or not current step based on whether or not current step is greater than or equal to two. So let's say if current step is greater than or equal to two, then do the following or we can make this a little bit cleaner and say if current step is less than two, then just return.
[16:49 - 16:55] So don't do anything below. So if we added this here now as I tried to hover over a circle, nothing would happen.
[16:56 - 16:58] Same thing here, nothing would happen. Maybe we want step one to be the cutoff.
[16:59 - 17:04] So you could easily do this so that you could hover in this step. This is personal preference, it's up to you.
[17:05 - 17:12] But then finally, as I approach step two, I actually can hover over individual students. So what have we done in this lesson?
[17:13 - 17:27] Well, we've taken our plain, scrolly telling text only version and we've added functionality. That functionality first transition points to new positions on the canvas, starting at the midpoint and then out into their actual respective positions.
[17:28 - 17:37] Then second, we made those transitions smooth. And then third and finally within that same reactive block, we updated hovered data to basically account for the current step.
[17:38 - 17:46] So we can dynamically highlight or de-emphasize certain students as we progressed. So we accomplished a lot in this step.
[17:47 - 17:57] If you want, I'm going to go ahead and delete our reference step that I added earlier just because that was kind of a visual reference. So I'll delete this bit of code and it's corresponding CSS rule set.
[17:58 - 18:14] And finally, we're ready to move to our next lesson where we'll be updating the axes on scroll so that as the user sees all of these circles in the center of our chart , there's no reason to see the axes on the x and y dimensions, so we could delete those. So we'll do that next lesson.
[18:15 - 18:16] Thanks for bearing with me and I'll see you then.