Jump to content
Search Community

Marquee Text Slider with ScrollTrigger

redfawx test
Moderator Tag

Recommended Posts

Hi Everyone, 

 

This may be a bit complicated to get a view of everything since I'm working in nuxt. Long story short I have a basic effect of what I want to accomplish but, it's not seamless.  My overall goal is to create a single marquee strip that will start to animate left to right. Easy right? Well to make it more interactive I want it to accelerate faster from left to right in the user is scrolling down. Again easy? well if the user scrolls up I want it to now stop where it is and start scrolling right to left while also accelerating when the user is scrolling. To currently achieve this I'm having basic marquess scrolling from left to right but containing it in a larger container that is being pushed left to right with the use of Scroll Trigger. So far the container essentially works and it's fine but my marquees are not seemless since there is the use of change of direction. 

 

A visual example can be found here: https://suturacreative.com/ if you scroll down a bit. 

 

I think essentially, I'm doing what's correct by duplicating left to right but do I have to create two duplicates at this point and have copies on either side or something?

 

attached below is my basic logic for one marquee:

 

<template>
  <div v-observe-visibility="visibilityChanged" class="font-hel">
    <div
      class="my-auto mx-auto overflow-x-hidden text-2xl md:text-4xl lg:text-5xl xl:text-6xl font-medium"
    >
      <div
        :class="'marquee-container-' + marqueeText"
        style="width: 200vw; margin-left: -100vw;"
      >
        <div class="hero-marquee">
          <div class="marquee">
            <span :class="'marquee-text-' + marqueeText" class="clipped-text">
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
            </span>
            <span :class="'marquee-text-' + marqueeText" class="clipped-text">
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
              <span class="text-yellow-500">{{ copy }} </span>
              <span class="text-white">{{ copy }}</span>
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { TimelineLite, Linear, TweenLite } from 'gsap/dist/gsap'

import NuxtSSRScreenSize from 'nuxt-ssr-screen-size'

export default {
  components: {
    // Service,
    // globe
  },
  mixins: [NuxtSSRScreenSize.NuxtSSRScreenSizeMixin],
  props: {
    copy: {
      type: String,
      required: true
    },
    marqueeText: {
      type: String,
      required: true
    },
    rate: {
      type: Number,
      required: true
    },
    offSet: {
      type: Number,
      required: true
    }
  },
  data() {
    return {
      isHidden: true,
      isVisible: true,
      animated: false,
      animation: new TimelineLite(),
      line1: new TimelineLite(),
      scrollDirection: 1,
      distance: null,
      style: null,
      marginRight: null,
      totalDistance: null,
      time: null,
      container: null
    }
  },
  beforeDestroy() {
    // window.removeEventListener('scroll', this.onScroll)
  },
  mounted() {
    this.$nextTick(function() {
      let lastScrollTop = 0
      const thisObj = this

      // element should be replaced with the actual target element on which you have applied scroll, use window in case of no target element.
      addEventListener(
        'scroll',
        function() {
          // or window.addEventListener("scroll"....
          const st = window.pageYOffset || document.documentElement.scrollTop
          if (st > lastScrollTop) {
            // downscroll code
            TweenLite.to(thisObj.container, thisObj.time, {
              repeat: -1,
              x: '-=' + thisObj.totalDistance,
              ease: Linear.easeNone
            })
          } else {
            // upscroll code
            TweenLite.to(thisObj.container, thisObj.time, {
              repeat: -1,
              x: '+=' + thisObj.totalDistance,
              ease: Linear.easeNone
            })
          }
          lastScrollTop = st <= 0 ? 0 : st // For Mobile or negative scrolling
        },
        false
      )

      const marquee = document.querySelectorAll(
        '.marquee-text-' + thisObj.marqueeText
      )
      marquee.forEach((el) => {
        // set a default rate, the higher the value, the faster it is
        // thisObj.rate = 30
        // get the width of the element
        thisObj.distance = el.clientWidth
        // get the margin-right of the element
        thisObj.style = window.getComputedStyle(el)
        thisObj.marginRight = parseInt(thisObj.style.marginRight) || 0
        // get the total width of the element
        thisObj.totalDistance = thisObj.distance + thisObj.marginRight
        // get the duration of the animation
        // for a better explanation, see the quoted codepen in the first comment
        thisObj.time = thisObj.totalDistance / thisObj.rate
        // get the parent of the element
        thisObj.container = el.parentElement

        this.animation.set(thisObj.container, {
          x: '-' + thisObj.totalDistance / thisObj.offSet,
          ease: Linear.easeNone
        })
      })

      setTimeout(function() {
        thisObj.animation.from(
          '.marquee-container-' + thisObj.marqueeText,
          {
            scrollTrigger: {
              trigger: '.marquee-container-' + thisObj.marqueeText,
              start: 'top center',
              scrub: 2
            },
            x: '+=200',
            duration: 0.25
          },
          0
        )
      }, 1500)

      this.line1 = new TimelineLite({
        force3D: true,
        repeat: -1,
        paused: false
      })
    })
  },
  methods: {
    visibilityChanged(isVisible, entry) {},
    getPosition(element) {
      let xPosition = 0
      let yPosition = 0

      while (element) {
        xPosition +=
          element.offsetLeft - element.scrollLeft + element.clientLeft
        yPosition += element.offsetTop - element.scrollTop + element.clientTop
        element = element.offsetParent
      }

      return { x: xPosition, y: yPosition }
    }
  }
}
</script>
<style scoped>
.hero-marquee {
  overflow: hidden;
  white-space: nowrap;
}

