WebGL Enhanced Drag Slider Tutorial With curtains.js — Part 3
Improve the overall performance of the slider by removing any layout repaint calls

This piece is the third and last part of our tutorial. In the first part, we created a JavaScript drag slider. In the second part, we added WebGL with curtains.js to improve the animations. Please check them out if you haven’t done so already.
In this final part, we’ll see how to improve the overall performance of the slider by removing any layout repaint calls.
Here’s the final pen:
As a reminder, we used curtains.js to add everything related to WebGL.
curtains.js is an open-source Vanilla JavaScript library. It is particularly useful here as its main purpose is to enhance DOM elements with WebGL effects.
With a few lines of JavaScript, you were able to create WebGL textured planes, bound to our slider items’ HTML elements, and then post-process the whole scene.
We can then see the advantages of using curtains.js:
- Clean and SEO-friendly HTML code.
- You don’t have to worry about your WebGL objects’ sizes and positions as most of the stuff (like resize) will be handled under the hood by the library.
- If, in any case, there’s an error during the WebGL initialization or in your shaders, the slider will still work!
Layout Repaint Calls / Reflow
Overall concept
First of all, you’ll need to understand what layout repaint and reflow are and what causes them. There’s a neat article written by Paul Irish, explaining it on GitHub.
Basically, every time you’re using a JavaScript method to get an HTML element style or position (such as element.getComputedStyle()
or element.getBoundingClientRect()
), or every time you’re trying to access the window scroll value via window.scrollY
, the browser needs to redraw the whole web page to calculate those values.
This could lead to a performance bottleneck and could cause jank issues.
Using curtains.js
As a matter of fact, curtains.js does internally use getBoundingClientRect
each time you’re adding a plane, or each time you resize your window to copy the plane’s HTML size and position, and apply it to its associated WebGL plane object.
There’s no way to avoid that, but, fortunately, this does not happen often so it’s not really a big deal.
But, we used the updatePosition
method in our slider’s onTranslation
hook and this function also calls getBoundingClientRect
, therefore triggering a reflow call.
We’ll now see how to improve our slider to get rid of those layout repaint calculations.
Another Way to Translate Our Planes
Using the setRelativePosition method
The library provides another way to translate a plane: the setRelativePosition
function.
It takes an X and a Y value in pixels as parameters and applies it to your plane by updating its model-view matrix.
Use Cases
Usually, when you’re using curtains.js, you’ll be using the updatePosition
inside a scroll event listener to update the positions of your planes according to the window scroll value.
Because there’s no way to get your scroll or HTML element’s position without triggering a reflow call, that’s still the best way to handle planes’ positions when using a native scroll.
But, if you are using a virtual scroll library, for example, you’ll have access to scroll values that get calculated without causing reflow calls and you’ll be able to update your planes’ positions accordingly, with our setRelativePosition
method!
Now, back to our slider. At any given time, we have access to its current translation (which gets calculated without triggering reflow) so why not use it to update our positions instead? That’s precisely what we are going to do.
The Idea
The overall concept is fairly simple: we will apply our translation to the planes using the setRelativePosition
function.
As our planes’ positions are automatically updated each time we resize our browser window, we’ll have to reset their relative positions there.
By doing that, we’ll break the sync between the slider’s current translation and our relative positions’ values. We then have to keep track of our slider’s previous translation and subtract those values to our relative position.
The slider and the WebGL are now synced again!
Updating the Code
First of all, we’ll add a previousTranslation
property to our WebGL slider class.
Let’s update our WebGLSlider
class constructor:
Next, we’ll have to update our onTranslation
Hook to use the setRelativePosition
function instead of the previous updatePosition
one:
The last thing we need to do is rewrite our onSliderResized
Hook to update our previousTranslation
property values, reset our planes’ relative positions, and we’re done!
And that’s all folks!
Now you’re ready to use curtains.js on your website with minimal performance impact.
You can try to change some values in your shader pass fragment shaders to tweak the effect a bit. You can also try to use a different displacement image (remember, you’ll have to use a pattern to obtain a seamless effect).
Here’s a bonus example with a different displacement image and a few variations in the shaders:
Conclusion
I hope you liked this article and found it useful. I also hope you saw how easy it is to use curtains.js to enhance your UI with neat WebGL effects.
Have a look at the library’s documentation and examples or check out its GitHub repo if you want to know more.
Be creative!