Famo.us ScrollView scroll like in a chat - javascript

I have ScrollView and sequence of Surface elements in it.
I want to be able to scroll to the latest element as soon as it appears in the array.
Here is a demo, I want it to be scrolled just like in any chat application... old surfaces go beyond the scene, and new will be always at the bottom. The question is the same as here: Famo.us how to make a scrollview be filled from bottom to top (or right to left)?
http://jsfiddle.net/LA49a/
Famous.loaded(function () {
var Engine = Famous.Core.Engine;
var Surface = Famous.Core.Surface;
var Scrollview = Famous.Views.Scrollview;
var Timer = Famous.Utilities.Timer;
var mainContext = Engine.createContext();
var scrollview = new Scrollview();
var surfaces = [];
var i = 0;
scrollview.sequenceFrom(surfaces);
Timer.setInterval(function () {
var temp = new Surface({
content: "Surface: " + (i + 1),
size: [undefined, 50],
properties: {
backgroundColor: "hsl(" + (i * 360 / 40) + ", 100%, 50%)",
lineHeight: "50px",
textAlign: "center"
}
});
temp.pipe(scrollview);
surfaces.push(temp);
i++;
// scrollview.goToNextPage();
}, 400);
mainContext.add(scrollview);
});

This is supported in the famous-flex ScrollView by setting the alignment property to 1.
See this demo:
https://github.com/IjzerenHein/famous-flex-chat

I'm assuming you're putting in a very large, round number to get the scrollview to move to the end, which shows your 'latest' element.
If you've got surfaces that are the same width (assuming an X orientation of the scrollview) you might get a smoother result by checking for the actual number of pixels you need to move to position the last item at the right edge of scrollview. You have the original array that you ran sequenceFrom() on. Multiply the number of items in that by your surface width and subtract pixels for the scrollview's width (which could be set to 'undefined' in which case you'll need to look at the window's width) to adjust for getting the latest element to the right side of the scrollview instead of the left side.
If your surfaces are not the same size, you might change things to track the x-offset of each surface as you add them to the backing array by just adding a property to the surface. Then you could just ask the surface what its offset is and subtract the right amount for the width of the scrollview.
Hope that helps you get closer.

Solved, by rotating ScrollView by 180˚ and all surfaces inside it also by 180˚ 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);
});

hmm, that's pretty crafty! Its a couple of extra matrices which could get very slow if you have a lot of text content in those nodes tho...
By using a huge number, this is just to force famo.us to scroll to the end of the view, I assume?
if the render had happened you could also:
pos = getPosition(nodeNum) // for last elements scrollposition
setPosition(pos)
But if the insert happens in same code block as your movement control it's not rendered yet.
There's a complicated workaround offered here using events to trigger the sizing calculation after the deploy/render has occurred:
how can we get the size of a surface within famo.us?
and another trick here to make famous recalculate sizes from the inimitable #johntraver
how to make famo.us recalculate sizes?
did you also look at Engine.nextTick() or JQuery.deferred ?
It seems that immediately after you insert the content there will be a "not rendered" instant but after an update you could get the sizes OK.

Related

How to resize a sprite pixi.js

I have some PixiJS code that generates a sprite:
let type = "WebGL";
if (!PIXI.utils.isWebGLSupported()) {
type = "canvas"
}
PIXI.utils.sayHello(type);
const app = new PIXI.Application({
width: 1000, height: 600, backgroundColor: 0x1099bb, resolution: window.devicePixelRatio || 1,
});
document.body.appendChild(app.view);
function renderSprite() {
const sprite = new PIXI.Sprite(PIXI.Texture.from("BEE-e1414622656271.png",));
sprite.anchor.set(0.5);
app.stage.addChild(sprite);
sprite.x = app.screen.width/2;
sprite.y = app.screen.height/2;
}
renderSprite();
The code works. However, the sprite generated is so large that it goes beyond the borders of the container.
I need to set the sprite's size, so I tried using the height and width options of baseTexture:
const sprite = new PIXI.Sprite(PIXI.Texture("BEE-e1414622656271.png", baseTexture=PIXI.baseTexture(options={
"height": 100,
"width": 100
})));
But I got an error stating that PIXI.baseTexture was not a function.
How do I resize the sprite so it fits within the container?
You can control position and size of sprite after it is created like this:
var sprites = {};
sprites.cat = new PIXI.Sprite(catTexture);
//Change the sprite's position
sprites.cat.x = 96;
sprites.cat.y = 96;
//Change the sprite's size
sprites.cat.width = 30;
sprites.cat.height = 150;
//Add the cat to the stage so you can see it
stage.addChild(sprites.cat);
and then later - even when this sprite is added to stage container - you can further modify its width and height.
Try putting in your game loop something like:
sprites.cat.width += 0.5;
See more in docs: https://github.com/kittykatattack/learningPixi#size-and-scale

