Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some insights on the flickering, jumping, jittering pinned elements bug (+ workaround) #660

Open
DarioSoller opened this issue Mar 7, 2017 · 24 comments

Comments

@DarioSoller
Copy link

First of all, I want to mention that I already used ScrollMagic in several onepagers, and want to say a big thank you for your effort & work, @janpaepke !

Nevertheless me and a few others (#288, #330, #316, #355, #424, #445, #497, #488, #504, #531, #538, #542, #553, #575, #595, #601) stumbled over some unexpected behavior with pinned elements. Some of the issues may be solved, or may have another cause, but I think it's good to bundle all related issues to get an overview of this bug. The example codepen by @DraganLibraFire ( #595 ) was what I experienced as well. So in the following, I just want to share, what I have found out and what I came up with to solve it in my case:

The Bug

The main cause was that the top value of the pinned element was jumping between two different values. Even though one was not scrolling anymore. Responsible event is triggered in line 1928:

.on("progress.internal", function (e) { updatePinState(); })

and within the updatePinState() function the topvalue for the pinned element is calculated. There the _progress variable switches between two different values. I hadn't had the time to further track down, where the _progress variable is updated or what causes the progress.internal event to be fired, without scrolling.

My Workaround

Somehow the described bug only happens for elements that are not at the top of the page. So somehow the scrollTop/offset top values seem to be corrupt (especially when using SVG elements as trigger elements). So what I did, was not using any triggerElement at all, but instead only using the durationand offset scene options and calculating the offset on my own:

  var SM = {
    sceneHeights: [], // has to be initialized with the scene heights
    scenePinLengths: [], // has to be initilized with "0"s
    mainPinEl: "#container",
    smController: new ScrollMagic.controller()
  }

  function _sceneOffset(sceneIndex){
    var total = 0;
    var sceneHeightsForOffset = SM.sceneHeights.slice(0, sceneIndex);
    var scenePinsForOffset = SM.scenePinLengths.slice(0, sceneIndex);

    for (var i = 0; i < sceneHeightsForOffset.length; i++) {
      total += sceneHeightsForOffset[i] + scenePinsForOffset[i];
    }

    return total;
  }

  var pinLength = 600;
  var sceneIndex = 0; // sceneIndex starting at 0
  var newSMScene = new ScrollMagic.Scene({
        duration: pinLength,
        offset: _sceneOffset(sceneIndex)
      }).setPin(SM.mainPinEl, {pushFollowers: false})
        .setTween(new TimelineMax())
        .addTo(SM.smController); // assign the scene to the controller

  SM.scenePinLengths[sceneIndex] = pinLength; // updating the scene pinning length

This solved the jitter while pinning elements bug for me. Hope it helps someone else implementing a quick fix and further I hope it helps @janpaepke gaining more ideas how to maybe tackle this.

@nitrokevin
Copy link

Hi I'm having this issue, are you able to explain how to implement your work around? I'm just getting errors,

I'm not sure what you are meant to do with this part

sceneHeights: [], // has to be initialized with the scene heights
scenePinLengths: [], // has to be initilized with "0"s

@DarioSoller
Copy link
Author

@nitrokevin you just have to init the sceneHeights and scenePinLengths variables ie. on the jQuery ready event like this:

$(document).ready(function ($) {
    var scenes = $('.scene'); // expecting scene wrapper elements with the class 'scene'

    for (var i = 0; i < scenes.length; i++) {
        SM.sceneHeights.push($(scenes[i]).height()); // init scene heights
        SM.scenePinLengths.push(0); // init scene pin duration array
    }
});

Didn't had the time right now to set up a proper codepen, but I hope this still helps you with your errors.

@nitrokevin
Copy link

great thanks, I'll take a look.

I was getting the issue where the top value of the spacer was flicking between two values which caused the wobble in safari only ... just spent hours going through it and noticed that I had used a negative margin-top value on the image I was pinning, as soon as I got rid of that the wobble went away

@scermat
Copy link

scermat commented Dec 29, 2017

I was having the exact same issue, and happy to report that your workaround to remove the triggerElement and work with the duration and offset did the trick.

Thank you for reporting and sharing the insight!

@TylerOlthuizen
Copy link

I implemented your code and it doesn't seem to work , what am I doing wrong? Here's my code , let me know please :)

