Image sequence by scroll portion problems

Please can somebody help me, I’ve added the ScrollMagic to a site I’m working on because I want to have an animation controlled by the user scrolling but I’m having a little bit of trouble with it. I’ve got it all working but if somebody scrolls really quickly the animation scrolls too quickly, ideally I’d like to set the fastest speed it’ll animate at but have no idea how to do that so would be grateful for any help, please.

If it helps the animation can be seen here: http://www.akaillustration.com/indexNEW.php (its the delight clients animation on the right when you scroll down).

Thanks in advance.

I mean, my first impulse is to say “don’t use a scroll animation for that, just have it be an animated gif if that’s what you’re going for.”

Frankly I don’t get why that particular piece is animation-locked to the scrolling…?

Looking at the docs, and a wild stab in the dark, but have you looked at experimenting with the ‘duration’ property?

http://scrollmagic.io/docs/ScrollMagic.Scene.html#duration

It takes a number based on distance, a string e.g. ‘100%’ based on the container or a callback function. The latter might be the way to go as it gives you a bit more control, but I would also be tempted to experiment with the string setting.

e.g.

var scene = new ScrollMagic.Scene({
  triggerElement: '#delightClientsContainer',
  duration: '100%' // <- callback here perhaps
})
  .setTween(tween)
  // .addIndicators() // add indicators (requires plugin)
  .addTo(controller)

  // scene.duration(5000); <- moved up into the object argument above

Could be well off here, but just a thought:)

I’m afraid I did try that but unfortunately it didn’t work. Thanks though

They want the user to be able to control that part of the site

I have been looking at an alternative, I don’t know if it is of interest. m-hutley mentioned gif, but another option that came to mind is a sprite grid.

A bit of experimenting with a logo I made some years ago. There are plug-ins, but I opted to manually arranged the grid sequence in Photoshop — it only took a minute or so.

A demo of the animation using css keyframes
https://codepen.io/rpg2019/pen/oNxVdeG

The next step would be to see if this could then be dynamically driven with scrolling and Javascript

Just a thought:)

Updated test with animate on scroll

Codepen here https://codepen.io/rpg2019/pen/ExKMMBa

CSS

#logo {
  margin: 2rem auto;
  width: 320px;
  height: 160px;
  background: #000 url('gamez-gear-sprite-360.jpg') no-repeat 0 0;
  box-shadow:
    rgba(17, 104, 151, 0.25) -20px 15px 60px,
    rgba(201, 113, 12, 0.15) 5px -5px 20px;
}

JS

document.addEventListener('DOMContentLoaded', function (event) {

  function elementInViewY (element, height = 0) {
    const view = element.getBoundingClientRect()
    return (
      view.top >= -height && view.bottom <= (window.innerHeight || document.documentElement.clientHeight) + height
    )
  }

  /**
   * @param {Function} handler - function for scroll event to call
   * @param {Number} wait - milliseconds between each frame on scroll event
   */
  function throttle (handler, wait = 60) {
    let timer = null

    return function (event) {
      if (timer !== null) return

      timer = setTimeout(() => {
        clearTimeout(timer)
        timer = null

        handler.call(event.target, event)
      }, wait)
    }
  }

  /**
   * @param {Element} target - element with background to animate
   * @param {*} sprite - sprite details e.g. width, height, frameCountX and frameCountY
   */
  function getScrollHandler (target, sprite) {

    const width = sprite.width
    const height = sprite.height
    const xOffset = sprite.width / sprite.frameCountX
    const yOffset = sprite.height / sprite.frameCountY

    // return handler
    return function (event) {

      if (!elementInViewY(target, height)) return // if element is not coming into view return

      const computedStyle = window.getComputedStyle(target)

      // parseInt will convert returned pixels to a number e.g. '10px' -> 10
      const backgroundPosX = (parseInt(computedStyle.backgroundPositionX, 10) - xOffset) % width
      const backgroundPosY = (backgroundPosX !== 0)
        ? parseInt(computedStyle.backgroundPositionY, 10)
        : (parseInt(computedStyle.backgroundPositionY, 10) - yOffset) % height

      target.style.backgroundPositionX = `${backgroundPosX}px`
      target.style.backgroundPositionY = `${backgroundPosY}px`
    }
  }

  window.addEventListener(
    'scroll',
    throttle(
      getScrollHandler(document.querySelector('#logo'), {
        // sprite details
        width: 3200,
        height: 640,
        frameCountX: 10,
        frameCountY: 4
      })
      , 30 /* milliseconds between frame */
    )
  )
})