Photoshop Scripting: Creating a selection, then fitting a text box inside

Using Javascript for Photoshop (ExtendScript) I am trying make a dynamic text box.
My end goal is a script that the user writes their own text into a new window pop up, then that text is positioned in the bottom 10% of the image, scaled dynamically to fit the bottom 10% of an image.
So, whatever the dimensions of the lower 10% (created by resizeCanvas argument) the text always fits within it comfortably. So when the user adds a few words it does not look too small and when they add a longer sentence it does not overspill.
Question originally asked here (with pictures) https://forums.adobe.com/message/10952562#10952562
Example Start Image:
start image
Window Pop up :
custom PopUp
Small Text Outcome:
outcome1
Long Text Outcome:
outcome 2
This is where I am currently:
#target Photoshop
app.preferences.rulerUnits = Units.PIXELS;
app.preferences.typeUnits = TypeUnits.PIXELS;
var doc = app.activeDocument;
var hgt10 = doc.height / 10;
var FillColor = new SolidColor;
FillColor.rgb.hexValue = 'ff0000';
var newHgt = doc.height + hgt10
doc.resizeCanvas(doc.width, newHgt, AnchorPosition.TOPCENTER);
var win = new Window('dialog', "Custom Text");
var grp1 = win.add('group');
grp1.alignChildren = "top";
var txt = grp1.add('edittext', [0, 0, 300, 150], "Write your message", {multiline: true, scrolling: true});
txt.active = true;
var grp2 = win.add('group');
var goBtn = grp2.add('button', undefined, "Run");
grp2.add('button', undefined, "Cancel");
var newLyr = doc.artLayers.add();
newLyr.kind = LayerKind.TEXT;
// CONTENTS
var txtLyr = newLyr.textItem
txtLyr.font = "ArialMT"
txtLyr.contents = txt.text
var uV = new UnitValue(20,'mm')
txtLyr.size = UnitValue(25,'mm')
txtLyr.color = FillColor;
txtLyr.position = [25, 2200] // x, y - Need to fix position to fit in the lower 10%
goBtn.onClick = function() {
win.close();
}
win.show();
So the areas I would like some assistance with is:
1) Getting the text to be positioned in the new area (created by the resizeCanvas argument extending by 10%)
2) Adding line breaks and automatically sizing to fit a variety of length text files into the field.
Kind Regards,
To add line breaks, replace all \ns with \rs (at least for Windows, I have a feeling that this Mac version may require something else, but I'm not sure):
txtLyr.contents = txt.text.replace(/\n/gm, '\r')
To position text you need to know the size (that'll depend on lines number) and leading.
var lines = txt.text.split('\n'); //getting the number of lines
var maxSize = hgt10 / lines.length; //calculating max size for a line (including the leading) = 10% of doc height devided by lines number
newLyr.textItem.useAutoLeading = true // making sure that text uses autoleading
var leading = newLyr.textItem.autoLeadingAmount; //autoleading value is in % of text size
maxSize = maxSize / leading * 100; // recalculating text size based on leading in pixels
txtLyr.size = maxSize;
txtLyr.position = [25, originalDocHeight + maxSize]
Result with one line and with several lines:

Fabric.js ruler with zoom

I' ve question.
I'm coding an editor like this, and I also want a ruler to add it (which change values with mouse-scroll (zoom in and out) )
But I can't do it. I tried all rulers on github but they are running only body tag.
Unfortunately, there are no documents for zoom in or zoom out.
I'm new with HTML5 canvas and I'm stuck. Waiting for your helps. Thanks!
One approach would be to have three separate canvases, one as the main canvas and one each for the top/left rulers.
When zooming in/out, you set the zoom level on the main canvas, but you will need to redraw the rulers manually, here is a really simple example:
function redrawRulers() {
topRuler.clear();
leftRuler.clear();
topRuler.setBackgroundColor('#aaa');
leftRuler.setBackgroundColor('#aaa');
zoomLevel = mainCanvas.getZoom();
for (i = 0; i < 600; i += (10 * zoomLevel)) {
var topLine = new fabric.Line([i, 25, i, 50], {
stroke: 'black',
strokeWidth: 1
});
topRuler.add(topLine);
var leftLine = new fabric.Line([25, i, 50, i], {
stroke: 'black',
strokeWidth: 1
});
leftRuler.add(leftLine);
}}
Fiddle
UPDATE: For the rulers, here's some very simple code to draw figures on the top ruler (fiddle also updated):
// Numbers
for (i = 0; i < 600; i += (100 * zoomLevel)) {
var text = new fabric.Text((Math.round(i / zoomLevel)).toString(), {
left: i,
top: 10,
fontSize: 8
});
topRuler.add(text);
}
Now, of course you will want to convert those numbers into whatever units are appropriate for your application. Also, you may want to consider drawing the numbers at more frequent intervals when you're zoomed in, and to space them out more when you're zoomed out. But I think I've given you enough to get you going here.
Add the below code in document.ready this will bind a mouse scroll event with the canvas and hence the zoom in and out will happen when you use mousewheel.
$(mainCanvas.wrapperEl).on('mousewheel', function(e) {
var dir = e.originalEvent.wheelDelta;
if (dir > 0){
var ZoomValue = mainCanvas.getZoom() * 1.2;
} else {
var ZoomValue = mainCanvas.getZoom() * .83333333333333333;
}
redrawRulers();
mainCanvas.setZoom(ZoomValue, e);
e.originalEvent.returnValue = false;
});
Updated Fiddle For Mouse Scroll

Keeping a portion of an image always visible on the canvas

I want to make sure that my instance of fabric.Image is always visible.
It doesn't need to be 100% visible, but a portion of it should always be seen. I've seen a few other questions that are similar and I've based my solution on them, but I haven't found anything that completely solves the problem.
My current solution works when no rotation (angles) have been applied to the image. If the angle is at 0 then this solution is perfect, but once the angle start changing I'm having problems.
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords();
var boundingRect = obj.getBoundingRect();
var max_pad_left_over_width = 50;//obj.currentWidth * .08;
var max_pad_left_over_height = 50;//obj.currentHeight * .08;
var max_top_pos = -(obj.currentHeight - max_pad_left_over_height);
var max_bottom_pos = obj.canvas.height - max_pad_left_over_height;
var max_left_pos = -(obj.currentWidth - max_pad_left_over_width);
var max_right_pos = obj.canvas.width - max_pad_left_over_width;
if(boundingRect.top < max_top_pos) {
obj.setTop(max_top_pos);
}
if(boundingRect.left < max_left_pos){
obj.setLeft(max_left_pos);
}
if(boundingRect.top > max_bottom_pos) {
obj.setTop(max_bottom_pos);
}
if(boundingRect.left > max_right_pos) {
obj.setLeft(max_right_pos);
}
});
I've created an example: https://jsfiddle.net/krio/wg0aL8ef/24/
Notice how when no rotation (angle) is applied you cannot force the image to leave the visible canvas. Now, add some rotation to the image and move it around. How can I get the rotated image to also always stay within view? Thanks.
Fabric.js provides with two object properties isContainedWithinRect and intersectsWithRect which we can use to solve the problem.
An object should not be allowed to go outside the canvas boundaries. This can be achieved if we somehow manage to make sure that the object is either inside the canvas, or at least intersects with the canvas boundaries.
For this case, since you want some portion of the image inside the canvas, we will take a rect smaller than the canvas instead of canvas boundaries to be the containing rect of the image.
Using the properties mentioned to make sure that the above two conditions are satisfied after any movement. Here's how the code looks like:
var canvas = new fabric.Canvas('c');
var imgElement = document.getElementById('my-img');
var imgInstance = new fabric.Image(imgElement, {
top: 0,
left: 0
});
imgInstance.setScaleX(0.6);
imgInstance.setScaleY(0.6);
canvas.add(imgInstance);
var prevLeft = 0,
prevTop = 0;
var max_pad_left_over_width = imgInstance.currentWidth * .08;
var max_pad_left_over_height = imgInstance.currentHeight * .08;
var max_top_pos = max_pad_left_over_height;
var max_bottom_pos = imgInstance.canvas.height - max_pad_left_over_height;
var max_left_pos = max_pad_left_over_width;
var max_right_pos = imgInstance.canvas.width - max_pad_left_over_width;
var pointTL = new fabric.Point(max_left_pos, max_top_pos);
var pointBR = new fabric.Point(max_right_pos, max_bottom_pos);
canvas.on('object:moving', function(e) {
var obj = e.target;
obj.setCoords();
if (!obj.intersectsWithRect(pointTL, pointBR) && !obj.isContainedWithinRect(pointTL, pointBR)) {
obj.setTop(prevTop);
obj.setLeft(prevLeft);
}
prevLeft = obj.left;
prevTop = obj.top;
});
Here's the link to the fiddle: https://jsfiddle.net/1ghvjxbk/ and documentation where these properties are mentioned.