screen shot 2018-01-03 at 4 17 09 pm

screen shot 2018-01-03 at 4 20 28 pm

@DarioSoller
Copy link
Author

@TylerOlthuizen: I have prepared a quick codepen that demos the workaround. I admit that it maybe was not very clear, that you have to repeat the code around the new ScrollMagic.Scene(...) for every scene of yours. Seems bulky with a lot of code duplication, but keep in mind that you probably end up with individual TweenMax animations for every scene.

@TylerOlthuizen
Copy link

@DarioSoller Thanks for the code pen! Yeah I was hoping I could just loop through all of my image I want to pin and use data attributes to get the x and y values that need to be set. Have you tried making it dynamic and class based with data attributes? I see now that the for loop in the document ready just sets the height of the mainPinEl ? Thanks for your help!

@DarioSoller
Copy link
Author

@TylerOlthuizen : You can easily do something like this, expecting to have i.e. a data-pin-length="400" data attribute on your scene element.

for (var i = 0; i < scenes.length; i++) {
  initPinScene(i, $(scenes[i].data('pin-length')));
}

// Scene
function initPinScene(sceneIndex, pinLength) {
  console.log("Scene Offset for sceneIndex "+ sceneIndex +" is: "+ _sceneOffset(sceneIndex));
  var newSMScene = new ScrollMagic.Scene({
    triggerHook: 0,
    duration: pinLength,
    offset: _sceneOffset(sceneIndex)
  }).setPin(SM.mainPinEl, {pushFollowers: false})
      .addIndicators({name: "pin scene "+sceneIndex})
      .addTo(SM.smController); // assign the scene to the controller

  SM.scenePinLengths[sceneIndex] = pinLength; // updating the scene pinning length
  return newSMScene;
}

Hope that helps!

@TheFlashYoda
Copy link

I fixed this issue for our project in a much simpler way. I added _tfyFirstTop variable to Scene and instantiated at -1. Then I put this code in the updatePinState function after fixedPos is calculated in the 'pinned state' section:

fixedPos[containerInfo.vertical ? "top" : "left"] += scrollDistance;

if (_tfyFirstTop === -1) {
_tfyFirstTop = fixedPos.top;
}

if (fixedPos.top !== _tfyFirstTop) {
fixedPos.top = _tfyFirstTop;
}

