Jump to content


Horizontal Scroll + Elementor

Recommended Posts

Hi guys,


So, I've done an Horizontal Scroll on Elementor but I'm getting really frustrated with horizontal scroll (anchors) because nothing really works.. Can you please advise what I'm doing wrong?

This is the Elementor site.


I've tried the bellow code:

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/ScrollToPlugin.min.js"></script>

let sections = gsap.utils.toArray("section"),
    nav = gsap.utils.toArray(".menu-item a"),
    getMaxWidth = () => sections.reduce((val, section) => val + section.offsetWidth, 0),
    maxWidth = getMaxWidth(),
    scrollSpeed = 4,
    lastScrollTween = 0,
    tl = gsap.timeline();

tl.to(sections, {
  x: () => window.innerWidth - maxWidth,
  duration: 1,
  ease: "none"

  animation: tl,
  trigger: ".elementor-section-wrap",
  pin: true,
  scrub: 1,
  snap: {snapTo: directionalSnap(tl), duration: 0.5},
  end: () => "+=" + maxWidth / scrollSpeed,
  invalidateOnRefresh: true

function init() {
  gsap.set(sections, {x: 0});
  maxWidth = getMaxWidth();
  let position = 0,
      distance = maxWidth - window.innerWidth;
  // add a label for each section to the timeline (for "labelsDirectional" functionality):
  tl.add("label0", 0);
  sections.forEach((section, i) => {
    let progress = position;
    position += section.offsetWidth / distance;
    tl.add("label" + (i+1), position);
    nav[i].onclick = () => { // link clicks should trigger a scroll tween to the appropriate spot
      snapProgress = progress; // save the current progress so that if we can return it in the directionalSnap() when called right after the scrollTo tween is done (because ScrollTrigger would normally look at the velocity and snap, causing it to overshoot to the next section)
      lastScrollTween = Date.now(); // for checking in the directionalSnap() if there was a recent scrollTo that finished, in which case we'd skip the snapping (well, return the current snapProgress)
      gsap.to(window, {scrollTo: maxWidth / scrollSpeed * progress, duration: 1, overwrite: "auto"});

ScrollTrigger.addEventListener("refreshInit", init); // on resize, things must be recalculated

// a helper function for doing "labelsDirectional" snapping, but we can't use that directly since we're doing some special things with scrollTo tweens, and we need a way to skip the snap if a scrollTo recently finished (otherwise it'll overshoot to the next section)
function directionalSnap(timeline) {
		return (value, st) => {
      if (Date.now() - lastScrollTween < 1650) { // recently finished doing a tweened scroll (clicked link), so don't do any snapping.
        return snapProgress;
      let a = [],
        labels = timeline.labels,
        duration = timeline.duration(),
        p, i;
      for (p in labels) {
        a.push(labels[p] / duration);
			a.sort((a, b) => a - b);
			if (st.direction > 0) {
				for (i = 0; i < a.length; i++) {
					if (a[i] >= value) {
						return a[i];
				return a.pop();
			} else {
				i = a.length;
				while (i--) {
					if (a[i] <= value) {
						return a[i];
			return a[0];

With this CSS (working fine on scroll):

@media only screen and (min-width:768px){
.elementor-inner .elementor-section-wrap{
    display: inline-flex;


    width: 100px;
    width: 100vw;

    overflow-y: hidden;
    overscroll-behavior-y: none;

.elementor-inner {
  width: 100vh;
  height: 100vw;
  overflow-x: hidden;
  overflow-y: scroll;
  transform: rotate(-90deg) translateX(-100vh);
  transform-origin: top left;
  position: absolute;
  scrollbar-width: none;
  -ms-overflow-style: none;

.elementor-inner .elementor-section-wrap {
  transform: rotate(90deg) translateY(-100vh);
  transform-origin: top left;
  display: flex;
  flex-direction: row;
  width: 600vw;

/*section {
  width: 100vw;
  height: 100vh;

 ::-webkit-scrollbar {
  display: none

@media only screen and (max-width:768px){
    .elementor-inner .elementor-section-wrap{
      display: block;

Thank you

Link to comment
Share on other sites

Hi FilipeOS,


Your link isn't working, but even if it was, there's not a lot we can really advise on without a minimal demo. Posting code snippets is almost meaningless for something that is UI based. Thanks!

Link to comment
Share on other sites

Yeah, we really need a minimal demo to offer any real help. 


I did notice a couple of things when I glanced at your code:

  1. You're using stale values rather than dynamic ones for the width, so I'd bet things aren't working quite right on resize. 
    // BAD
    x: () => window.innerWidth - maxWidth,
    gsap.to(window, {scrollTo: maxWidth / scrollSpeed * progress...})
    // GOOD
    x: () => window.innerWidth - getMaxWidth(),
    gsap.to(window, {scrollTo: getMaxWidth() / scrollSpeed * progress...})


  2. You forgot to register ScrollToPlugin

If you still need help, please provide a minimal demo

Link to comment
Share on other sites

Hi, sorry, for some reason the link was gone.


This is the link


I've applied the 1. The 2 is the code I have on topright? gsap.registerPlugin(ScrollTrigger);

Link to comment
Share on other sites

5 hours ago, FilipeOS said:

Hi, sorry, for some reason the link was gone.


This is the link

That link still isn’t working. Please provide a clear minimal demo in CodePen or CodeSandbox if you still need help.


5 hours ago, FilipeOS said:

The 2 is the code I have on topright? gsap.registerPlugin(ScrollTrigger);

No. ScrollToPlugin isn’t the same as ScrollTrigger. 

gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);


Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.