How to smoothly have a menu bar shrink as the scrollview is pushed up? - famo.us

I'm trying to achieve the effect of the header collapsing when scrolling up seen here: http://youtu.be/QXeFl093_FI?t=50s
I'm quite new to famo.us but here's a jsfiddle with what I have tried so far: http://jsfiddle.net/jamalio/cy07xxs7/
var Engine = require('famous/core/Engine');
var Utility = require('famous/utilities/Utility');
var Surface = require('famous/core/Surface');
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Scrollview = require('famous/views/Scrollview');
var StateModifier = require('famous/modifiers/StateModifier');
var Transform = require('famous/core/Transform');
var mainContext = Engine.createContext();
var headerSize = 150;
var header = new Surface({
content: "hello",
properties: {
backgroundColor: "#ccc",
color: "#000",
textAlign: "center",
lineHeight: "9"
}
})
var scrollView = new Scrollview();
var surfaces = [];
for (var i = 0, l = 30; i < l; i++) {
var surface = new Surface({
content: "test",
size: [undefined, 60],
classes: ["padding"],
properties: {
backgroundColor: "#fff",
color: "#333"
}
})
surface.pipe(scrollView)
surfaces.push(surface);
}
scrollView.sequenceFrom(surfaces);
var scrollModifier = new StateModifier({
size: [undefined,200],
transform: Transform.translate(0,headerSize,0)
});
scrollView.justStarted = true;
var headerModifier = new StateModifier({
size: [undefined,headerSize],
transform: Transform.translate(0,0,1)
});
scrollView.sync.on('start', function(e) {
console.log(e);
scrollView.justStarted = true;
scrollView.startingPoint = e.clientY;
});
scrollView.sync.on('update', function(e) {
if(!scrollView.justStarted &&
e.position < 0
) {
console.log(e);
var currentSize = headerModifier.getSize();
console.log(currentSize);
var changeAmount = headerSize + e.position;
headerModifier.setSize([undefined,changeAmount]);
}
scrollView.justStarted = false;
});
mainContext.add(scrollModifier).add(scrollView);
mainContext.add(headerModifier).add(header);
It's tricky because the scrollview.getPosition() doesn't return the position of the entire scrollview but rather the existing nodes. In the backend the scrollview removes nodes from the view and so it doesn't account for them.
In order to get around this, I'm getting the "position" on the "update" event and on update I'm calculating the difference from the original size of the header. The problem is when you drag the scrollview past it's boundaries, it stops sending events and it usually skips the last few, so instead of the position going back to 0, it stops at -4 or so, which messes with the whole logic.
Does anyone have a better way to go about this?

I would do a full length scrollview and a draggable that pulls down from the top as you scroll. Just tap into the update to know the direction and how far it's moved and use the scrollview._scroller.on('edgeHit') to bring the draggable down when you hit the top. That doesn't really shrink the header as much as move it off screen but whatever. If you want the header to actually shrink you could use a modifier to scale it or just change it's size. If you want to keep the logic you have you could use the edgeHit events to reset the number to 0 or it's maximum but in general using get position on scrollview is a horrible experience that doesn't even usually give you what you want it to.

Related

z-translate is ignored by GridLayout view

