Updated for Divi 5: Luxy.js still works in Divi 5: I tested every setup from this post on a Divi 5 site, and the #page-container and #et-main-area wrappers are still there. The snippets now skip the effect for visitors who prefer reduced motion, fix the cut-off footer automatically, and the post explains what Luxy.js breaks (sticky elements, Scroll Effects) so you can decide if it fits your site.
Want to make your Divi site feel extra smooth and fancy? Try Luxy.js!
Luxy.js is a tiny JavaScript library that makes the page glide softly to a stop when you scroll, and it can move chosen elements at a different speed than the rest of the page (a parallax effect). You load it with one script tag and start it with a few lines of code, no plugin needed.
If you've ever scrolled through a site and thought “Whoa, that felt nice”, chances are you've experienced a custom scroll effect. And that's exactly what Luxy.js can do for your Divi website.
Luxy adds a buttery-smooth scrolling experience with subtle parallax effects. It's super lightweight and doesn't require any plugins. You'll just need a few lines of code, and in this post I'll show you how to implement it in several ways, depending on your layout setup.
Behind the scenes: Yup, this is another effect inspired by a student question inside Divi Stylist Academy. You ask, I test, and if it works, we all win 😄
Watch the walkthrough
The video shows the whole setup in Divi: adding the library, picking the right wrapper for your header, and adding parallax to single elements.
Want to see exactly how to implement this effect? In this step-by-step video tutorial, I'll show you how to set up Luxy.js inside your Divi website (with different code options depending on your setup). 👇 Watch below and follow along!
The video was recorded in Divi 4. The steps are the same in Divi 5 (Theme Options ➜ Integration is still there), but use the updated snippets from this post, and add the parallax data attributes the Divi 5 way if you like (see Option 3).
You can also check out the official Luxy.js demo.
Step 1: Add Luxy.js to your Divi site
Load the Luxy.js file from a CDN in Divi ➜ Theme Options ➜ Integration ➜ Add code to the < head > of your blog (same place in Divi 4 and Divi 5; make sure Enable header code is switched on):
<script src="https://cdn.jsdelivr.net/npm/luxy.js@0.1.0/dist/js/luxy.min.js" defer></script>
The defer attribute lets the page render first. It's safe here, because the setup code from Step 2 waits until the page is ready.
Want the effect on one page only? Put both scripts (this one and the one from Step 2) in a Code module on that page instead of Theme Options.
Step 2: Choose your setup
Now it's time to initialize Luxy. Pick the version of the script that matches your layout best, and paste it in the same Integration field, right below the script from Step 1.
All versions are wrapped in jQuery(document).ready(…). That matters in Divi 5: Divi loads jQuery at the bottom of the page by default (Theme Options ➜ General ➜ Performance ➜ Defer jQuery And jQuery Migrate) and runs wrapped code once jQuery is there. Each version also skips the effect in the Visual Builder (the et-fb body class, still used in Divi 5), for visitors whose device is set to reduce motion, and on pages without the wrapper element (for example the Divi "Blank Page" template has no #et-main-area; without that check Luxy.js stops with a JavaScript error). More about these choices in my guide to adding JavaScript to Divi.
Option 1: You don't have a fixed navigation
If your navigation scrolls with the page (in other words, it's not fixed to the top of the screen), you can use #page-container as the wrapper:
<script>
jQuery(document).ready(function($){
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var $wrapper = $('#page-container');
if (! $('body').hasClass('et-fb') && ! reduceMotion && $wrapper.length) {
luxy.init({
wrapper: '#page-container',
wrapperSpeed: 0.06
});
}
});
</script>
Option 2: You use a fixed navigation (standard or Theme Builder)
Use this version if:
- You're using the standard Divi header and enabled Fixed Navigation in Theme Options, or
- You're using a Theme Builder header with a section set to Stick to top (Advanced ➜ Scroll Effects ➜ Sticky Position, same place in Divi 4 and Divi 5).
In this case, your wrapper should be #et-main-area instead. The header sits outside that wrapper, so it stays fixed while the content glides:
<script>
jQuery(document).ready(function($){
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var $wrapper = $('#et-main-area');
if (! $('body').hasClass('et-fb') && ! reduceMotion && $wrapper.length) {
// Luxy.js only counts the wrapper's height, so add the header's height below it
// (otherwise the bottom of the footer gets cut off)
var fixHeight = function(){ $wrapper.css('padding-bottom', $wrapper.offset().top); };
fixHeight();
new ResizeObserver(fixHeight).observe($wrapper.parent()[0]);
luxy.init({
wrapper: '#et-main-area',
wrapperSpeed: 0.06
});
}
});
</script>
Note: Luxy.js sets the page height to the height of the wrapper and forgets the header above it, so without a fix the bottom of the page (usually the footer) gets cut off by the height of your header. The fixHeight lines add that height below the wrapper automatically, and update it when the header changes size (I tested this in Divi 5). If you prefer the manual way from the video, add extra bottom padding (as tall as your header, often 150–200px) to the last row or section of your Footer template instead.
Option 3: Add parallax effect to a single element
Want one element to move at a different speed than the rest of the page? Use a unique CSS class for that element, something like element.
In your builder, apply this class to the module, image, or row you want to animate (Divi 5: Advanced ➜ Attributes ➜ Add Attribute ➜ CSS Class; Divi 4: Advanced ➜ CSS ID & Classes). (Don't forget: this should be an element that isn't crucial to layout alignment, more on that in the video!)
In the tutorial, I also show how to use an empty row with a background image to fake a parallax background. Why? Because Divi's native parallax setting doesn't play nicely with Luxy.
Here's the code:
<script>
jQuery(document).ready(function($){
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var $wrapper = $('#et-main-area');
if (! $('body').hasClass('et-fb') && ! reduceMotion && $wrapper.length) {
// Luxy.js only counts the wrapper's height, so add the header's height below it
// (otherwise the bottom of the footer gets cut off)
var fixHeight = function(){ $wrapper.css('padding-bottom', $wrapper.offset().top); };
fixHeight();
new ResizeObserver(fixHeight).observe($wrapper.parent()[0]);
$('.element').attr({
'data-speed-y': '-5',
'data-offset': '20'
});
luxy.init({
wrapper: '#et-main-area',
wrapperSpeed: 0.06,
targets: '.element'
});
}
});
</script>
Divi 5 tip: you can skip the $('.element').attr(…) lines and add the two data attributes right in the builder: Advanced ➜ Attributes ➜ Add Attribute, name data-speed-y with value -5, and name data-offset with value 20. I tested both ways in Divi 5, Luxy.js picks them up either way. Keep the class, because the targets option uses it to find the element.
Option 4: Multiple parallax elements, each with different speed
You can use multiple custom classes and give each element its own scroll behavior. Just replace element and element2 with your own class names, and apply those classes in the Divi Builder.
The fun part? You can mix and match values: try different data-speed-y and data-offset numbers to get just the right look. A negative data-speed-y moves the element up faster than the page, a positive one makes it lag behind. It may take a few tries to get everything just right, depending on spacing and layout.
<script>
jQuery(document).ready(function($){
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var $wrapper = $('#et-main-area');
if (! $('body').hasClass('et-fb') && ! reduceMotion && $wrapper.length) {
// Luxy.js only counts the wrapper's height, so add the header's height below it
// (otherwise the bottom of the footer gets cut off)
var fixHeight = function(){ $wrapper.css('padding-bottom', $wrapper.offset().top); };
fixHeight();
new ResizeObserver(fixHeight).observe($wrapper.parent()[0]);
$('.element').attr({
'data-speed-y': '-5',
'data-offset': '20'
});
$('.element2').attr({
'data-speed-y': '3',
'data-offset': '10'
});
luxy.init({
wrapper: '#et-main-area',
wrapperSpeed: 0.06,
targets: '.element, .element2'
});
}
});
</script>
Option 5: Disable on mobile/touch devices
Luxy.js works on mobile, but if you don't love how it feels on touch screens, you can disable it with a simple condition that checks for touch input:
<script>
jQuery(document).ready(function($){
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
var $wrapper = $('#et-main-area');
if (! $('body').hasClass('et-fb') && ! reduceMotion && ! isTouch && $wrapper.length) {
// Luxy.js only counts the wrapper's height, so add the header's height below it
// (otherwise the bottom of the footer gets cut off)
var fixHeight = function(){ $wrapper.css('padding-bottom', $wrapper.offset().top); };
fixHeight();
new ResizeObserver(fixHeight).observe($wrapper.parent()[0]);
$('.element').attr({
'data-speed-y': '-5',
'data-offset': '20'
});
$('.element2').attr({
'data-speed-y': '3',
'data-offset': '10'
});
luxy.init({
wrapper: '#et-main-area',
wrapperSpeed: 0.06,
targets: '.element, .element2'
});
}
});
</script>
Keep in mind that laptops with a touch screen count as touch devices too, so they also get the normal scroll.
What changes when you use Luxy.js in Divi 5?
Luxy.js works by making the whole wrapper position: fixed and moving it with transform while you scroll. That is the same in Divi 4 and Divi 5, and it has side effects on some Divi features. Here is what I found on a Divi 5 test page:
- Sticky elements inside the wrapper stop sticking. A row with Sticky Position ➜ Stick to Top scrolled away with the page instead of staying at the top. Only things outside the wrapper (like the header in Option 2) stay fixed. If you need sticky rows or modules on a page, leave Luxy.js off on that page.
- Scroll Effects run out of sync. Divi still animates elements with Advanced ➜ Scroll Effects (Vertical Motion, Fading In and Out and the rest), but it measures their position the moment you scroll, while Luxy.js is still gliding the content to its new place. In my test the same element ended up in a noticeably different spot and opacity than without Luxy.js. Use Luxy's own parallax (Options 3 and 4) instead of Scroll Effects on the same page.
- Links to sections still work. A link to a section ID (like
#contact) still scrolled to the right section in Divi 5. - jQuery loads late, and that's fine. Divi 5 defers jQuery by default, and the wrapped snippets above ran correctly with that setting.
Is Luxy.js a good idea? Accessibility, mobile, and performance
Luxy.js is a fun effect for portfolio and showcase sites, but it changes how scrolling feels, so use it with care. These are the trade-offs to know about before you add it to a client site:
- Motion sensitivity: smooth-scroll and parallax effects can make some people feel dizzy. That's why every snippet in this post checks
prefers-reduced-motion: visitors who turned on "reduce motion" in their system settings get the normal scroll. - Keyboard users: with Luxy.js active, pressing Tab to a link further down the page does not scroll the page to it, so the focused link stays off screen (I tested this). The fix below takes care of it.
- Mobile: touch screens already have their own momentum scrolling, so the extra smoothing can feel laggy. Use Option 5 if you want Luxy.js on desktop only, and test on a real phone.
- Performance: Luxy.js updates the page on every animation frame, even when nobody is scrolling. On long, heavy pages that can cost battery and make scrolling feel heavier on older devices.
- Maintenance: version 0.1.0 is the latest release of Luxy.js, and the library hasn't been updated for years. It still works, but don't expect fixes for new browser changes.
To keep keyboard navigation working, add these lines inside the if block of your snippet, right after luxy.init({…});:
// Scroll to the focused element when someone uses the Tab key
$(document).on('focusin', function(e){
var r = e.target.getBoundingClientRect();
if (r.top < 0 || r.bottom > window.innerHeight) {
window.scrollTo(0, window.scrollY + r.top - window.innerHeight / 3);
}
});
For more simple ways to make Divi sites easier to use for everyone, read my beginner tips for more accessible Divi sites.
And that's it!
Pick the option that matches your header, paste the two scripts, and check the page with and without a fixed header, sticky elements, and Scroll Effects before you go live.
As I mentioned in the tutorial, Luxy is fun to play with, but the parallax effect can be a bit tricky. I recommend using it mainly on background elements, where exact positioning isn't critical. And it works best on layouts with plenty of white space, so elements have room to breathe and move around.
If you decide to try it on your own site, I'd love to see what you come up with! Share it in my Divi Lovers Facebook group, and let's spread that smooth scroll joy 💜





