Flutter — Parallax Effect
Understand how it works and how to implement it.
I was going through a bunch of ideas for a personal website and decided after a lot of deliberation that I have to somehow throw in a parallax effect.
I had no clue as to how to do it. But I had a real thirst to learn how. One of my various searches yielded this fantastic YouTube video, from where I learnt how to implement it. If you prefer videos to reading, then I must say, it’s a really worthwhile watch.
If you stuck around though….let’s dive right in.
What is “Parallax Effect” ?
We aren’t going to talk about parallax effect from physics/astronomy (radius * θ = length). Uh-uh.
We’re talking about something like this:
You can also check out my personal website, which implements this exact same effect. However, I must warn you that it only works on big screens (like monitors and laptop screens) for now. I need to change things up a little bit for mobile users :)
Essentially, it’s a bunch of images stacked (that was a hint) on top of each other, with each element moving faster than the one below it, with respect to the Stack.
So, the first element will be the slowest, and the last in the stack will be the fastest. If that sounds way too hard and way too much effort, then take a deep breath, because it only gets easier from here. Theory’s over !
A Skeletal Widget Structure
Let’s assume we have 2 Containers containing text. One below the other. We need to hijack the scroll speed and hence slow things down the way we want. That will let the widgets move by themselves. That is, when the user scrolls.
And how are we going to let a user scroll ?
My approach is a little hacky….let me know in the comments if you have any better ideas !
We’re going to superimpose a ListView on top of all the widgets in the Stack. This means it’s going to be the absolute last child of the Stack widget.
Next, we plonk a Container as the child of the ListView. Now here’s the hacky part — the height of the Container has to be a little more than the height of the widget tree ! So, too less, and some of your content will vanish and too much and the user will end up scrolling WAY too much.
That’s the part I hate the most. It’s just not very scalable and I am looking for alternatives…..but I simply can’t find anything else.
EDIT: So I dug around around a little bit and guess what ? I found this brilliant stackoverflow post. Turns out all we needed to do was wrap the Stack widget with a LayoutBuilder ! I’ve added a new section to this article to explain the new code. My GitHub repo is updated to reflected these changes. Duh. 😏
So, how are we going to hijack the scroll part ?
Here’s what we need to do:
- Use Positioned to get access to the top: parameter of its child widget
- We place all the Positioned widgets within a NotificationListener<ScrollUpdateNotification> widget
- This gives us access to an onNotification onCallBack event
- Check if the callback is a ScrollNotification
We define top values for each Positioned widget and then simply change it as the user scrolls using setState. Needless to say, the parent widget MUST be a StateFul widget, unless you want to half-kill yourself working with a Stateful Wrapper. 🤷🏽♂️
Seems like a little bit of work, eh ? The effect is really rewarding in the end though. So there is some light at the end of the tunnel !
Making The Code Scalable
Remember this part ?
Here we’re setting the height: to 1500, which is completely caveman. It irked me soo much. Let’s get rid of it.
Now to make stuff more scalable, we need to do a couple of things:
- Initialize a new variable which will contain the real height of the Stack
- Wrap the Stack widget with a LayoutBuilder
- The LayoutBuilder has a constructor which takes in 2 arguments, namely, a context and a BoxConstraint which is what we’re interested in
- Then we add a little bit to the height that the LayoutBuilder gave us through constraints.maxHeight and set that as the height of the Container in the ListView widget.
If you’re wondering why we added a little bit to the height then here’s why: the height that we got from contraints.maxHeight was the exact height of the Stack widget. This means that you won’t be able to scroll anymore ! Hence the height needs to be a wee bit more. I used 500, but you can use whatever you want.
Please note, that I wrote the following code for web use. It will have to be adapted to work well on mobile devices.
Alright then, if you have any queries feel free to post them in the comments and I’ll try to help out ! Peace.