HTML

<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<div id='logo'></div>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Fugiat modi, culpa soluta quidem eius itaque, possimus ex enim quam, quas laboriosam voluptate at magni. Obcaecati facere placeat incidunt veniam, aliquam consequuntur amet voluptate, eos veritatis magni dolor nam? Laborum ab sed sint, voluptas nesciunt voluptatum eaque autem nulla quos inventore?</p>
<hr>

Thank you so much for helping me with this, thats almost exactly what I’m after but is it possible to change the direction of the scroll/animation so that if you scroll back up the page it goes in reverse?

Thank you again

Surgery on my right hand today, so typed with my left, which was fun

document.addEventListener('DOMContentLoaded', function (event) {

  function elementInViewY (element, height = 0) {
    const view = element.getBoundingClientRect()
    return (
      view.top >= -height && view.bottom <= (window.innerHeight || document.documentElement.clientHeight) + height
    )
  }

  /**
   * @param {Function} handler - function for scroll event to call
   * @param {Number} wait - milliseconds between each frame on scroll event
   */
  function throttle (handler, wait = 60) {
    let timer = null

    return function (event) {
      if (timer !== null) return

      timer = setTimeout(() => {
        clearTimeout(timer)
        timer = null

        handler.call(event.target, event)
      }, wait)
    }
  }

  function preCalcAllFramePositions ({ width, height, frameCountX, frameCountY }) {
    const offsetX = width / frameCountX
    const offsetY = height / frameCountY
    const length = frameCountX * frameCountY
    const positions = []

    for (let i = 0, x = 0, y = 0; i < length; i++) {
      positions[i] = { x, y }
      x = (x - offsetX) % width
      if (x === 0) y = (y - offsetY) % height
    }

    return positions
  }

  class Sprite {
    constructor (spriteProperties) {
      this.frames = preCalcAllFramePositions(spriteProperties)
      this.size = this.frames.length
      this.frameWidth = spriteProperties.width / spriteProperties.frameCountX
      this.frameHeight = spriteProperties.height / spriteProperties.frameCountY
      this.currFrame = 0
    }

    get nextFrame () {
      this.currFrame = (this.currFrame + 1) % this.size
      return this.frames[this.currFrame]
    }

    get prevFrame () {
      this.currFrame = (this.currFrame === 0) ? this.size - 1 : this.currFrame - 1
      return this.frames[this.currFrame]
    }
  }

  /**
   * @param {Element} target - element with background to animate
   * @param {*} sprite - sprite details e.g. width, height, frameCountX and frameCountY
   */
  function getScrollHandler (target, spriteProperties) {
    const win = window
    const sprite = new Sprite(spriteProperties)
    let prevScroll = win.scrollY

    // return handler
    return function (event) {
      const currScroll = win.scrollY
      const frame = (currScroll > prevScroll) ? sprite.nextFrame : sprite.prevFrame
      prevScroll = currScroll

      if (!elementInViewY(target, sprite.frameHeight)) return // if element is not coming into view return

      window.requestAnimationFrame(() => {
        target.style.backgroundPositionX = `${frame.x}px`
        target.style.backgroundPositionY = `${frame.y}px`
      })
    }
  }

  window.addEventListener(
    'scroll',
    throttle(
      getScrollHandler(document.querySelector('#logo'), {
        width: 3200,
        height: 640,
        frameCountX: 10,
        frameCountY: 4
      })
      , 30
    )
  )
})

It’s a bit of a mish-mash, but it seems to work. I have updated the previous codepen

Sorry but I dont get it, you can be more specific

As mentioned I have recently come out of surgery, so I am typing one handed and feeling rough as rats. You will have to forgive the lack of details.

It is not the most elegant piece of code, and I may well do a bit of refactoring.

In short I pre-calculate the coordinates for each frame going in one direction and stick that into an array.

This makes it a bit easier then for changing directions. Going back I select the previous index and forwards the next index. It save calculating coordinates on the fly. It also saves having to get the computed background position each time.