_// set new values
_util.css(pinOptions.spacer.firstChild, {

The idea is to only set that value once per scene and keep it locked down. Worked great with minimal code. Hope this helps! And thanks @DarioSoller for your research into what the issue was and where it was happening at. I was able to easily trace the code from there!

@DarioSoller
Copy link
Author

Great news, I hoped there is such a simple fix.
Thanks for sharing!

@Kiefna
Copy link

Kiefna commented Apr 16, 2018

@TheFlashYoda 's solution doesn't work for me, the same corrupt values that cause the jitter in the first place can cause the first value for the scene to be set incorrectly. For the pages I'm building, it seems about 1 in 5 scenes.

@sebastianjung
Copy link

I was about to rip my hairs off, when i realized the jumping of the pinned section on IE was triggered by the addIndicators Plugin of Scrollmagic.

Disabling those might work.

Hope this helps!

@zurnet
Copy link

zurnet commented Aug 18, 2018

I've tried disabling the plugin, same issue. If anyone has any real concrete solutions or perhaps a fix to the library?

@DarioSoller
Copy link
Author

@sebbler1337: I am aware that the addIndicator plugin does cause some extra jitter, but in a lot of pinning cases disabling the plugin, alone will not solve the jitter completly.

@zurnet: Have a look in the comments above. There some workarounds, you could try.

@zurnet
Copy link

zurnet commented Aug 22, 2018

@TheFlashYoda are you able to expand more on this part

I added _tfyFirstTop variable to Scene

are you simply putting a variable inside a function that contains the new scene?

@TheFlashYoda
Copy link

@zurnet in my case, the function updatePinState was causing the jitter by jumping between two values for some reason. My variable captures the very first value that is set for the top and from there on, holds the top to that value, essentially negating what updatePinState does. It's been awhile since I've looked at the code but I'm not sure why top would need to be changed once it is initially set for a horizontally scrolling situation - that should always be the same value, shouldn't it? Why this didn't work for @Kiefna is because the value it first grabbed was wrong - that didn't happen for me and I suppose in that case if I couldn't find a solution I would have added code to hardwire the values that are needed? I am by no means an expert in this module - just shared what worked for me in hopes that it would help others.

@888jacklee
Copy link

@DarioSoller this does help, keep up the good work.

@grayayer
Copy link

grayayer commented Feb 11, 2019

@DarioSoller - In order to test your codepen workaround on iPad in full screen view I had to fork it, because it says "The owner of this Pen needs to verify their email address to enable Full Page View.". That forked version is at https://codepen.io/grayayer/pen/PVeEbP.

It's a shame that one of the fundamental actions of ScrollMagic, pinning an object just doesn't out-of-the-box seem to work on touch devices.

As far as I can tell this jitter is caused by an improper top position value being set and then reset. You can address this with a single css rule that overides the inline scrolMajic Value.

.scrollmagic-pin-spacer{
   top:0 !important
}

@DarioSoller
Copy link
Author

@grayayer sry I was not aware that I haven't verified my email yet. So I have also done that now. Good that the CSS top: 0 !important rule works for you. Much simpler than my approach ;)

@jeroenbraspenning
Copy link

I made an alternative solution, might be suitable for someone.

var FixScrollMagicJumpingPin = (scene, pinnedElement) => {
  let pinned = false
  let maxPos = 0

  // overwrite the top on every update
  // not ideal but does the job.
  scene.on('update', function(event) {
    if (!pinned) return

    let updatedTop = Math.max(maxPos, parseInt(pinnedElement.style.top))
    setTimeout(() => {
      if (!pinned) return
      pinnedElement.style.top = updatedTop + 'px'
    }, 0)
    maxPos = updatedTop
  })

  scene.on('enter', function(event) {
    pinned = true
  })

  scene.on('leave', function(event) {
    pinned = false
  })
}

export default FixScrollMagicJumpingPin

@tmcepa
Copy link

tmcepa commented Mar 19, 2020

I made an alternative solution, might be suitable for someone.

var FixScrollMagicJumpingPin = (scene, pinnedElement) => {
  let pinned = false
  let maxPos = 0

  // overwrite the top on every update
  // not ideal but does the job.
  scene.on('update', function(event) {
    if (!pinned) return

    let updatedTop = Math.max(maxPos, parseInt(pinnedElement.style.top))
    setTimeout(() => {
      if (!pinned) return
      pinnedElement.style.top = updatedTop + 'px'
    }, 0)
    maxPos = updatedTop
  })

  scene.on('enter', function(event) {
    pinned = true
  })

  scene.on('leave', function(event) {
    pinned = false
  })
}

export default FixScrollMagicJumpingPin

how i implement this?

@whartonweb
Copy link

Great solution, @DarioSoller

@RaviGill247
Copy link

Is there any way to implement this workaround in the "react-scrollmagic" version?

@DarioSoller
Copy link
Author

@RaviGill247 I don't know the react-scrollmagic implementation, but I have done some GSAP stuff in React and found it to be playing really nicely together. GSAP recently also released a Scroll Trigger Plugin, which might be a fresh alternative for the unfortunately poorly maintained ScrollMagic lib.

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

No branches or pull requests