1

I am trying to build a Sticky scrollview in famo.us and I am getting stuck in two places.

1) The body scrolls on top of the header. When I add a Transform.translate(0, 0, 1) to the header before adding it to the _mainScrollview it does nothing.

2) How do I keep the velocity going on the _bodyScrollview when the _mainScrollview gets stuck?

/***
 * A view that scrolls, sticks the header at stickAt,
 * and then allows the body to scroll.
 */
function Sticky(options) {
    View.apply(this, arguments);

    options = options || {};

    this.options = Object.create(Sticky.DEFAULT_OPTIONS);
    for (var i in Sticky.DEFAULT_OPTIONS) {
        if (options[i] !== undefined) this.options[i] = options[i];
    }

    // setup the scrollviews

    this._bodyScrollview = new Scrollview({});
    this._bodyScrollview.sequenceFrom([this.options.body]);

    this._mainScrollview = new Scrollview({});
    this._mainScrollview.sequenceFrom([this.options.header, this._bodyScrollview]);
    this._eventInput.pipe(this._mainScrollview);

    // track the velocities

    this._mainScrollview.sync.on('update', function (data) {
        this._mainVelocity = data.velocity;
    }.bind(this));

    this._bodyScrollview.sync.on('update', function (data) {
        this._bodyVelocity = data.velocity;
    }.bind(this));

    this.add(this._mainScrollview);
}

Sticky.prototype.render = function () {
    // If the main scrollview is scrolled up (velocity < 0)
    // past the stickAt point, stick it and unstick the body scrollview.
    if (this._mainVelocity < 0) {
        if (this._mainScrollview.getPosition() >= this.options.stickAt) {
            this._eventInput.unpipe(this._mainScrollview);
            this._eventInput.pipe(this._bodyScrollview);

            Tools.forcePosition(this._mainScrollview, this.options.stickAt, true);

            this._mainVelocity = 0;
        }
    }

    // If the main scrollview is scrolled down (velocity > 0)
    // past 0, stick it and unstick the main scrollview.
    if (this._bodyVelocity > 0) {
        console.log(this._bodyScrollview.getPosition());
        if (this._bodyScrollview.getPosition() <= 0) {
            this._eventInput.unpipe(this._bodyScrollview);
            this._eventInput.pipe(this._mainScrollview);

            Tools.forcePosition(this._bodyScrollview, 0, true);

            this._bodyVelocity = 0;
        }
    }

    return View.prototype.render.call(this);
};

/**
 * Force a scrollview to a position
 */
Tools.forcePosition = function (scrollview, position, noSpring) {
    if (noSpring) {
        scrollview._springState = 0;
        scrollview._physicsEngine.detachAll();
    }

    scrollview.setVelocity(0);
    scrollview.setPosition(position);    
};
jonperl
  • 871
  • 1
  • 10
  • 22

3 Answers3

1

You can do this by using a famous-flex FlexScrollView, see this demo:

https://github.com/IjzerenHein/famous-flex-chat

Tutorial: https://github.com/IjzerenHein/famous-flex/blob/master/tutorials/FlexScrollView.md

IjzerenHein
  • 2,690
  • 1
  • 17
  • 13
0

1) I suspect your Header size is wrong and overlaps the main scroll content. Check using the inspecter and modify size as necessary. (See the Famo.us Common pitfalls: Why am I not getting this click?)

2) Subscribe to the edgeHit event of the Scroller and manually set velocity (like you do when you're stopping the scroller).

BTW, instead of having this

ScrollView
    HeaderSurface
    ScrollView
        Content

I would layout like this:

 HeaderSurface
 ScrollView
     HeaderSurface
     Content1
     Content2
     Content3

So a single HeaderSurface is referenced twice in the RenderTree.So I would clone the HeaderSurface and if you scroll past a certain point, you show the first (sticky) HeaderSurface. This overlaps the second (scrollviewed) HeaderSurface.

markmarijnissen
  • 5,569
  • 2
  • 28
  • 34
  • 1) works in safari. I am guessing it has to do with the intersecting surfaces bug in Chrome mentioned here https://famo.us/guides/dev/pitfalls.html – jonperl Apr 22 '14 at 16:33
  • Also for future viewers, adding a RenderNode in two trees does not seem to work. – jonperl Apr 23 '14 at 15:24
0

1) This turned out to be a problem with Chrome, it works on safari. The workaround was to create the header after the body.

2) Passing around velocities proved to be difficult and complex -- when I would stick the header the body would bounce because it was inside the same scrollview.

I rebuilt the view to use one scrollview with a filler node representing the header. Then I manually transform the header depending on where the scrollview is.

HeaderSurface
BodyScrollview
    HeaderFiller
    BodySurface

Here is the code:

/***
 * A view that scrolls sticks the header at stickHeaderAt
 * and allows the body to scroll underneath the header.
 */
function Sticky(options) {
    View.apply(this, arguments);

    options = options || {};

    this.options = Object.create(Sticky.DEFAULT_OPTIONS);
    for (var i in Sticky.DEFAULT_OPTIONS) {
        if (options[i] !== undefined) this.options[i] = options[i];
    }

    // Setup the body scrollview.

    // Add a filler node to represent the header.
    // In render we will manually transform the header depending on where the scrollview is.
    var headerFiller = new RenderNode(new Modifier({
        size: this.options.header.getSize()
    }));
    headerFiller.add(new Surface);

    this._bodyScrollview = new Scrollview({});
    this._bodyScrollview.sequenceFrom([headerFiller, this.options.body]);

    this.add(this._bodyScrollview);

    this._eventInput.pipe(this._bodyScrollview);

    // Create a wrapping container surface for the header after
    // the body as a workaround of a bug where chrome does not
    // respect our transform on the header.

    var headerContainer = new ContainerSurface({
        size: this.options.header.getSize(),
        transform: Transform.translate(0, 0, 1)
    });
    headerContainer.add(this.options.header);

    // Setup the header node.

    // Transform the header on top of the body (broken in chrome).
    this._headerModifier = new StateModifier({
        transform: Transform.translate(0, 0, 1)
    });
    var headerNode = new RenderNode(this._headerModifier);
    headerNode.add(headerContainer);

    this.add(headerNode);
}

Sticky.prototype = Object.create(View.prototype);
Sticky.constructor = Sticky;

Sticky.DEFAULT_OPTIONS = {
    header: null,
    body: null,
    stickHeaderAt: 0
};

Sticky.prototype.render = function () {
    // If the body scrollview is on the first page handle sticking / unsticking the header.
    // Otherwise the body is scrolled up enought that the header is already stuck, so ignore it.
    if (this._bodyScrollview._node.getNext()) {
        var scrollviewPosition = this._bodyScrollview.getPosition();

        // Stop the header when the scrollview hits stickHeaderAt.
        if (scrollviewPosition > this.options.stickHeaderAt) {
            this._headerModifier.setTransform(Transform.translate(0, -1 * this.options.stickHeaderAt, 1));
        }
        // When the scrollview is below the stickHeaderAt point
        // move the header with the scrollview.
        else {
            this._headerModifier.setTransform(Transform.translate(0, -1 * scrollviewPosition, 1));
        }
    }

    return View.prototype.render.call(this);
};

/**
 * Reset the header and body to the top.
 */
Sticky.prototype.reset = function () {
    this._headerModifier.setTransform(Transform.translate(0, 0, 1));

    this._bodyScrollview.goToPreviousPage();
    Tools.forcePosition(this._bodyScrollview, 0, true);
};
jonperl
  • 871
  • 1
  • 10
  • 22