cubiq / iscroll

Smooth scrolling for the web
http://iscrolljs.com
MIT License
12.87k stars 3.81k forks source link

resize / beforeRefresh events or dimensions callback #573

Open rodneyrehm opened 10 years ago

rodneyrehm commented 10 years ago

Hey Matteo,

By using plain CSS I can give a list of elements the full width of the page while making them appear in line. Essentially horizontally scrolling elements that each have the dimension of the viewport. iScroll will only make that scrollable when I calculate the effective width of all the scrollElement's children and apply that to scrollElement. This by itself is not a problem. It can be done rather easily:

var scrollWrapper = document.getElementById('scroll-wrapper');
var scrollElement = scrollWrapper.firstElementChild;

function fixScrollElementWidth() {
  // (border-box) width of all children
  var width = [].reduce.call(scrollElement.children, function(sum, element) { 
    return sum + element.offsetWidth; 
  }, 0);
  // add some space so last element appears centered
  // (also fixes last-page identification problem within iScroll)
  width += (scrollWrapper.clientWidth - scrollElement.lastElementChild.offsetWidth) / 2;
  scrollElement.style.width = width + 'px';
}

// and later initialize
fixScrollElementWidth();
var scroller = new IScroll(scrollWrapper, { snap: 'li' });

This looks generic enough to be included in iScroll's snap module. (Caution, Element.children and Element.firstElementChild aren't supported by oldIE).

My real quarrel is with reacting to changing viewports, though. If I register an event handler for refresh, it is run after IScroll-internal handlers have been executed. So right now I have no choice but listen to window.onresize myself:

window.addEventListener('resize', fixScrollElementWidth, true);
fixScrollElementWidth();
var scroller = new IScroll(scrollWrapper, { snap: 'li' });

As you can see I forgot to also listen to orientationchange and I didn't debounce my event handling. It works, but it's an ugly solution that leaves me - the developer - open to making silly mistakes. I would much prefer one of the following solutions:

  1. introduce the event resize that is triggered before the refresh happens. It's good enough for me, but will fail the next guy, cause it's not triggered for a manual call to scroller.refresh(), which I'd have done after adding a new item to the list (i.e. modified the scrolling content).
  2. introduce the event beforeRefresh that is triggered as the very first thing of refresh(). Your events are executed synchronously and thus allow me to fix things externally, every time IScroll updates its stuff internally.
  3. provide a configuration option dimensions that accepts a callback function that allows me to configure calculating width and height:
var scroller = new IScroll(scrollWrapper, {
  dimensions: function(scrollElement) {
    var width = [].reduce.call(scrollElement.children, function(sum, element) { 
      return sum + element.offsetWidth; 
    }, 0);
    // add some space so last element appears centered
    // (also fixes last-page identification problem within iScroll)
    width += (scrollElement.parentElement.clientWidth - scrollElement.lastElementChild.offsetWidth) / 2;
    // return the width, so IScroll can trigger the reflow when it needs to
    return {
      x: width,
      y: scrollElement.offsetHeight
    };
  }
});

I think I would go with option 2 and 3. 2 allows the most flexibility for anything I haven't thought of yet, 3 solves the problem of calculating dimensions.

cubiq commented 10 years ago

this already slipped under my radar. I believe the best solution is to threat the "refresh" (and "init") events as special events and pass them directly as init parameters in the options object.

beforeRefresh wouldn't be a problem.

davidpfahler commented 10 years ago

So this is a planned enhancement @cubiq ?