.marquee {
  font-size: 0;
}

.clipped-text {
  display: inline-block;
  font-size: 100px;
  margin-right: 24px;
}

@media only screen and (max-width: 600px) {
  .marquee {
    font-size: 0;
  }

  .clipped-text {
    display: inline-block;
    font-size: 33px;
    margin-right: 15px;
  }
}
</style>

 

Link to comment
Share on other sites

@mikel Thank you so much for all the help and I've translated the above codepen into my es6 environment. One thing I'm having trouble with is when switching directions it's starting the animation from the beginning and not keeping the marquee at the current position its already animated to. Is there a simple way of doing this? Thank you again for all your help.

Below is my current version of the marquee component:

 

<template>
  <div class="font-hel">
    <div id="no01" ref="marquee" class="wrapper">
      <div class="boxes">
        <div class="box">GreenSock</div>
        <div class="box">Nice Tool</div>
        <div class="box">GreenSock</div>
        <div class="box">Animate</div>
        <div class="box">Fast &amp; easy</div>
        <div class="box">GreenSock</div>
        <div class="box">The best</div>
      </div>
    </div>
  </div>
</template>
<script>
import { gsap, TimelineLite } from 'gsap/dist/gsap'

import NuxtSSRScreenSize from 'nuxt-ssr-screen-size'

export default {
  mixins: [NuxtSSRScreenSize.NuxtSSRScreenSizeMixin],
  props: {
    copy: {
      type: String,
      required: true
    },
    marqueeText: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      master: null,
      boxWidth: null,
      totalWidth: null,
      no01: null,
      dirFromLeft: null,
      dirFromRight: null,
      mod: null,
      animation: new TimelineLite()
    }
  },
  mounted() {
    this.$nextTick(function() {
      this.boxWidth = 250
      this.totalWidth = this.boxWidth * 7 //  * n of boxes
      this.no01 = this.$refs.marquee.querySelectorAll('.box')
      this.dirFromLeft = '+=' + this.totalWidth
      this.dirFromRight = '-=' + this.totalWidth

      this.mod = gsap.utils.wrap(0, this.totalWidth)

      this.setMarquee(this.no01, 15, this.dirFromLeft)
      const thisObj = this
      thisObj.animateFromLeft()
    })
  },
  methods: {
    setMarquee(which, time, direction) {
      const thisObj = this
      gsap.set(which, {
        x(i) {
          return i * thisObj.boxWidth
        }
      })
    },
    marquee(which, time, direction) {
      const thisObj = this

      thisObj.animation.to(which, {
        x: direction,
        modifiers: {
          x: (x) => thisObj.mod(parseFloat(x)) + 'px'
        },
        duration: time,
        ease: 'none',
        repeat: -1
      })
    },
    animateFromRight() {
      const thisObj = this
      thisObj.marquee(thisObj.no01, 15, thisObj.dirFromRight)
    },
    animateFromLeft() {
      const thisObj = this
      thisObj.marquee(thisObj.no01, 15, thisObj.dirFromLeft)
    }
  }
}
</script>
<style scoped>
.wrapper {
  width: 150%;
  height: 50px;
  background: grey;
  overflow: hidden;
}
.wrapper .box {
  position: absolute;
  width: 250px;
  height: 50px;
  background-color: grey;
  color: black;
  font-size: 40px;
  font-weight: 600;
  line-height: 50px;
  text-align: center;
}
.wrapper .boxes {
  position: relative;
  left: -250px;
}
</style>

 

Link to comment
Share on other sites

This topic can be closed. I've fixed what I was aiming to accomplish with simply changing Timeline to TweenLite. Thank you again all for your help. If someone is trying to find a solution this was the code that ended up working for me (still need to dry it out):

 

<template>
  <div class="w-full overflow-x-hidden">
    <div id="no01" ref="marquee" class="wrapper flex font-hel">
      <div class="boxes my-auto">
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
      </div>
    </div>
  </div>
</template>
<script>
import {
  gsap,
  TweenLite
  // TimelineLite
} from 'gsap/dist/gsap'

import NuxtSSRScreenSize from 'nuxt-ssr-screen-size'

