Scrollmagic: CSS percentage-based layout isn't updating pinned elements' dimensions on window resize

Created on 18 Aug 2015  路  12Comments  路  Source: janpaepke/ScrollMagic

Hello Jan,

I'm trying a fairly simple layout with two ScrollMagic scenes. I've got the elements pinning and scrolling just as I need them to - this part was ok, the library's working really well :)

I've got an example here: http://codepen.io/anon/pen/doEgzp

However the layout falls apart when the screen is resized - the pinned elements seem to retain the dimensions they were given when the page initially loaded.

Looking through the old issues in this repo lead me to https://github.com/janpaepke/ScrollMagic/issues/38 where you suggest controlling layout with CSS rather than recalculating with JS. I think I've done this as much as I can since my layout uses only relative and absolute positioning, with percentage left/right values or vh height units.

I've tried explicitly updating the scenes' duration and offset values on resize, I've tried destroying and re-creating the scenes, I've even tried scene.refresh(); and scene.update(true) just in case.

Please could you suggest anything I might have missed?

Many thanks,

Gareth

Most helpful comment

Thank you for this! This really got me out of some :hankey:

I modified your code a little, @4foot30.

  1. To remove jQuery
  2. Debounce resize
  3. Add scenes to reusable array
var controller = new window.ScrollMagic.Controller();

var scenes = [];
var activeScenes = [];

// push a couple of scenes to our array (below are just examples)
// examples below use measurements that would need to be updated on resize
scenes.push( function() {
  return new window.ScrollMagic.Scene({
    triggerElement: '.schedule',
    triggerHook: 'onEnter',
    duration: function() {
      return document.querySelector('.schedule').offsetHeight / 4;
    }
  })
  .setTween('.accommodation, .schedule, .registry', {
    backgroundColor: '#BBD5E7',
    ease: window.Linear.easeNone
  })
  .addTo(controller);
});

scenes.push( function() {
  // example of iPad > only scene using basic width condition)
  var scene = null;
  if (window.innerWidth >= 768) {
    scene = new window.ScrollMagic.Scene({
      triggerElement: '.schedule',
      triggerHook: 'onLeave',
      duration: function() {
        var containerHeight = document.querySelector('.schedule').offsetHeight;
        return containerHeight - window.innerHeight;
      }
    })
    .setPin('.schedule__content')
    .addTo(controller);
  }
  return scene;
});

function addScenes(newScenes) {
  // reset active scenes
  activeScenes = [];
  // loop over each scene and add/re-add
  newScenes.forEach(function (newScene, index) {
    if (typeof newScene === 'function') {
      // add the new scene
      var newScene = newScene();
      // push it to our active scenes array
      activeScenes.push(newScene);
    }
  });
}

// debounced re-size event - less destroying and re-adding scenes
var timeoutDuration = 400;
var resizeTimeout = null;
window.addEventListener('resize', function(event) {
  if (resizeTimeout) {
    clearTimeout(resizeTimeout);
  }
  resizeTimeout = setTimeout(function() {
    // loop over each active scene
    activeScenes.forEach(function (scene, index) {
      // make sure scene wasn't null
      if (scene) {
        // destroy active scene
        scene.destroy(true);
      }
    });
    // after we have destroyed old scenes, re-add them
    addScenes(scenes);
  }, timeoutDuration);
});

// add initial scenes
addScenes(scenes);

Hope this helps other people too.

All 12 comments

I've kept at this and I think I've cracked it.

Initially I thought it wouldn't work, but moving scene creation and destruction into their own functions that can be called on screen resize seems to work.

$(window).on('resize', function() {
    scene1.destroy(true);
    scene2.destroy(true);
    scene1 = createScene1();
    scene2 = createScene2();
  });

Here's an alteration of the example from my previous comment, now working on resize: http://codepen.io/anon/pen/NqVZRV

I also had to add a class to any pinned elements, because their widths weren't getting correctly calculated. I used !important to override the inline styles that were applied by the Javascript:

.col.active {
  width: 50% !important;
}

I hope this helps someone - I couldn't get a CSS-only solution to work, because as far as I could tell ScrollMagic controls the layout by wrapping the pinned elements in new divs and then applying hard-coded pixel values for dimensions and positions. But I'm sure this is down to my strange Flexbox-based layout :)

Maybe this will help with the other referenced issues:
https://github.com/janpaepke/ScrollMagic/issues/81
https://github.com/janpaepke/ScrollMagic/issues/101
https://github.com/janpaepke/ScrollMagic/issues/116
https://github.com/janpaepke/ScrollMagic/issues/379