I want to use GridLayout in ScrollView context and be able to translate surfaces of GridLayout in Z-direction. The problem is that for some reason Z-translate is ignored in GridLayout. Seems like GridLayout has zero perspective because translate in x and y directions just work. How to fix that?
Here is example where z-translate just does nothing :(
define(function(require, exports, module) {
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var Scrollview = require("famous/views/Scrollview");
var View = require("famous/core/View");
var ContainerSurface = require("famous/surfaces/ContainerSurface");
var MouseSync = require("famous/inputs/MouseSync");
var TouchSync = require("famous/inputs/TouchSync");
var GenericSync = require("famous/inputs/GenericSync");
GenericSync.register({ 'mouse': MouseSync, 'touch': TouchSync });
var syncX = new GenericSync(['mouse', 'touch'], { direction: 0 });
var GridLayout = require("famous/views/GridLayout");
var StateModifier = require('famous/modifiers/StateModifier');
var Transform = require("famous/core/Transform");
var mainContext = Engine.createContext();
mainContext.setPerspective(1000);
var grid = new GridLayout({
dimensions: [6, 2]
});
var scrollview = new Scrollview({direction: 0});
syncX.pipe(scrollview);
var surfaces = [];
var mods = [];
var views = [];
for (var i = 0, temp; i < 12; i++) {
view = new View();
mod = new StateModifier();
temp = new Surface({
content: "Surface: " + (i + 1),
size: [200, 200],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "200px",
textAlign: "center"
}
});
temp.pipe(syncX);
surfaces.push(temp);
mods.push(mod);
view.add(mod).add(temp);
views.push(view);
}
grid.sequenceFrom(views);
var container = new ContainerSurface({size:[1200,400]});
container.context.setPerspective(1000); ///<----------
container.add(grid);
scrollview.sequenceFrom([container]);
Engine.on('click', function(){
mods[0].setTransform(Transform.translate(2,2,999), {duration:1000});
});
mainContext.add(scrollview);
});
BTW I did test it in Chrome
The GridLayout should not ignore the z-translate as you can see if you run the code below. There are some things missing and extra pieces you do not need in the code you supplied above.
Note: Putting a modifier on the view in the GridLayout the way you are using it may not end up giving you the desired result you want, but the example does show how it works.
Example jsBin
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var Scrollview = require("famous/views/Scrollview");
var View = require("famous/core/View");
var ContainerSurface = require("famous/surfaces/ContainerSurface");
var GridLayout = require("famous/views/GridLayout");
var StateModifier = require('famous/modifiers/StateModifier');
var Transform = require("famous/core/Transform");
var mainContext = Engine.createContext();
mainContext.setPerspective(1000);
var grid = new GridLayout({
dimensions: [6, 2]
});
var scrollview = new Scrollview({direction: 0});
var surfaces = [];
var mods = [];
var views = [];
for (var i = 0; i < 12; i++) {
var view = new View();
var mod = new StateModifier({
size: [200, 200],
transform: Transform.translate(0,0,0.001)
});
var temp = new Surface({
content: "Surface: " + (i + 1),
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "200px",
textAlign: "center"
}
});
surfaces.push(temp);
mods.push(mod);
temp.pipe(view);
view.add(mod).add(temp);
views.push(view);
temp.on('click', translateOut.bind(temp, i));
}
grid.sequenceFrom(views);
var container = new ContainerSurface({size:[1200,400]});
container.add(grid);
grid.pipe(container);
scrollview.sequenceFrom([container]);
container.pipe(scrollview);
function translateOut(index){
if (!this.moved) {
mods[index].setTransform(Transform.translate(0,0,400), {duration:1000});
} else {
mods[index].setTransform(Transform.translate(0, 0,0.001), {duration:1000});
}
this.moved =!this.moved;
}
mainContext.add(scrollview);
This is due to a newly introduced chrome bug. It looks like Chrome tries to fast path some of its rendering when everything is initially at a z translation of 0. If you add a Surface to the scene at a no zero z with an opacity of 0 you can trick Chrome into drawing correctly.
mainContext
.add(new StateModifier({
transform: Transform.translate(0, 0, 100000000),
size: [1, 1],
opacity: 0
})).add(new Surface({
properties: {backgroundColor: 'chromeHack'}
}));
I will edit this post when I make a simpler example and file as a chrome bug.

How to create empty space between the Surfaces in ScrollView in famo.us?