Need more than one of the same jQuery effect on same page

So I'm using this jquery background scroller, basically I want to get more than two on the same page (going at different speeds) and I can't figure out how to do it.
http://www.devirtuoso.com/2009/07/how-to-build-an-animated-header-in-jquery/
var scrollSpeed = 70; // Speed in milliseconds
var step = 1; // How many pixels to move per step
var current = 0; // The current pixel row
var imageHeight = 4300; // Background image height
var headerHeight = 300; // How tall the header is.
//The pixel row where to start a new loop
var restartPosition = -(imageHeight - headerHeight);
function scrollBg(){
//Go to next pixel row.
current -= step;
//If at the end of the image, then go to the top.
if (current == restartPosition){
current = 0;
}
//Set the CSS of the header.
$('#header').css("background-position","0 "+current+"px");
}
//Calls the scrolling function repeatedly
var init = setInterval("scrollBg()", scrollSpeed);
I can add in other css to the script but I want a different speed for other divs.
#andy, here is Tom Th's idea worked up; ie a js constructor function, from which you can instantiate multiple instances :
function bgScroller(options) {
var settings = {
containerID: '', //id of the scroller's containing element
scrollSpeed: 50, //Speed in milliseconds
step: 1, //How many pixels to move per step
imageHeight: 0, //Background image height
headerHeight: 0, //How tall the header is.
autoStart: true
};
if(options) {
jQuery.extend(settings, options);
}
var current = 0, // The current pixel row
restartPosition = -(settings.imageHeight - settings.headerHeight), //The pixel row where to start a new loop
interval = null,
$container = jQuery('#' + settings.containerID),
that = {};
if(!$container.length || !settings.imageHeight || !settings.headerHeight) {
return false; //nothing will work without these settings so let's not even try
}
function setBg() {
$container.css("background-position", "0 " + current + "px");
}
function scrollBg(){
current -= settings.step;//Go to next pixel row.
//If at the end of the image, then go to the top.
if (current <= restartPosition){
current = 0;
}
setBg();
}
that.reset = function() {
that.stop();
current = 0;
setBg();
}
that.start = function() {
interval = setInterval(scrollBg, settings.scrollSpeed);
};
that.stop = function(){
clearInterval(interval);
};
that.reset();
if(settings.autoStart) {
that.start();
}
return that;
}
Parameters are passed as properties of an object literal "map", overriding the hard-coded defaults in the constructor. For any parameters not included, the default values will be used. Here's a couple of examples :
var headerScroller = new bgScroller({
containerID: "header",
scrollSpeed: 70, //Speed in milliseconds
imageHeight: 4300, //Background image height
headerHeight: 300, //How tall the header is.
});
var otherScroller = new bgScroller({
containerID: "myOtherDiv",
scrollSpeed: 30, //Speed in milliseconds
imageHeight: 2800, //Background image height
headerHeight: 200, //How tall the header is.
});
I have included three public methods; .reset(), .start() and .stop(), which provide limited control over scrollers after instantiation. Use as follows:
headerScroller.stop();
headerScroller.reset();
headerScroller.start();
Notes:
Working demo here.
Dependencies: jQuery 1.0 or later
.reset() calls .stop() automatically so there's no need to call .stop() beforehand.
No provision is made for changing settings after instantiation but this would be possible with a little more thought.
jQuery plugin would be similar but would take a little more time to develop (with little advantage).

Categories