I have two surfaces. One is draggable and one is 'full screen' (size: [undefined, undefined]). I'd like to drag the first surface (yellow in my example) right and have the second (green)surface appear. When I click on the green surface, I'd like show the first surface again back in the original starting point (center of screen).
I'd also like the green surface to be non-draggable.
I'm a famo.us novice, and any help is GREATLY appreciated!
fiddle here: http://jsfiddle.net/cjs123456/vfzy4j51/
// the position state
var position = [0, 0];
// create a Sync to listen to mouse events
var sync = new MouseSync();
var renderController = new RenderController();
var mySurface = new Surface({
size: [350, 200],
content: 'drag me right more than 100px to see other surface',
properties: {
backgroundColor: "hsl(" + (5 * 360 / 40) + ", 100%, 50%)",
lineHeight: '200px',
textAlign: 'center',
cursor: 'pointer'
}
});
// Surface provides events that the sync listens to
mySurface.pipe(sync);
// Syncs have `start`, `update` and `end` events. On `update` we increment the position state of the surface based
// on the change in x- and y- displacements
sync.on('update', function(data){
position[0] += data.delta[0];
position[1] += data.delta[1];
console.log(data.position[0]);
if (data.position[0] > 100) {
console.log("FULL");
renderController.show(fullSurface);
}
});
// this modifier reads from the position state to create a translation Transform that is applied to the surface
var positionModifier = new Modifier({
transform : function(){
return Transform.translate(position[0], position[1], 0);
}
});
// a modifier that centers the surface
var centerModifier = new Modifier({
origin : [0.5, 0.5],
align: [0.5, 0.5]
});
var fullSurface = new Surface({
size: [undefined, undefined],
content: 'Click me to show other surface',
properties: {
backgroundColor: "hsl(" + (9 * 360 / 40) + ", 100%, 50%)",
lineHeight: '400px',
textAlign: 'center'
}
});
fullSurface.on("click", function() {
renderController.show(mySurface);
});
renderController.show(mySurface);
var mainContext = Engine.createContext();
var node = mainContext.add(centerModifier).add(positionModifier);
node.add(renderController);
});
There are more than one way to solve this issue.
Control the dragging of the render controller
Create separate render controller and control views
Because your use case here is simple enough, I will show the creating of a separate render controller
Here is the jsFiddle Example of the Code
Create a background render controller
var renderController = new RenderController();
var backRenderController = new RenderController();
Change the node to be the center modifier. Add our draggable to the node. Add the background controller to the node.
var node = mainContext.add(centerModifier)
node.add(positionModifier).add(renderController);
node.add(backRenderController);
Control the draggable render contoller view (Line 42)
renderController.hide();
backRenderController.show(fullSurface);
Clicking on the background will reset the position of the draggable back to the origin, hide the background and show the draggable again.
// Set draggable position back to the origin
position = [0, 0];
// Hide the back render element
backRenderController.hide();
// Show the draggable
renderController.show(mySurface);
Full code:
// the position state
var position = [0, 0];
// create a Sync to listen to mouse events
var sync = new MouseSync();
var renderController = new RenderController();
var backRenderController = new RenderController();
var mySurface = new Surface({
size: [350, 200],
content: 'drag me right more than 100px to see other surface',
properties: {
backgroundColor: "hsl(" + (5 * 360 / 40) + ", 100%, 50%)",
lineHeight: '200px',
textAlign: 'center',
cursor: 'pointer'
}
});
// Surface provides events that the sync listens to
mySurface.pipe(sync);
// Syncs have `start`, `update` and `end` events. On `update` we increment the position state of the surface based
// on the change in x- and y- displacements
sync.on('update', function (data) {
position[0] += data.delta[0];
position[1] += data.delta[1];
console.log(data.position[0]);
if (data.position[0] > 100) {
console.log("FULL");
renderController.hide();
backRenderController.show(fullSurface);
}
//else {
// mySurface.setPosition([0,0,0], {
// curve: Easing.outBack,
// duration: 300
// });
//}
});
// this modifier reads from the position state to create a translation Transform that is applied to the surface
var positionModifier = new Modifier({
transform: function () {
return Transform.translate(position[0], position[1], 0);
}
});
// a modifier that centers the surface
var centerModifier = new Modifier({
origin: [0.5, 0.5],
align: [0.5, 0.5]
});
var fullSurface = new Surface({
size: [undefined, undefined],
content: 'Click me to show other surface',
properties: {
backgroundColor: "hsl(" + (9 * 360 / 40) + ", 100%, 50%)",
lineHeight: '400px',
textAlign: 'center'
}
});
fullSurface.on("click", function () {
// Set draggable position back to the origin
position = [0, 0];
// Hide the back render element
backRenderController.hide();
// Show the draggable
renderController.show(mySurface);
});
//var node = mainContext.add(myModifier);
//node.add(draggable).add(surface);
renderController.show(mySurface);
var mainContext = Engine.createContext();
//mainContext.add(myModifier).add(draggable).add(renderController);
var node = mainContext.add(centerModifier)
node.add(positionModifier).add(renderController);
node.add(backRenderController);
Related
My goal is to mimic Z-translation in perspective mode by using multiple modifiers. I can not use just z-translation of a surface because a text of translated surface became blurred (at least at Chrome but also on another browsers). The idea of using concurrent modifiers is explained in my blog: https://ozinchenko.wordpress.com/2015/02/04/how-to-avoid-blurring-of-the-text-in-famo-us-during-transition-in-z-direction/
As a result I want to have smooth translation in Z direction surface with a smooth text scaling.
the codepen code is here:
http://codepen.io/Qvatra/pen/yyPMyK?editors=001
var Engine = famous.core.Engine;
var Surface = famous.core.Surface;
var ImageSurface = famous.surfaces.ImageSurface;
var ContainerSurface = famous.surfaces.ContainerSurface;
var View = famous.core.View;
var Entity = famous.core.Entity;
var Modifier = famous.core.Modifier;
var StateModifier = famous.modifiers.StateModifier;
var Transform = famous.core.Transform;
var Transitionable = famous.transitions.Transitionable;
var TransitionableTransform = famous.transitions.TransitionableTransform;
var Easing = famous.transitions.Easing;
var Scrollview = famous.views.Scrollview;
var perspective = 1000;
var fontValue = 100; //initially font-size is 100%
var surfSize = [100,100];
var mainContext = Engine.createContext();
mainContext.setPerspective(perspective);
var transitionable = new Transitionable(0);
var mySurface = new Surface({
size: surfSize,
properties: {
backgroundColor: 'red',
textAlign: 'center',
color: 'white',
fontSize: fontValue + '%',
lineHeight: surfSize[1] + 'px'
},
content: 'Click Me'
});
var transitionModifier = new StateModifier({
origin: [.5, .5],
align: [.5, .5],
transform: Transform.translate(0,0,0.01)
});
mainContext.add(transitionModifier).add(mySurface);
function translateZ(dist, transition) {
transitionable.reset(0);
transitionable.set(dist, transition);
function prerender() {
var currentDist = transitionable.get();
//perspective formula: dist = perspective(1 - 1/scaleFactor)
var currentScale = 1 / (1 - currentDist / perspective);
var currentSize = [surfSize[0] * currentScale, surfSize[1] * currentScale];
var currentFontValue = fontValue * currentScale;
//f.e: bring closer => make projection scaleFactor times bigger
var transitionTransform = Transform.translate(0,0, currentDist);
//scaling back to avoid text blurring
var scaleTransform = Transform.scale(1/currentScale, 1/currentScale, 1);
transitionModifier.setTransform(Transform.multiply(transitionTransform, scaleTransform));
mySurface.setSize(currentSize); //resize to get correct projection size
mySurface.setOptions({
properties:{
fontSize: currentFontValue + '%', //resizing font;
lineHeight: currentSize[1] + 'px' //align text;
}
})
if (currentDist === dist) {
Engine.removeListener('prerender', prerender);
}
}
Engine.on('prerender', prerender);
}
Engine.on('click', function() {
translateZ(750, {curve: 'easeOutBounce', duration: 2000});
});
Why do I have the shaking of the image? How to avoid that?
The StateModifier is changing the size of your surface while you are setting the size of the surface. Because you are handling the size of the surface, there is no need to change (set) the StateModifier to scale. I am not sure your method will hold up in all cases, but this answers your question.
Here is a new translateZ function:
function translateZ(dist, transition) {
transitionable.reset(0);
transitionable.set(dist, transition);
function prerender() {
var currentDist = transitionable.get();
//perspective formula: dist = perspective(1 - 1/scaleFactor)
var currentScale = 1 / (1 - currentDist / perspective);
var currentSize = [surfSize[0] * currentScale, surfSize[1] * currentScale];
var currentFontValue = fontValue * currentScale;
mySurface.setSize(currentSize); //resize to get correct projection size
mySurface.setOptions({
properties:{
fontSize: currentFontValue + '%', //resizing font;
lineHeight: currentSize[1] + 'px' //align text;
}
})
if (currentDist === dist) {
Engine.removeListener('prerender', prerender);
}
console.log('trans')
}
Engine.on('prerender', prerender);
}
When using a SequentialLayout in trying to apply StateModifiers to Surface objects that had been added to a layout, it looks like some unexpected behavior happens:
When applying transformations via setTransform on a StateModifier, I expect to see the transformation applied from the origin of the Surface in question.
Instead, the transform is applied from an origin of 0,0 in relation to the parent SequentialLayout
Given the code below, the above behavior seems to make no logical sense (for context, I am working on a sorting algorithms demo, using famo.us):
/* globals define */
define(function(require, exports, module) {
'use strict';
// import dependencies
var Engine = require('famous/core/Engine');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Surface = require('famous/core/Surface');
var StateModifier = require('famous/modifiers/StateModifier');
var SequentialLayout = require('famous/views/SequentialLayout');
// create the main context
var mainContext = Engine.createContext();
// your app here
var surfaces = [];
// Sorter
var Sort = require('sort');
var arr = [100,25,20,15,30,-20,-10,10,0];
var min = Math.min.apply(null, arr);
var base_dims = [ 50, 50 ];
arr.forEach(function(el) {
surfaces.push(new Surface({
content: el,
size: base_dims.map(function(d) { return d + (el - min); }),
properties: {
backgroundColor: 'rgb(240, 238, 233)',
textAlign: 'center',
padding: '5px',
border: '2px solid rgb(210, 208, 203)',
marginTop: '50px',
marginLeft: '50px'
}
}));
});
var sequentialLayout = new SequentialLayout({
direction: 0,
itemSpacing:20
});
sequentialLayout.sequenceFrom(surfaces);
mainContext.add(sequentialLayout);
var swap_modifiers = [
new StateModifier({}), new StateModifier({})
];
Sort.bubble_sort_iterative(arr, function(first_swap_index, second_swap_index) {
swap_modifiers[0].setTransform(
Transform.translate(300, 0, 0),
{ duration : 750, curve: 'linear' }
);
swap_modifiers[1].setTransform(
Transform.translate(300, 0, 0),
{ duration : 750, curve: 'linear' }
);
mainContext.add(swap_modifiers[0]).add(surfaces[first_swap_index]);
mainContext.add(swap_modifiers[1]).add(surfaces[second_swap_index]);
});
});
A surface has no origin, a (state-)modifier has an origin. Since you don't provide any origin vaue, the default value is set up, which is [0, 0]. See more:
http://famo.us/university/lessons/#/famous-101/positioning/8
Think of your SequentialLayout as a Render Node in your tree. Adding surfaces to SequentialLayout is in essence adding individual nodes to that tree branch. SequentialLayout happens to be adding each item at the same level in the tree.
Sort.bubble_sort_iterative(... changes the location of the surfaces by adding them to the mainContext of your application. This is the same level as the sequentialLayout and makes their origin the same origin as the sequentialLayout. Not what you wanted!
Remember: Adding a modifier to a context will make that context the parent node.
Without knowing the specifics of the above code, we know that we can add a View rather than surfaces to the sequentialLayout and could transition the View's modifiers within each of those items without changing their location in the render tree.
A simple code example of views in the sequential layout:
arr.forEach(function(el) {
var surfSize = base_dims.map(function(d) { return d + (el - min); });
console.log(size);
var view = new View();
view.mod = new StateModifier({ size: surfSize });
view.surface = new Surface({
content: el,
size: [undefined, undefined],
properties: {
backgroundColor: 'rgb(240, 238, 233)',
textAlign: 'center',
padding: '5px',
border: '2px solid rgb(210, 208, 203)',
marginTop: '50px',
marginLeft: '50px'
}
});
view.add(view.mod).add(view.surface);
surfaces.push(view);
});
Trying to swap out the views from one to the other will give you some unexpected results. It would be better to just swap out the options and content values.
How can I attach a StateModifier to a Surface that resides in a GridLayout?
My Code looks something like this:
//...
var grid = new Gridlayout({dimensions: [2,1]});
var surfaces = [];
grid.sequenceFrom(surfaces);
var surface01 = new Surface({content: 'Surface 01'});
var surface02 = new Surface({content: 'Surface 02'});
surfaces.push(surface01,surface02);
this._node.add(grid);
//...
Since the surfaces are not added to the render tree explicitly like:
this._node.add(modifier).add(surface)
I don't know how I can attach Modifiers to the surfaces?! Am I missing something? Any help is much appreciated
You will need to add a view as your sequence from items. The example code below uses a RenderNode as the view item and adds the StateModifier and Surface
Example jsBin Code [v0.3.0 of Famo.us]
var mainContext = Engine.createContext();
var surfaces = [];
var grid = new GridLayout({
dimensions: [2, 1]
});
var counter = 0;
_getView = function(name) {
var rnode = new RenderNode();
var state = new StateModifier({
size: [undefined, 500]
});
var surface = new Surface({
content:name,
properties: {
backgroundColor: "hsl(" + (counter * 360 / 8) + ", 100%, 50%)",
lineHeight: '500px',
textAlign: 'center',
cursor: 'pointer'
}
});
rnode.add(state).add(surface);
counter += 1;
return rnode;
};
surfaces.push(_getView('Surface 1'));
surfaces.push(_getView('Surface 2'));
grid.sequenceFrom(surfaces);
mainContext.add(grid);
If I'm not missing anything you want to modify the state of your surface inside the gridLayout, by clicking on one of them??
The gridLayout has an array of state that you can access by typing gridlayout._states['index of your surface']
var mainContext = Engine.createContext();
var surfaces = [];
var GridLayout = new GridLayout({
dimensions: [4,10]
});
for (var i = 0; i<40; i++){
var surface = new Surface({
content: 'surface' + i,
size: [200, 200]
});
surface.index = i;
surfaces.push(surface);
});
GridLayout.sequenceFrom(surfaces);
//now if you want to modify the state of the surface with index 2
GridLayout._states[2].set(Transform.rotateX(Math.PI/2)); // your surface will rotate by 90° on X axis
mainContext.add(GridLayout);
In my context I have a scroll view, and I'm trying to position the child elements within the view using origin/align properties in a state modifier. However for some reason, when I scroll to the bottom, the last surface isn't displayed correctly.
I can see this is because I'm using origin/align but I'm not sure on the correct way to position child elements within a scroll view? If someone could point me in the right direction that would be great.
Thanks
Code:
main.js
// Create the main context
var mainContext = Engine.createContext();
// Create scroll view
var scrollView = new Scrollview();
var surfaces = [];
scrollView.sequenceFrom(surfaces);
// Create logo
var logoNode = new RenderNode();
var logo = new ImageSurface({
size: [150, 112],
content: 'img/logo.png',
classes: ['logo']
});
// Center logo within context, center and set opacity
var modifier = new StateModifier({
align: [0.5, 0.05],
origin: [0.5, 0.05],
});
logoNode.add(modifier).add(logo);
logo.pipe(scrollView);
surfaces.push(logoNode);
var tribesLength = Object.keys(tribes).length;
for (var t = 0; t < tribesLength; t++) {
var tribe = new TribesView({tribes: tribes, tribe: t});
tribe.pipe(scrollView);
surfaces.push(tribe);
}
mainContext.add(scrollView);
TribesView.js
function TribesView() {
View.apply(this, arguments);
_displayTribe.call(this);
}
TribesView.prototype = Object.create(View.prototype);
TribesView.prototype.constructor = TribesView;
TribesView.DEFAULT_OPTIONS = {
tribes: {},
tribe: 0,
};
function _displayTribe() {
var tribes = this.options.tribes;
var tribe = this.options.tribe;
var node = new RenderNode();
var surface = new Surface({
size: [, 100],
content: tribes[tribe]['name'],
properties: {
background: tribes[tribe]['bg'],
color: 'blue'
}
});
var modifier = new StateModifier({
origin: [0, 0.1],
align: [0, 0.1]
});
node.add(modifier).add(surface);
surface.pipe(this._eventOutput);
this.add(node);
}
module.exports = TribesView;
The problem comes as you suspected, from the use of..
var modifier = new StateModifier({
origin: [0, 0.1],
align: [0, 0.1]
});
in the _displayTribe function. You have to remember that TribeView although labeled a view is nothing representative of something visual on screen. That means when you add this modifier to a surface inside a view, view thinks it is one place, which will be laid out in scrollview, and the modifier will put it in another place (in your case too low on screen).
It is difficult to give you a clear example, because I do not have the data or images or anything to make this look halfway good. If you want to use modifiers within your TribeViews, take a look at chain modifier. I have found it helpful for creating a sort of container surface without using a containerSurface.
Here is what I did to _displayTribe to give the content text an offset relative to the view..
function _displayTribe() {
var tribes = this.options.tribes;
var tribe = this.options.tribe;
var surface = new Surface({
size: [undefined, 100],
properties: {
color: 'blue',
border:'1px solid black'
}
});
this.add(surface)
var text = new Surface({
size:[undefined,true],
content: "Helloo",
})
chain = new ModifierChain()
var containerModifier = new StateModifier({
size: [undefined, 100],
});
var modifier = new StateModifier({
origin: [0, 0.1],
align: [0, 0.1]
});
chain.addModifier(modifier)
chain.addModifier(containerModifier)
this.add(chain).add(text);
surface.pipe(this._eventOutput);
}
I removed anything 'asset' related since it was not available to me. The first surface I added to the view completely unmodified. This allows us to see where scrollview is placing our view. For the text surface, I am using true sizing, and creating a ModifierChain to simulate the container as I previously mentioned. You do this by defining two modifiers, one for the size of the container, and the other for positioning in the container, then chain them together.
Lots of information, Hope this helps!
I want to have my header and footer almost take up the entire screen (there will just be a thin line left in the middle which will contain a textbox. If the user enters the right password, I want the textbox to disappear and the header and footer to gradually get shorter (making more room for content to appear in the center of the screen).
Is it possible to apply a transition to the height of the header and footer on a HeaderFooterLayout?
How do I show a typical password box where the characters all show as *'s?
Like many animations that are not supported by default, you can add a transition by using the Transitionable class.. Here is an example that expands the header when you click it..
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var HeaderFooterLayout = require("famous/views/HeaderFooterLayout");
var Transitionable = require("famous/transitions/Transitionable");
var Easing = require("famous/transitions/Easing");
var mainContext = Engine.createContext();
var layout = new HeaderFooterLayout({
headerSize: 100,
footerSize: 50
});
var header = new Surface({
size: [undefined, undefined],
content: "Header",
classes: ["red-bg"],
properties: {
lineHeight: "100px",
textAlign: "center"
}
})
var open = false;
header.on("click",function(){
var transition = {duration: 400, curve: Easing.inOutQuad };
var start = open ? 200 : 100 ;
var end = open ? 100 : 200 ;
open = !open;
var transitionable = new Transitionable(start);
var prerender = function(){ layout.setOptions({ headerSize: transitionable.get()} ) };
var complete = function(){ Engine.removeListener('prerender', prerender) };
Engine.on('prerender', prerender);
transitionable.set(end, transition, complete);
});
layout.header.add(header);
layout.content.add(new Surface({
size: [undefined, undefined],
content: "Content",
classes: ["grey-bg"],
properties: {
lineHeight: window.innerHeight - 150 + 'px',
textAlign: "center"
}
}));
layout.footer.add(new Surface({
size: [undefined, 50],
content: "Footer",
classes: ["red-bg"],
properties: {
lineHeight: "50px",
textAlign: "center"
}
}));
mainContext.add(layout);
As for the password field, you simply create an InputSurface and set it's type to password..
inputSurface = new InputSurface({
size:[200,60],
type: 'password'
});
^ Watch out for performance issues when using Transitionable on headerSize. Especially iPhones with iOS 7 seem to be acting glitchy.
You can also animate header / footer size via CSS transitions, although it's a bit of a bubblegum fix and has it's pitfalls:
var headerContainer = new ContainerSurface({
size: [undefined, 50],
classes: ['my-header']
});
layout.header.add(headerContainer);
headerContainer.setSize([undefined,300]);
Then in CSS:
.my-header { transition: 200ms all; }