I've created a horizontal ScrollView and added some Surfaces to it. I want to have an empty space between the Surfaces when I scroll (swipe) the view. I've tried setting the margins, padding, borders, etc.. in the properties of the Surfaces and the ScrollView but the surfaces remain connected together no matter what.
Does anyone know how I can achieve this?
This could be done a few ways. The non-famo.us approach would be to add surfaces and use Content HTML to create margins. For the sake of the example, I will assume you want a list of surfaces that just need some margin!
I knew about itemSpacing in SequntialLayout, and was surprised to not find such a thing in ScrollView as well. So to solve the problem, I simply added all my surfaces to a SequentialLayout, and then add that as the sole item in Scrollview.
Also note I added a background surface, to capture the mouse events that happen between the surfaces!
Here is what I did.. Hope it helps!
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var Scrollview = require("famous/views/Scrollview");
var SequentialLayout = require("famous/views/SequentialLayout");
var mainContext = Engine.createContext();
var bg = new Surface({ size:[undefined,undefined] });
var scrollview = new Scrollview();
var scrollSurfaces = [];
scrollview.sequenceFrom(scrollSurfaces);
var sequentialLayout = new SequentialLayout({itemSpacing:20});
var surfaces = [];
sequentialLayout.sequenceFrom(surfaces);
for (var i = 0; i < 40; i++) {
var surface = new Surface({
content: "Surface: " + (i + 1),
size: [undefined, 200],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "200px",
textAlign: "center"
}
});
surface.pipe(scrollview);
surfaces.push(surface);
}
scrollSurfaces.push(sequentialLayout);
bg.pipe(scrollview);
mainContext.add(bg);
mainContext.add(scrollview);
I think using a ContainerSurface is better, use many containsurfaces to contain the surface.You should emulate in Apple iphone 5.In other environment, you should adjust the origin of scrollModifier to fix.
define(function(require, exports, module) {
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var Transform = require("famous/core/Transform");
var View = require('famous/core/View');
var StateModifier = require('famous/modifiers/StateModifier');
var Scrollview = require("famous/views/Scrollview");
var Modifier = require("famous/core/Modifier");
var ContainerSurface = require("famous/surfaces/ContainerSurface");
var mainContext = Engine.createContext();
var scrollview = new Scrollview({
direction: 0,
paginated: 'true',
pageStopSpeed: 2
});
var scrollnodes = [];
scrollview.sequenceFrom(scrollnodes);
for (var i = 0; i < 40; i++) {
var container = new ContainerSurface();
var temp = new Surface({
content: "Surface: " + (i + 1),
size: [window.innerWidth -40, window.innerHeight - 40],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "200px",
textAlign: "center"
}
});
// wrong
// will return temp as scrollnode, it will ignore the container
// var scrollnode = container.add(temp)
// this is right
container.add(temp);
var scrollnode = container;
temp.pipe(scrollview);
scrollnodes.push(scrollnode);
}
var scrollModifier = new StateModifier({
origin: [0.07, 0.04]
});
mainContext.add(scrollModifier).add(scrollview);
});
You could also use the FlexScrollView, which supports a spacing option to achieve this:
var scrollView = new FlexScrollView({
layoutOptions: {
spacing: 10
}
});
https://github.com/IjzerenHein/famous-flex/blob/master/tutorials/FlexScrollView.md
https://github.com/IjzerenHein/famous-flex

Famo.us how to make a scrollview be filled from bottom to top (or right to left)?

