Skip to content

Add scroll predicate support for dynamic scroll targets#94

Merged
hirasso merged 8 commits intoswup:mainfrom
ovenum:feat/scroll-predicate
Feb 4, 2026
Merged

Add scroll predicate support for dynamic scroll targets#94
hirasso merged 8 commits intoswup:mainfrom
ovenum:feat/scroll-predicate

Conversation

@ovenum
Copy link
Copy Markdown
Contributor

@ovenum ovenum commented Jan 23, 2026

Description

Extends the fragmentVisit.scroll option with a ScrollPredicate that recieves the Visit object to set the scroll behaviour dynamically.

This can be usefull for routes with query parameters where the scroll should reset based on the presence of query parameters.

{
  from: '/blog',
  to: '/blog/:slug',
  containers: ['#main'],
  scroll: (visit) => {
     // Enable scrolling when searchParams changed
     const fromParams = new URL(visit.from.url).searchParams;
     const toParams = new URL(visit.to.url).searchParams;
     
     // Enable scrolling when params changed. Just for illustration!
     return fromParams.toString() !== toParams.toString();
  }
}

Checks

  • The PR is submitted to the main branch
  • The code was linted before pushing (npm run lint)
  • All tests are passing (npm run test)
  • New or updated tests are included
  • The documentation was updated as required

Additional information

@ovenum ovenum changed the title Feat/scroll predicate Add scroll predicate support for dynamic scroll targets Jan 23, 2026
@hirasso
Copy link
Copy Markdown
Member

hirasso commented Jan 25, 2026

Thanks so much for the PR!

I'm wondering if this might better be implemented in userland? The visit:start hook seems like a good fit: you can access the current fragmentVisit via visit.fragmentVisit in the handler.

That said, I'm curious about your use case. Have you needed this across multiple projects?

it might be worth discussing this in an issue first. What do you think?

@hirasso
Copy link
Copy Markdown
Member

hirasso commented Jan 25, 2026

...I just realized that visit.fragmentVisit is not documented, yet. Based on your example, you could try if this works for you:

const swup = new Swup({
  plugins: [
    new SwupFragmentPlugin({
      rules: [
        {
          from: "/blog",
          to: "/blog/:slug",
          containers: ["#main"],
          name: "blog-detail", // give your rule a name
        },
      ],
    }),
  ],
  hooks: {
    "visit:start": (visit) => {
      modifyVisitScroll(visit);
    },
  },
});

function modifyVisitScroll(visit: Visit) {
  // optional: target only one specific type of fragment visit
  if (visit.fragmentVisit?.name !== "blog-detail") return;

  // Enable scrolling when searchParams changed
  const fromParams = new URL(visit.from.url).searchParams;
  const toParams = new URL(visit.to.url).searchParams;

  // Enable scrolling when params changed. Just for illustration!
  visit.scroll.reset = fromParams.toString() !== toParams.toString();
}

@ovenum
Copy link
Copy Markdown
Contributor Author

ovenum commented Jan 25, 2026

Thanks for looking into this.

That said, I'm curious about your use case. Have you needed this across multiple projects?

Just for one website, but would affect any website where navigation occurs to a URL with query parameters that should have different scroll behaviour based on the presence or absence of query parameters.

Example navigation pattern (where overlay is rendered via the fragments plugin)

/ => `/some/overlay`

# click close link) close overlay, do not reset scroll 
/some/overlay => /  

# click link inside overlay) close overlay, reset scroll position so new content is visible
/some/overlay -> /?filter=swup  

These target urls will be handled by the same FragmentRule definition (it does not account for query params), but should have different scroll behaviour.

While it’s possible to achieve this with hooks, having the scroll behaviour defined at the Rule level organises code a little cleaner. Like this route matching paths, if condition and scroll behaviour are kept in one place, rather then split between rule definition and separate hook handlers.

The Fragments plugin already supports setting the scroll option, having a scroll callback avoids needing conditional logic in the visit hook to check which rule matched before setting scroll behaviour.

The code changes are also quite minimal and mimic what’s already in place for the if predicate, that’s also why a PR was created directly. Which of course i’,m happy to discuss/modify further : )

@hirasso
Copy link
Copy Markdown
Member

hirasso commented Jan 25, 2026

You are right, the change really is minimal. And having all fragment-visit-related config in one place actually is a strong argument. Ready to merge this as soon as the README is updated :)

Comment thread src/inc/functions.ts Outdated
The `visit.scroll` was already accessible via the visit parameter, so the separate `scroll` argument was unnecessary.
@ovenum
Copy link
Copy Markdown
Contributor Author

ovenum commented Feb 3, 2026

@hirasso finished this finally. Looks like the two task runners failed. Could you restart them?
Tests are passing locally.

Copy link
Copy Markdown
Member

@hirasso hirasso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you ❤️

@hirasso hirasso merged commit a43a7b7 into swup:main Feb 4, 2026
2 of 4 checks passed
@hirasso
Copy link
Copy Markdown
Member

hirasso commented Feb 5, 2026

Published in version 1.3.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants