Best choice on how to use Stimulus


I am using Symfony framework, Stimulus & Drive.
I want to listen to scroll event on my web page in order to add a shadow to my navbar if scrollY > 0

// base.html
<nav id="navbar-top" data-controller="scroll-position">
// scroll-position_controller.js
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
        if (!document.documentElement.hasAttribute('data-scroll')){
            document.documentElement.dataset.scroll = '0';
            document.addEventListener("scroll", this.storeScroll, { passive: true })

    storeScroll() {
        document.documentElement.dataset.scroll = String(window.scrollY);
// _navbar.scss
html:not([data-scroll='0']]) {
    #navbar-top {
        box-shadow: 0 0.5rem 0.5rem -0.5rem rgba(0, 0, 0, 0.2);

I realize that adding the scroll-position controller to my navbar is not a good solution as this controller initialize and connect each time I render a page. So I wonder if it would be better to add the scroll-position controller to HTML element. Benefit is that as HTML persist from one rendering to the next, it will not initialize & connect at each rendering. But I have red that we should avoid to attach Stimulus controller to wide scope element for performance concerns. So my question is what is the best solution ? Maybe it’s even better to not use Stimulus in this kind of situation as I may just write a simple vanilla javascript function adding the event listener outside a Stimulus controller and thanks to Turbo Drive this function will only load once. What would you do ? Thanks !

I am not an HTML expert but the fact that an HTML element exist on all pages is true, but it should replaced after new page hit. So I guess you will have the same problem.
I don’t see a way of having the JS not replay between page hits unless you have something like Turbo and have your page made of two turbo frames : one for the nav and one for the rest of the page. Then any page hit made outside of the nav should not trigger any JS attached to the nav.
But I may be wrong, maybe someone will have a good idea for you case…
(Also I don’t really think your eventListener is really expensive for your page.)

By HTML element I mean <html> tag which persist from one rendering to the next. But <html> has a wide scope and I have understood by reading documentation that Stimulus Controller should be attached to the element with the smallest scope possible for better performance.

I know it sounds like trying to optimize an already not expensive script but what I would like to understand is when to use Stimulus and how to use it efficiently, because this situation will arise later with heavier script. My idea is that in this kind of situation Stimulus should not be used and I should use a simple eventListener in a classic js file, but maybe I am wrong.

Hey :wave: I think you might be able to accomplish this with CSS scroll-driven animations (the best solution is no javascript, right? :slight_smile: ):

Browser support is not yet ubiquitous for that, so depending on your requirements you may need to go the route of Stimulus.

Have you looked into the data-turbo-permanent attribute? I think that might be just the thing you’re looking for to address the problem you’re having: Turbo Handbook

1 Like

Thanks mgodwin, indeed with data-turbo-permanent I may preserve event listeners as permanent elements are transferred between pages.
Scroll driven animations look great but support is a real issue.