Had the same problem with pinned divs changing height on browser resize, solution worked great!

Thank you for this! This really got me out of some :hankey:

I modified your code a little, @4foot30.

  1. To remove jQuery
  2. Debounce resize
  3. Add scenes to reusable array
var controller = new window.ScrollMagic.Controller();

var scenes = [];
var activeScenes = [];

// push a couple of scenes to our array (below are just examples)
// examples below use measurements that would need to be updated on resize
scenes.push( function() {
  return new window.ScrollMagic.Scene({
    triggerElement: '.schedule',
    triggerHook: 'onEnter',
    duration: function() {
      return document.querySelector('.schedule').offsetHeight / 4;
    }
  })
  .setTween('.accommodation, .schedule, .registry', {
    backgroundColor: '#BBD5E7',
    ease: window.Linear.easeNone
  })
  .addTo(controller);
});

scenes.push( function() {
  // example of iPad > only scene using basic width condition)
  var scene = null;
  if (window.innerWidth >= 768) {
    scene = new window.ScrollMagic.Scene({
      triggerElement: '.schedule',
      triggerHook: 'onLeave',
      duration: function() {
        var containerHeight = document.querySelector('.schedule').offsetHeight;
        return containerHeight - window.innerHeight;
      }
    })
    .setPin('.schedule__content')
    .addTo(controller);
  }
  return scene;
});

function addScenes(newScenes) {
  // reset active scenes
  activeScenes = [];
  // loop over each scene and add/re-add
  newScenes.forEach(function (newScene, index) {
    if (typeof newScene === 'function') {
      // add the new scene
      var newScene = newScene();
      // push it to our active scenes array
      activeScenes.push(newScene);
    }
  });
}

// debounced re-size event - less destroying and re-adding scenes
var timeoutDuration = 400;
var resizeTimeout = null;
window.addEventListener('resize', function(event) {
  if (resizeTimeout) {
    clearTimeout(resizeTimeout);
  }
  resizeTimeout = setTimeout(function() {
    // loop over each active scene
    activeScenes.forEach(function (scene, index) {
      // make sure scene wasn't null
      if (scene) {
        // destroy active scene
        scene.destroy(true);
      }
    });
    // after we have destroyed old scenes, re-add them
    addScenes(scenes);
  }, timeoutDuration);
});

// add initial scenes
addScenes(scenes);

Hope this helps other people too.

@janpaepke Is this really the desired way to work with ScrollMagic? Looks very hacky to me and sounds like something ScrollMagic should do on it's own. Or did I miss something?

It is incredibly hacky!

But, as per the previous comments, we couldn't find a way to do this otherwise... if it does exist nobody has shown an alternative here yet (and this issue is almost 8 months old)

@lennerd @spacedawwwg That's what I was thinking - this destroy/recreate approach just didn't feel correct when I did it, I think I've missed something fundamental.

I think we all three doing some kind of same mistake here. But I'm not able to see it. 馃檲

See this example: http://janpaepke.github.io/ScrollMagic/examples/basic/simple_pinning.html

When you scroll down to the second element to pin the element with a duration of zero and you resize the window after a while of scrolling the element is repositioned. If you watch the pin spacer in the dev tools you will see, that the elements style properties are changed dynamically. Strange ... 馃槙

those examples use fixed durations

...I wonder what would happen if they wanted to use the variable height of a div, e.g:

var scene = new window.ScrollMagic.Scene({
      triggerElement: '.section',
      triggerHook: 'onLeave',
      duration: function() {
        // This height is variable as the text drops onto more lines when scaled
        var containerHeight = document.querySelector('.section').offsetHeight;
        return containerHeight - window.innerHeight;
      }
    })
    // pin item to top while section is in view
    .setPin('.section__pinning-thing')
    .addTo(controller);

That might have been my trouble - my layout was heavily based on vh units. But as far as I could see, pinning an element gave that element fixed dimensions, which did not seem to update on screen resize.

So I was missing the dynamic duration value you guys are using. I have the same problem without a dynamic duration value.

Any hints for me? http://codepen.io/lennerd/pen/pyaYwa

So I have first added my 'hack' to deal with the viewport re-size

secondly, I have added a 'container' around you pin item to act as the trigger (as once the 'pin' is 'pinned' it will no longer act as a trigger element)

http://codepen.io/spacedawwwg/pen/ONQqzW

NOTE: Change timeoutDuration to 100 if you want a faster re-size (though It would be overkill in my opinion... as it is only us dev that re-size their browser window wildly to see what happens)

Thanks for this.

Was this page helpful?
0 / 5 - 0 ratings