user profile image

By locking the perspective on the body element and transforming elements in 3D on top of that, you can easily create parallax scrolling effects that don’t require JavaScript and can be much more performant as a result. Mixins can be found here

How it works?

Parallax is simply the effect of depth because objects in the background move more slowly than objects in the foreground. Because we have 3d transforms and perspective objects in the background are smaller and move more slowly than objects in the foreground. If you lock the perspective to the view that is scrolling and preserve that perspective on child elements, a parallax effect will be visible. While these objects move at different speeds they are also scaled differently so this effect can be inverted using the scale() function of CSS transforms so things still move as if they are on different planes but the perspective no longer has impact on the scale of the element. All of this math sounds complicated, so it is bundled into easy to use mixins that can be applied with a given depth. You can also use this alongside other 3d transforms and the perspective shifts will happen to 3d boxes and things.

Some background:

I have been playing with this technique since late 2012 and have iterated on it a few times. My interest was peaked again with @keithclark’s pen that really polished the experience and added scale() to the transforms bringing objects back out of perspective. I refined this so it was more mathematically repeatable with the help of Brenna O'Brien and created Sass mixins to make the whole process really easy.


  1. Freakin awesome, love it! Nice work :)

  2. I really like that this emulates how parallax actually works. Objects further away move slower. Makes the javascript equivalent seem hacky and cumbersome. Great job.

  3. Awesome. I can wait to steal this technique. :)

  4. This is the first implementation of parallax that I've seen work well on iOS. Fantastic work!

  5. Excellent job Scott (again) - thanks for sharing :)

  6. Great find, however it's not always working...

    On Chrome at OSX the parallax effect is not visible; tested with: - Chrome 32.0.1700.107 - Chrome 34.0.1819.0 canary

    Both on OS X 10.8.4 (12E55)

    It does however work on Safari (6.0.5) and Firefox (26.0)

  7. @Grezvany13 This is a bug with Chrome on Retina Macs where preserve-3d doesn’t work properly. It is being tracked: https://code.google.com/p/chromium/issues/detail?id=338980&q=parallax&colspec=ID%20Pri%20M%20Iteration%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified

    IE (11 and below tested) don’t play nice with with preserve-3d either. However this technique is progressive enhancement so it should appear flat, not broken.

  8. Superb... (y)

  9. I have long admired the Parallax effect but hated that it had to be a JS thing as it's a presentation item and should be CSS.

    Now it is.... rockstar.

  10. This is amazing, thank you. The big issue I'm finding with this though is there is no momentum scrolling in iOS... which seems weird, because it works on desktop with a trackpad or a magicmouse.

    EDIT: ...nevermind, all it took was -webkit-overflow-scrolling: touch; applied to the parent perspective element.

  11. this is amazing. unfortunately it's not possible to apply overflow hidden to the parent element. this makes it very hard to use.

  12. superb... and would love to steal this technique... :P HOW dare you to think like this :) this is really awesome.

  13. If you want to trigger any affixing or fixed-position based on the scroll position, Keith's method, won't let you do it. Maybe it's because the layers become absolute positioned that when you want to trigger a bootstrap nav affix via position:fixed; the affixing won't be triggered.

    See the issue in this fiddle

    And if it does, the affixed element goes off screen, rather than being fixed position relative to the viewport. See here

  14. @gwho Correct, the fixed item must be outside of the parallaxed area. In this mixin you can select a parallax area other than body

    EDIT: Note I don’t do things exactly like Keith does. I use % heights instead of VH on the body to avoid issues with iOS. Also, perspective is variable so you can do other 3d transforms to the elements without them looking blown out.

  15. @gwho Here is a fork where the parallax effect is isolated so that you can have a fixed nav: http://codepen.io/scottkellum/pen/xbPLKB

  16. You are the most magnificent awesomes!

    I've spent so much time trying to figure this out, and got to the point of knowing exactly what's wrong with it, but not being able to right it lol. It sure helps to ask around, and learn by reverse engineering. Much faster, And you still learn it in the end.

    Thanks a bunch!

  17. Pure CSS. A mixin so that it's modular like a decorator. Doesn't mess up when using other 3d transforms. Increased browser compatibility. Works with fixed positions. Doesn't creates a new stacking context, thereby preventing you from going crazy wondering why z-indexes are behaving seemingly illogically.

  18. tl:dr I'm trying to figure out how to implement this in multiple sections.


    Seeing as the "container" element needs to have fixed position, it seems you must have the entire page as with a perspective property set, and elements at the "base level" (i.e. not a "background" layer of parallax) must be positioned absolutely manually to simulate it being "in-flow".

    I've been trying to figure out how to make the "base layer" (i.e. elements not moved forwards or backwards) be out of the parallax-enabling div (i.e. .container in your example case), while only including the background layer in the parallax-enabling div.

    It seems making different background images show at different points in the scroll would need to be accomplished by manually adjusting the "top:" values, or by replacing each image with javascript.

    Another aspect I've been considering is elements in the parallax not triggering scroll() or offset().top events in jquery. In the fork you provided, the navbar was fixed to begin with, but I want to provision for triggering somethign like bootstrap's .affix(), where it becomes fixed after scrolling to a certain point.

  19. If we use the perspective property, it seems unavoidable to make the entire document parallaxed.

    Here's my reasoning: Using the perspective property does create a new "stacking context", as indicated by the presence of an additional scrollbar. In the forked example, you'll notice the scrollbar for the parallax-enabled .contanier div starts after the navbar, rather than at the topmost "viewport" level. If you include elements with total height greater than your topmost "viewport" level's height, the original, real scrollbar will appear, and you'll be looking at two scrollbars.

    And once we make the entire document have a perspective property, scrolling within that div does not trigger .scroll() or .ScrollTop() or offset().top in jquery, as the topmost "viewport" level has not actually scrolled at all, The only scrolling was within the div that has the perspective property.

    You can detect the .scrollTop() position of the parallaxe-enabled element with its own scrollbar by calling `$('.parallax-enabled-element').scrollTop(), but this still doesn't get around the fact that there can only be one page-wide parallax-enabled element... otherwise, you'll end up with multiple scrollbars.

    It seems if you to to preserve the "main" in-flow layer to detect .scroll() positions, and add slower scrolling backgrounds, you can't use the perspective property.

    These are my thoughts thoughts and deductions so far. Please correct if I've made any errors.

  20. @gwho wow, a lot here.

    For one thing, the container element doesn’t need to be fixed position. This technique may not be right for your needs though, and that’s ok. JavaScript techniques are always going to give you more control. Maybe checking out some of Keith Clark’s examples will help you wrap your head around sections as he tends to structure his in sections: http://codepen.io/keithclark/pen/JycFw.

  21. hey @scottkellum

    this is great! thanks so much for this.

    I don't believe I have the same issue as @gwho, but i'm looking to have a parallax effect with only a specific section within a page. So the images within the section's container would have the parallax effect based on the whole body- currently with your code, and putting the container instead of the "body" area, the container actually becomes scrollable. I've tried playing around with that overflow, but obviously it's something more than that.

    Do you have any insight on doing this? Basically i have two overlaid images that I want to have a parallax effects on within the container that scrolls with the body

  22. @lunacraz Glad you like it. The code here is fairly configurable. Unfortunately I don’t have much context as to how you are using it or much time to do custom implementations for everyone. I’m sure you can figure it out though, the principle behind the technique is fairly straightforward and what you are asking about sounds a lot like what @gwho was asking about.

  23. @scottkellum

    Thanks for the response! so to give you an example of what I'm doing


    Let me know if you can quickly think of anything that will point me in the right direction. I still haven't sat down and tried everything, so I'll continue to do so. But basically it doesn't have that effect until I scroll into that section; ideally it would scroll with the body scroll.

    But if not, thanks anyway! Still grasping the true power of Sass and you're definitely a nice contributor :)

    Claudina says hi, by the way! she sent me this

  24. Would be cool to see a version of this that doesn't rely on compass...

  25. @StuartArtl tweaked, no longer requires Compass (using autoprefixer now).

  26. Scott, I'm new to this, so may be a stupid question, but can I get horizontal parallax effect using CSS??

  27. @Tomy-rex yes, scroll sideways on this example and you’ll see one ;)

  28. Is there a way to make it have a maximum scroll distance? Like for instance, if you wanted to end the scroll on a kitten at the bottom right?

  29. Hey @scottkellum , I hate to drill ya with another question, but I have hit a wall here. I'm also trying to take the initialization off of the body, in order to have a fixed nav. When it's applied to the body everything works perfectly, but when it's applied to the container element it changes immensely. The container has no extra styles implemented, so I am at a loss as to what's causing it. When I apply the parallax effect to the desired element, all it does is push the element down - it does not even have parallax, it's just moved.

    Any possible light you can shed on this? Again, when it's applied to the body, every thing works perfect with the exception of the fixed element I need; but, take it off of the body and apply it to the container and all hell breaks loose.

    Thank you so much for sharing your parallax snippet, my man, it's very cool.

  30. @gbish Hmm, yeah anything is possible but I think I need to write some more math to make that happen in a predictable way. I’ll tinker when I have time. I think the margin-bottom offset needs to be set to compensate for the perspective shift. Feel free to poke at it as well, this isn’t a black box that you can’t add to.

  31. @ThompsonCody do you have an example?

  32. @scottkellum


    That is the problem i'm experiencing. Maybe i'm just not fully understanding how to implement the parallax, once it's taken off the body? I need my home section to be scrolling slower then the rest of the site, while the nav remains fixed.

    Thank you, for taking the time Scott :)

  33. @ThompsonCody You need to ensure the parallax container can scroll as opposed to letting it expand to its full height, also parallax elements must be a position other than static to break them into layers: http://codepen.io/scottkellum/pen/JRjPvE

  34. @scottkellum Oh my goodness, I definitely was overthinking this one. Thank you so much Scott, I really appreciate it :)

  35. Over the weekend I somehow caught myself thinking about CSS-only Parallax (no idea why!) and it turns out it's a thing. Nice work!

  36. @newtly Do you have an example of where you’re at, what you’re trying to do, and what isn’t working?

  37. @scott I had learned this effect from somewhere (although for just a very simple use where i have a large hero section which scrolls down slowly than the content below overlapping), I got here from Keith's article where i got for finding a solution to a bug, which seems impossible to solve as its been a week now and the same bug is in this thing as well.

    The issue being that in chrome for android or any other mobile browser the address bar won't go up (or come down) while scrolling inside the parallax container which can be a lot troublesome as we will have lots of content in real websites, And the problem increases as if we scroll down to the very bottom and then pull it once more to hide the address bar and after that if the user wants to navigate off/ use the address bar then the user will have to scroll back to the very top. Also running this pen on mobile chrome, the native feature of pull down to refresh doesn't work (similar to my site) but the fork of your pen that you provided for fixed top nav bar does refresh on pull down, can you explain why?

    As final resort I am thinking of removing the effect all together. Although It is also possible that we can remove the effect only on mobiles that have this address bar responding to scrolling. If it is possible ? (As i dont have access to test devices or the info about which-all browsers have this behaviour).

  38. Hey @vimalPatra, thanks for the report. That makes sense that would happen. There are probably ways around it but it gets complicated rather quickly.

    Regarding why, this is because the address bar on these mobile devices show/hide based on scrolling of the body element but the technique limits the scrolling to stuff _inside the body element. Mobile browsers count on the body element itself to scroll up and down for this behavior and not the body element to be fixed and things inside it to scroll changing the behavior of the address bar.

  39. Any way to prevent the horizontal scroll on this?

    Found a solution: See the note at the bottom of this article: https://keithclark.co.uk/articles/pure-css-parallax-websites/ transform-origin-x: 100%;

    And thanks Scott!

Leave a Comment Markdown supported. Click @usernames to add to comment.

You must be logged in to comment.