export default {
  mixins: [NuxtSSRScreenSize.NuxtSSRScreenSizeMixin],
  props: {
    copy: {
      type: String,
      required: true
    },
    marqueeText: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      master: null,
      boxWidth: null,
      totalWidth: null,
      no01: null,
      dirFromLeft: null,
      dirFromRight: null,
      mod: null,
      animation: null
    }
  },
  mounted() {
    this.$nextTick(function() {
      this.boxWidth = 250
      this.totalWidth = this.boxWidth * 16 //  * n of boxes
      this.no01 = this.$refs.marquee.querySelectorAll('.box')
      this.dirFromLeft = '+=' + this.totalWidth
      this.dirFromRight = '-=' + this.totalWidth

      this.mod = gsap.utils.wrap(0, this.totalWidth)

      this.setMarquee(this.no01, 15, this.dirFromLeft)
      const thisObj = this
      thisObj.animateFromLeft()
    })
  },
  methods: {
    setMarquee(which, time, direction) {
      const thisObj = this
      gsap.set(which, {
        x(i) {
          return i * thisObj.boxWidth
        }
      })
    },
    marquee(which, time, direction) {
      const thisObj = this

      thisObj.animation = TweenLite.to(which, {
        x: direction,
        modifiers: {
          x: (x) => thisObj.mod(parseFloat(x)) + 'px'
        },
        duration: time,
        ease: 'none',
        repeat: -1
      })
    },
    animateFromRight() {
      const thisObj = this
      thisObj.marquee(thisObj.no01, 15, thisObj.dirFromRight)
    },
    animateFromLeft() {
      const thisObj = this
      thisObj.marquee(thisObj.no01, 15, thisObj.dirFromLeft)
    }
  }
}
</script>
<style scoped>
.wrapper {
  width: 150%;
  height: 150px;
  background: grey;
  overflow: hidden;
}
.wrapper .box {
  position: absolute;
  width: 250px;
  height: 50px;
  /* font-size: 40px; */
  font-weight: 600;
  line-height: 50px;
  text-align: center;
}
.wrapper .boxes {
  position: relative;
  left: -250px;
}
</style>

 

Link to comment
Share on other sites

  • 1 month later...
On 7/6/2020 at 6:45 AM, redfawx said:

This topic can be closed. I've fixed what I was aiming to accomplish with simply changing Timeline to TweenLite. Thank you again all for your help. If someone is trying to find a solution this was the code that ended up working for me (still need to dry it out):

 


<template>
  <div class="w-full overflow-x-hidden">
    <div id="no01" ref="marquee" class="wrapper flex font-hel">
      <div class="boxes my-auto">
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-white">{{ copy }}</div>
        <div class="box text-2xl md:text-6xl text-yellow-500">{{ copy }}</div>
      </div>
    </div>
  </div>
</template>
<script>
import {
  gsap,
  TweenLite
  // TimelineLite
} from 'gsap/dist/gsap'

import NuxtSSRScreenSize from 'nuxt-ssr-screen-size'

export default {
  mixins: [NuxtSSRScreenSize.NuxtSSRScreenSizeMixin],
  props: {
    copy: {
      type: String,
      required: true
    },
    marqueeText: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      master: null,
      boxWidth: null,
      totalWidth: null,
      no01: null,
      dirFromLeft: null,
      dirFromRight: null,
      mod: null,
      animation: null
    }
  },
  mounted() {
    this.$nextTick(function() {
      this.boxWidth = 250
      this.totalWidth = this.boxWidth * 16 //  * n of boxes
      this.no01 = this.$refs.marquee.querySelectorAll('.box')
      this.dirFromLeft = '+=' + this.totalWidth
      this.dirFromRight = '-=' + this.totalWidth

      this.mod = gsap.utils.wrap(0, this.totalWidth)

      this.setMarquee(this.no01, 15, this.dirFromLeft)
      const thisObj = this
      thisObj.animateFromLeft()
    })
  },
  methods: {
    setMarquee(which, time, direction) {
      const thisObj = this
      gsap.set(which, {
        x(i) {
          return i * thisObj.boxWidth
        }
      })
    },
    marquee(which, time, direction) {
      const thisObj = this

      thisObj.animation = TweenLite.to(which, {
        x: direction,
        modifiers: {
          x: (x) => thisObj.mod(parseFloat(x)) + 'px'
        },
        duration: time,
        ease: 'none',
        repeat: -1
      })
    },
    animateFromRight() {
      const thisObj = this
      thisObj.marquee(thisObj.no01, 15, thisObj.dirFromRight)
    },
    animateFromLeft() {
      const thisObj = this
      thisObj.marquee(thisObj.no01, 15, thisObj.dirFromLeft)
    }
  }
}
</script>
<style scoped>
.wrapper {
  width: 150%;
  height: 150px;
  background: grey;
  overflow: hidden;
}
.wrapper .box {
  position: absolute;
  width: 250px;
  height: 50px;
  /* font-size: 40px; */
  font-weight: 600;
  line-height: 50px;
  text-align: center;
}
.wrapper .boxes {
  position: relative;
  left: -250px;
}
</style>

 

Hi @redfawx

 

I want the same way as you mentioned in your first comment.

But i need that in JS not in es6 environment.

 

Can you please help me?

 

Thanks

JC

 

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.
×
×
  • Create New...