Assuming I've a scrollview with direction 1 (vertical). While populating the scrollView, the items do appear and are aligned from top to bottom.
I would like to know if there is a way to reverse this ordering i.e. to make the scrollView be populated from the bottom to the top. A good example of usage is a message list. You want the last message to always appear on the bottom of the list. By default, the message would be aligned on top, which is not convenient.
[EDIT]
I just found we can easily set our own transform according to an offset overriding the outputFrom function callback. For example the following will invert the scroll content:
scrollview.outputFrom(function(offset)
{
return Transform.translate(0, -offset)
});
Did it like you suggested, it works just fine while rotating all by 180˚. Example: http://jsfiddle.net/tamtamchik/LA49a/3/
Famous.loaded(function () {
var Engine = Famous.Core.Engine;
var Surface = Famous.Core.Surface;
var Scrollview = Famous.Views.Scrollview;
var Timer = Famous.Utilities.Timer;
var Transform = Famous.Core.Transform;
var StateModifier = Famous.Modifiers.StateModifier;
var ContainerSurface = Famous.Surfaces.ContainerSurface;
var mainContext = Engine.createContext();
var scrollview = new Scrollview();
var surfaces = [];
var i = 0;
var surfaceHeight = 50;
scrollview.sequenceFrom(surfaces);
Timer.setInterval(function () {
var container = new ContainerSurface({
size: [undefined, surfaceHeight]
});
var temp = new Surface({
content: "Surface: " + (i + 1),
size: [undefined, surfaceHeight],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "50px",
textAlign: "center"
}
});
var surfaceRotate = new StateModifier({
transform: Transform.rotateZ(Math.PI)
});
var surfacePush = new StateModifier({
transform: Transform.translate(window.innerWidth, surfaceHeight)
});
container.add(surfacePush).add(surfaceRotate).add(temp);
container.pipe(scrollview);
temp.pipe(scrollview);
surfaces.unshift(container);
i++;
}, 400);
var rotate = new StateModifier({
transform: Transform.rotateZ(Math.PI)
});
var push = new StateModifier({
transform: Transform.translate(window.innerWidth, window.innerHeight)
});
mainContext.add(push).add(rotate).add(scrollview);
});
Well as famo.us documentation says: "Scrollview will lay out a collection of renderables sequentially in the specified direction", so it will order the surfaces in the order you provide.
My advice is to add the surfaces to a list and then just add them to the ScrollView in reverse.
What you are looking for is unshift(). You can view the following code run at codefamo.us
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var StateModifier = require('famous/modifiers/StateModifier');
var Transform = require('famous/core/Transform');
var Easing = require('famous/transitions/Easing');
var Scrollview = require('famous/views/Scrollview');
var mainContext = Engine.createContext();
var items = [];
var scroll = new Scrollview();
mainContext.add(scroll);
var counter=0;
scroll.sequenceFrom(items);
function addOne() {
setTimeout(function() {
var surface = new Surface({
size: [100, 100],
content: 'surf: '+counter++,
properties: {
textAlign: 'center',
lineHeight: '20px'
}
});
items.unshift(surface);
if (counter<10)
addOne();
},2000);
}
addOne();

Adding Views to Scrollview in Famo.us

This issue concerns adding Views to Scrollview (rather than surfaces).
When adding a View with a single surface everything seems to work as intended. Each View stacks neatly on top of one another.
However when adding a View with multiple surfaces they do not stack nicely. In this case each View seems to be set to the exact height of the scrollview viewport. So if each View is only 100px tall, and the scrollview viewport is 500px tall, there will be 400px of whitespace between each View when scrolling. Also if the View is taller than the scrollview vp, the Views will overlap.
So the question is: How do you add a View will multiple surfaces to a scrollview and have them stack nicely?
Example function to build the scrollview...
function _createScrollview() {
var scrollview = new Scrollview();
var surfaces = [];
scrollview.sequenceFrom(surfaces);
for (var i = 0, temp; i < 10; i++) {
temp = new TestView();
temp.pipe(scrollview);
surfaces.push(temp);
}
this.add(scrollview);
}
Example of View...
define(function(require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Transform = require('famous/core/Transform');
var StateModifier = require('famous/modifiers/StateModifier');
// constructor
function TestView() {
View.apply(this, arguments);
_createBox_a.call(this);
_createBox_b.call(this);
}
// prototype
TestView.prototype = Object.create(View.prototype);
TestView.prototype.constructor = TestView;
// options
TestView.DEFAULT_OPTIONS = {};
// helper functions
function _createBox_a() {
var surf = new Surface({
size: [undefined, 150],
properties: {
backgroundColor: 'red',
}
});
surf.pipe(this._eventOutput);
this.add(surf);
}
function _createBox_b() {
var surf = new Surface({
size: [undefined, 150],
properties: {
backgroundColor: 'blue'
}
});
var mod = new StateModifier({
transform: Transform.translate(0, 150, 0)
});
surf.pipe(this._eventOutput);
this.add(mod).add(surf);
}
module.exports = TestView;
});
View does not know how large it is supposed to be. If you call temp.getSize() in your example, 'undefined' is returned. You will want to explicitly set the size during initialization.. If you must set the size after initialization, it will be..
view.setOptions({size:someSize});
Here is how _createScrollview could look..
Hope this helps!
function _createScrollview() {
var scrollview = new Scrollview();
var surfaces = [];
scrollview.sequenceFrom(surfaces);
for (var i = 0, temp; i < 10; i++) {
temp = new TestView({size:[undefined,300]});
temp.pipe(scrollview);
surfaces.push(temp);
}
this.add(scrollview);
}