If you put console.dir(sprite) under const sprite = new Sprite(spriteProperties) it should give you a clearer picture.

As for checking direction I check the current scrollY and see if is larger than or smaller than the previous scrollY

Hope that helps

A bit of a cleanup, dropping the out of place class for a factory function

document.addEventListener('DOMContentLoaded', function (event) {

  function elementInViewY (element, height = 0) {
    const view = element.getBoundingClientRect()
    return (
      view.top >= -height && view.bottom <= (window.innerHeight || document.documentElement.clientHeight) + height
    )
  }

  /**
   * Throttles each event call to handler e.g scroll event
   * @param {Function} event handler
   * @param {Number} wait - milliseconds between each event call
   */
  function throttle (handler, wait = 60) {
    let timer = null

    return function (event) {
      if (timer !== null) return

      timer = setTimeout(() => {
        clearTimeout(timer)
        timer = null

        handler.call(event.target, event)
      }, wait)
    }
  }

  /**
   * Pre calculate each frame's x and y coordinates
   * @param {Object}
   * @returns {Array} All x and y coordinates in sequence
   */
  function preCalcAllFramePositions ({
    width,
    height,
    offsetX,
    offsetY,
    frameCount
  }) {
    const positions = []

    for (let i = 0, x = 0, y = 0; i < frameCount; i++) {
      positions[i] = { x, y }
      x = (x - offsetX) % width
      if (x === 0) y = (y - offsetY) % height
    }
    return positions
  }

  /**
   * Sprite Factory function
   * @param {Object} sprite's basic properties
   * @returns {Object}
   */
  function makeSprite ({ width, height, frameCountX, frameCountY }) {
    let currFrame = 0

    const sprite = {
      width,
      height,
      offsetX: width / frameCountX,
      offsetY: height / frameCountY,
      frameCount: frameCountX * frameCountY,

      get nextFrame () {
        currFrame = (currFrame + 1) % sprite.frameCount
        return sprite.frames[currFrame]
      },

      get prevFrame () {
        currFrame = (currFrame === 0) ? sprite.frameCount - 1 : currFrame - 1
        return sprite.frames[currFrame]
      }
    }

    sprite.frames = preCalcAllFramePositions(sprite)
    return sprite
  }

  /**
   * @param {Element} target - element with background to animate
   * @param {Object} spriteProps - sprite details e.g. width, height, frameCountX and frameCountY
   */
  function getScrollHandler (target, spriteProps) {
    const sprite = makeSprite(spriteProps)
    let prevScroll = window.scrollY

    // return handler
    return function (event) {
      const currScroll = window.scrollY
      const frame = (currScroll > prevScroll) ? sprite.nextFrame : sprite.prevFrame
      prevScroll = currScroll

      if (!elementInViewY(target, sprite.offsetY)) return // if element is not coming into view return

      window.requestAnimationFrame(() => {
        target.style.backgroundPositionX = `${frame.x}px`
        target.style.backgroundPositionY = `${frame.y}px`
      })
    }
  }

  window.addEventListener(
    'scroll',
    throttle(
      getScrollHandler(document.querySelector('#logo'), {
        width: 3200,
        height: 640,
        frameCountX: 10,
        frameCountY: 4
      })
      , 30
    )
  )
})

1 Like

Thank you that’s perfect.

1 Like

Given this is written in modern JS, the script will fail in older browser like IE11.

I have uploaded a repository which as well as the modern JS (scroll.js) includes a transpiled and bundled version (scrollIE.bundle.js) that does work in IE11.

The script has been amended so usage is as follows

<!-- type='module' for modern browsers -->
<script src='./public/js/scroll.js' type='module'></script>
<!-- fallback for older browsers -->
<script src='./public/js/scrollIE.bundle.js' nomodule></script>
<script>
  document.addEventListener('DOMContentLoaded', function(event) {
    // no adding eventListener here
    spritePlayOnScroll(
      document.querySelector('#logo'),
      {
        width: 3200,
        height: 640,
        frameCountX: 10,
        frameCountY: 4
      },
      30 // milliseconds between frames
    )
  })
</script>

You can see the repository here sprite-anim-on-scroll

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.