Add surface to scrollview (with animation)

Is it possible to add dynamically a surface to already populated scroll view. On click I want all scroll view elements to move animated down and on top to add a new surface again animated.
Is it a scroll view the right way to do it?
Yes, you can add new surfaces dynamically into a populated Scrollview. Simply add a new surface into the Array that you passed to Scrollview#sequenceFrom.
An example based on the scrollview example:
(Edited to add animation)
define(function(require, exports, module) {
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var Scrollview = require("famous/views/Scrollview");
var Timer = require("famous/utilities/Timer");
var Easing = require("famous/transitions/Easing");
var RenderNode = require("famous/core/RenderNode");
var Modifier = require("famous/core/Modifier");
var Transitionable = require('famous/transitions/Transitionable');
var mainContext = Engine.createContext();
var scrollview = new Scrollview();
var surfaces = [];
scrollview.sequenceFrom(surfaces);
for (var i = 0, temp; i < 40; i++) {
temp = new Surface({
content: "Surface: " + (i + 1),
size: [undefined, 200],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "200px",
textAlign: "center"
}
});
temp.pipe(scrollview);
surfaces.push(temp);
}
// Add new surface after 1 second
Timer.setTimeout(function(){
var modifier = new Modifier({
opacity: 0
});
var node = new RenderNode(modifier);
var heightTarget = 200;
var dynamicSurface = new Surface({
content: "Dynamic Surface",
size: [undefined, 0],
properties: {
lineHeight: "200px",
textAlign: "center",
backgroundColor: "#80e0c0"
}
});
node.add(dynamicSurface);
dynamicSurface.pipe(scrollview);
surfaces.unshift(node);
var opacity = new Transitionable(0);
var sizeY = new Transitionable(0);
// Performance implication - both of these functions execute on every tick.
// They could be deleted after transition is complete if they were non-trivial
// (allowing prototype function to resume)
dynamicSurface.getSize = function() {
var height = sizeY.get();
return [undefined, height];
};
modifier.opacityFrom(function(){
return opacity.get();
});
// Animate the height of the added surface, causing the rest of the list to move down
sizeY.set(heightTarget, {duration: 500, curve: Easing.outCirc}, function(){
// Need to set surface size manually after the transition, otherwise the div height is 0
// (even though space was made for it)
dynamicSurface.setSize([undefined, heightTarget]);
// Fade in the item's content
opacity.set(1, {duration: 500});
});
}, 1000);
mainContext.add(scrollview);
});
This method is based on the approach I found in the Taasky Famo.us demo:
https://famo.us/demos/
https://launch-demos.famo.us/Taasky/
It translates items off of the screen and then animates down their height to 0 to simulate closing the gap. The example above animates the height from 0 to 200 to simulate making the gap before insertion.
Take a look at Ijzerenhein's Famous Flex, it has all you need (including great tutorials and documentation): https://github.com/IjzerenHein/famous-flex